Home > Windows にまつわる e.t.c.

スケジュールジョブ(PowerShell)でパスワードをセキュアに使う(証明書編)


スケジュールジョブ(PowerShell)でパスワードをセキュアに使う(セキュアストリング編)」で、セキュアストリングを使ってパスワードをセキュアにハンドリングして認証する方法を書きましたが、セキュアストリングは実行アカウントに紐づいているので、ワークグループで展開するには何かと面倒です。

良い方法がないかと思っていたら、MS エバンジェリストの安納さんが Blog で証明書を使ってパスワードハンドリングする方法を紹介していたので、これを下敷きにしてセキュアなパスワードのハンドリング方法です。

 

手順をざっくり説明すると...

暗号化準備

Windows SDK インストール
証明書作成
拇印の取得

展開準備

パスワード暗号化
証明書エクスポート

展開と活用

証明書インポート
パスワード復号

こんな流れになります。

証明書は コンピューター証明書にストアしているので、証明書アクセスには管理権限が必要ですが、実行アカウントをビルトインの「SYSTEM」にしても問題なくパスワードを取り出すことができます。

 

証明書の作成

まずは使用する証明書ですが、AD CSで発行すると有効期限がどうしても限られるので、自己署名証明書で有効期限を2050/12/31って証明書を作ります。

 

自己署名証明書の作成は、makecat.exe を使います。

makecat.exe はVisual Studio に含まれていますが、Visual Studio をインストールしていない環境では Windows SDK をインストールします。

Windows Software Development Kit (SDK) for Windows 8.1

ダウンロードした sdksetup.exe を実行すると、何をインストールするのかを聞いてきますので「Windows Software Development Kit」のみをインストールします。

 

makecert.exe は以下にインストールされます。

C:\Program Files (x86)\Windows Kits\8.1\bin\x64\makecert.exe

C:\Program Files (x86)\Windows Kits\8.1\bin\x86\makecert.exe

C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x64\makecert.exe

C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x86\makecert.exe

 

証明書の作成と拇印の取得

makecat.exe のあるフォルダをカレントにして、以下のコマンドで証明書を作成します。(PowerShellプロンプトで実行する前提です)

証明書は、コンピューター証明書の個人に格納されます。

.\makecert -sky exchange -r -n "CN=Encpassword" -pe -a sha256 -len 2048 -e 12/31/2050 -sr LocalMachine -ss My "EncPassword.cer"

 

関数にするとこんな感じになります

########################################################
# 証明書の作成
########################################################
function MakeCert(
                    $CertName   # 証明書名
                ){
    .\makecert -sky exchange -r -n "CN=$CertName" -pe -a sha256 -len 2048 -e 12/31/2050 -sr LocalMachine -ss My "$CertName.cer"
}

 

証明書は拇印をキーにハンドリングするので、証明書ができたら拇印を取得します。

$CertName = "YourCertName"
$ThumbprintFile = "C:\Work\Thumbprint.txt"

# 拇印の取得
$Thumbprint = (Get-ChildItem Cert:\LocalMachine\My | ? {$_.Subject -eq "CN=$CertName"}).Thumbprint

# 拇印をファイルに出力
$Thumbprint | Set-Content $ThumbprintFile

 

関数にするとこんな感じになります

########################################################
# 拇印の取得
########################################################
function GetThumbprintFile(
                    $CertName,      # 証明書名
                    $ThumbprintFile # 拇印ファイルのフルパス
                ){
    # 拇印の取得
    $Thumbprint = (Get-ChildItem Cert:\LocalMachine\My | ? {$_.Subject -eq "CN=$CertName"}).Thumbprint

    # 拇印をファイルに出力
    $Thumbprint | Set-Content $ThumbprintFile
}

 

パスワードの暗号化

それでは証明書を使ってパスワードを暗号化しましょう。(赤文字が暗号化するパスワード)

## パスワードの暗号化
$PlainPassword = "P@ssw0rdP@ssw0rd"
$ThumbprintFile = "C:\Work\Thumbprint.txt"
$PasswordFile = "C:\Work\Password.txt"

# 証明書の拇印(Thumbprint)読み込み
$Thumbprint = Get-Content $ThumbprintFile

# パスワードの暗号化(暗号化してBase64で出力)
$Cert = get-item cert:\LocalMachine\MY\$Thumbprint
Add-type –AssemblyName System.Security
$pass = [Text.Encoding]::UTF8.GetBytes($PlainPassword)
$content = new-object Security.Cryptography.Pkcs.ContentInfo –argumentList (,$pass)
$Enveloped = new-object Security.Cryptography.Pkcs.EnvelopedCms $content
$Enveloped.Encrypt((new-object System.Security.Cryptography.Pkcs.CmsRecipient($Cert)))
$Password = [Convert]::ToBase64String($Enveloped.Encode())

# 暗号化されたパスワードをファイルに出力
$Password | Set-Content $PasswordFile

 

関数にするとこんな感じになります

########################################################
# パスワードの暗号化
########################################################
function MakePasswordFile(
                    $PlainPassword,     # 暗号化するパスワード
                    $ThumbprintFile,    # 拇印ファイルのフルパス
                    $PasswordFile       # パスワードファイルのフルパス
                ){
    # 証明書の拇印(Thumbprint)読み込み
    $Thumbprint = Get-Content $ThumbprintFile

    # パスワードの暗号化(暗号化してBase64で出力)
    $Cert = get-item cert:\LocalMachine\MY\$Thumbprint
    Add-type –AssemblyName System.Security 
    $pass = [Text.Encoding]::UTF8.GetBytes($PlainPassword) 
    $content = new-object Security.Cryptography.Pkcs.ContentInfo –argumentList (,$pass) 
    $Enveloped = new-object Security.Cryptography.Pkcs.EnvelopedCms $content 
    $Enveloped.Encrypt((new-object System.Security.Cryptography.Pkcs.CmsRecipient($Cert))) 
    $Password = [Convert]::ToBase64String($Enveloped.Encode())

    # 暗号化されたパスワードをファイルに出力
    $Password | Set-Content $PasswordFile
}

 

別のコンピューターでパスワードを復号するために証明書をエクスポートする

出力された暗号化パスワードを別のコンピューターで使うには、復号用の証明書をインポートし、インポートした証明書を使ってパスワードを復号します。

まずは、証明書を持っているコンピューターから復号用証明書をエクスポートします。

GUI でエクスポートしても良いのですが、PowerShell でエクスポートするのならこんな感じです。

# 証明書のエクスポート(Windows Server 2012/ Windows 8以降の場合)
$ThumbprintFile = "C:\Work\Thumbprint.txt"
$CertFile = "C:\Work\EncPassword.pfx"

# 証明書の拇印(Thumbprint)読み込み
$Thumbprint = Get-Content $ThumbprintFile

# パスワードを指定して証明書のエクスポート
$Password = ConvertTo-SecureString -AsPlainText -Force "P@ssw0rd"
$Cert = get-item cert:\LocalMachine\MY\$Thumbprint
Export-PfxCertificate -Cert $Cert -FilePath $CertFile -Password $Password

 

関数にするとこんな感じになります

##################################################################
# 証明書のエクスポート(Windows Server 2012/ Windows 8以降の場合)
##################################################################
function ExportCert(
                    $ThumbprintFile,    # 拇印ファイルのフルパス
                    $CertFile,          # 証明書ファイルのフルパス
                    $ExportPassword     # Export パスワード
                ){
    # 証明書の拇印(Thumbprint)読み込み
    $Thumbprint = Get-Content $ThumbprintFile

    # パスワードを指定して証明書のエクスポート
    $Password = ConvertTo-SecureString -AsPlainText -Force $ExportPassword
    $Cert = get-item cert:\LocalMachine\MY\$Thumbprint
    Export-PfxCertificate -Cert $Cert -FilePath $CertFile -Password $Password
}

 

# 証明書のエクスポート(Windows Server 2008 R2 / Windows 7以前の場合)
$ThumbprintFile = "C:\Work\Thumbprint.txt"
$CertFile = "C:\Work\EncPassword.pfx"

# 証明書の拇印(Thumbprint)読み込み
$Thumbprint = Get-Content $ThumbprintFile

# パスワードを指定して証明書のエクスポート
$Cert = get-item cert:\LocalMachine\MY\$Thumbprint
$bytes = $Cert.Export("Pfx","P@ssw0rd")
[System.IO.File]::WriteAllBytes($CertFile, $bytes)

 

関数にするとこんな感じになります

######################################################################
# 証明書のエクスポート(Windows Server 2008 R2 / Windows 7以前の場合)
######################################################################
function ExportCert(
                    $ThumbprintFile,    # 拇印ファイルのフルパス
                    $CertFile,          # 証明書ファイルのフルパス
                    $ExportPassword     # Export パスワード
                ){
    # 証明書の拇印(Thumbprint)読み込み
    $Thumbprint = Get-Content $ThumbprintFile

    # パスワードを指定して証明書のエクスポート
    $Cert = get-item cert:\LocalMachine\MY\$Thumbprint
    $bytes = $Cert.Export("Pfx", $ExportPassword) 
    [System.IO.File]::WriteAllBytes($CertFile, $bytes)
}

 

別のコンピューターでパスワードを復号するために証明書をインポートする

続いて、対象のコンピューターで証明書をインポートします。インポート先はコンピューター証明書の個人です。
GUIでインポートしても良いのですが、PowerShellでインポートするのならこんな感じです。

# 証明書のインポート(Windows Server 2012/ Windows 8以降の場合)
$Password = ConvertTo-SecureString -AsPlainText -Force "P@ssw0rd"
$CertFile = "C:\common\EncPassword.pfx"
$Cert = "Cert:\LocalMachine\My"
Import-PfxCertificate -FilePath $CertFile -CertStoreLocation $Cert -Password $Password

 

# 証明書のインポート(Windows Server 2008 R2 / Windows 7以前の場合)
$CertFile = "C:\common\EncPassword.pfx"
certutil -p "P@ssw0rd" -importpfx $CertFile NoExport

 

インポートしたら、EncPassword.pfx は不要なので削除します(パスワード保護がかかっているとはいえ、秘密鍵を持っているので必ず削除します)

 

インポートはよく使うので、ps1 にしておくと便利です。

InstallCertificate.ps1

##############################################################
#
# 証明書インストーラー
# このスクリプト、証明書ファイル、拇印ファイルを適当なフォルダーにコピーして実行
#
##############################################################
if (-not(([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))) {
    echo "[FAIL] 実行には管理権限が必要です"
    exit
}

$ScriptFileName = $MyInvocation.MyCommand.Path
$ScriptDir = Split-Path $ScriptFileName -Parent

# 証明書ファイル名
$CertificateFileName = "EncPassword.pfx"

# 証明書パスワード
$CertificatePassword = "P@ssw0rd"

# 拇印ファイル名
$ThumbprintFileName = "Thumbprint.txt"

# 証明書ファイルフルパス
$CertificateFileFullPath = Join-Path $ScriptDir $CertificateFileName
if( -not (Test-Path $CertificateFileFullPath) ){
    echo "[FAIL] $CertificateFileFullPath not found !!"
    exit
}

# 拇印ファイルのフルパス
$ThumbprintFileFullPath = Join-Path $ScriptDir $ThumbprintFileName
if( -not (Test-Path $ThumbprintFileFullPath) ){
    echo "[FAIL] $ThumbprintFileFullPath not found !!"
    exit
}

# 証明書の拇印の読み込み
$Thumbprint = Get-Content $ThumbprintFileFullPath

$CertPath = Join-Path "cert:\LocalMachine\MY\" $Thumbprint

# 証明書インストール確認
if( test-path $CertPath ){
    echo "[INFO] 証明書はインストール済みです"
}
# 証明書がインストールされていないのでインストール
else{
    certutil -p $CertificatePassword -importpfx $CertificateFileFullPath NoExport
    echo "[INFO] 証明書をインストールしました"
}

# 証明書ファイル削除
del $CertificateFileFullPath

# インストールスクリプト削除
del $ScriptFileName

 

パスワードの復号

拇印ファイルと、パスワードファイルを読み込んで、パスワードを証明書復号します。

# パスワードの復号
$PasswordFile = "C:\common\Password.txt"
$ThumbprintFile = "C:\common\Thumbprint.txt"

# 暗号化や復号化に必要な System.Security アセンブリを読み込む
Add-type –AssemblyName System.Security

# 証明書の拇印(Thumbprint)の読み込み
$Thumbprint = Get-Content $ThumbprintFile

# 証明書を取得
$Cert = get-item cert:\LocalMachine\MY\$Thumbprint

# 暗号化したパスワードの読み込み
$Password = Get-Content $PasswordFile

# Base64でエンコードされたパスワードをデコードし、証明書を使って復号化(Decrypt)
$Enveloped = new-object Security.Cryptography.Pkcs.EnvelopedCms
$Enveloped.Decode([Convert]::FromBase64String( $Password ))
$Enveloped.Decrypt( $Cert )

# バイト型からストリング型に変換
$PlainPassword = [Text.Encoding]::UTF8.GetString($Enveloped.ContentInfo.Content)

# 平文に復号されたパスワード
$PlainPassword

このスクリプトを実行するバッチに組み込んで使います。

復号もよく使うので、関数化しておくと便利です。

#######################################################
# 証明書復号
# 復号に失敗したら $null を返す
#######################################################
function Encrypt(
                $ThumbprintFile,    # 拇印ファイルフルパス
                $PasswordFile       # パスワードファイルフルパス
                ){

    if( -not (test-path $PasswordFile )){
        $ErrorMessage = "[FAIL] Encrypt FAIL. Password file " + $PasswordFile + " not found !!"
        Echo $ErrorMessage
        return $null
    }

    if( -not (test-path $ThumbprintFile )){
        $ErrorMessage = "[FAIL] Encrypt FAIL. Thumbprint file " + $ThumbprintFile + " not found !!"
        echo $ErrorMessage
        return $null
    }

    ### 証明書復号のメイン処理
    Add-type –AssemblyName System.Security
    $Thumbprint = Get-Content $ThumbprintFile
    $CertPath = "cert:\LocalMachine\MY\" + $Thumbprint
    if(-not(test-path $CertPath)){
        $ErrorMessage = "[FAIL] Encrypt FAIL. Certificate not found !!"
        echo $ErrorMessage
        return $null
    }
    $Cert = get-item $CertPath
    $Password = Get-Content $PasswordFile
    $Enveloped = new-object Security.Cryptography.Pkcs.EnvelopedCms
    $Enveloped.Decode([Convert]::FromBase64String( $Password ))
    $Enveloped.Decrypt( $Cert )
    $PlainPassword = [Text.Encoding]::UTF8.GetString($Enveloped.ContentInfo.Content)
    return $PlainPassword
}

 

PowerShell のコマンドレットで指定する -Credential オプションに引き渡す資格情報は ID / Password の平文ではないので、資格情報を作成する必要があります。
その場合はこちらをどうぞ。

平文 ID Password から 資格情報を作成する

 

元ネタ

フィールドSEあがりの安納です

【PowerShell】証明書を使用してパスワードを暗号化する

【PowerShell】証明書を使用して暗号化したパスワードを復号する

 

関連情報

AES 256 の PowerShell 実装
http://www.vwnet.jp/Windows/PowerShell/AES.htm

SHA-2(SHA256) の PowerShell 実装
http://www.vwnet.jp/Windows/PowerShell/SHA256.htm

RSA 公開鍵暗号の PowerShell 実装
http://www.vwnet.jp/Windows/PowerShell/RSACrypto.htm

RSA 電子署名(SHA256)の PowerShell 実装
http://www.vwnet.jp/Windows/PowerShell/RSASignature.htm

HMAC(SHA256) の PowerShell 実装
http://www.vwnet.jp/Windows/PowerShell/HMAC-SHA256.htm

関数を PowerShell プロンプトで実行する
http://www.vwnet.jp/Windows/PowerShell/2016100401/UseFunctionInPsPrompt.htm

 

back.gif (1980 バイト)

home.gif (1907 バイト)

Copyright © MURA All rights reserved.