直接使う
## 説明 ##
CryptProtectData function の Wrapper が System.Security.Cryptography.ProtectedData です。以上......。
<参考にしたサイト>
ProtectedData クラス
CryptProtectData 関数 (dpapi.h)
Add-Type -AssemblyName System.Security
$PlainText = "PASSWORD"
$ByteArray = [Text.Encoding]::UTF8.GetBytes($PlainText)
$EncryptedBytes = [Security.Cryptography.ProtectedData]::Protect($ByteArray, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
[Convert]::ToBase64String($EncryptedBytes) | Tee-Object -Variable Base64
$r_EncryptedBytes = [Convert]::FromBase64String($Base64)
$r_ByteArray = [Security.Cryptography.ProtectedData]::Unprotect($r_EncryptedBytes, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
$r_PlainText = [Text.Encoding]::UTF8.GetString($r_ByteArray)
$r_PlainText
内部で使う
## 説明 ##
暗号化に DPAPI が使われます。詳細は @Kit-i の方の記事が参考になると思います。
<参考にしたサイト>
【PowerShell】平文を暗号化・暗号文を復号する
🔰Windows PowerShellで文字列の暗号化と復号化
パターン A
## 説明 ##
PowerShell におけるオーソドックスな暗号化方法です。
"PASSWORD" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Tee-Object -Variable EncryptedText
$SecureString = ConvertTo-SecureString $EncryptedText
$BSTR = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
$PlainText = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
$PlainText
パターン B
## 説明 ##
PSSerializer は PSObject の状態をできるだけ維持した状態で XML として出力するクラスです。System.Security.SecureString は「パターン A」と同様の暗号化された文字列として表されます。
$SecureString = "PASSWORD" | ConvertTo-SecureString -AsPlainText -Force
[Management.Automation.PSSerializer]::Serialize($SecureString) | Tee-Object -Variable EncryptedXml
$SecureString = [Management.Automation.PSSerializer]::Deserialize($EncryptedXml)
$BSTR = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
$PlainText = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
$PlainText
間接的に使う
## 説明 ##
ここでは明示的に TPM を使用して暗号化してみたいと思います。Microsoft Platform Crypto Provider を指定すると、TPM チップ内の秘密鍵を用いて、TPM 内部で暗号化と復号が行われます。ただ詳しいことはよくわからないので How to use TPM 2.0 to secure private keys や Architecture などを参考にしてみてください。
<参考にしたサイト>
TPM Key attestation support
CNG/KSP without role separation (example)
Microsoft Platform Crypto Provider
## 説明 ##
CngKeyCreationOptions.MachineKey を指定しなかった場合、User Store に TPM-backed 鍵の参照情報が保存され、それは現在のユーザーのプロファイルに紐付いて保護されます。その保護の手段が DPAPI になります。
$KeyParams.KeyCreationOptions += [CngKeyCreationOptions]::MachineKey
[CngKey]::Open($KeyName, $Provider, "MachineKey")
またモジュラス長が 2048 までしか選択できない場合があり、その場合の平文の byte size は 190 まで(OAEP SHA256 のとき)に制限されます(What is the maximum size of the plaintext message for RSA OAEP?)。
<参考にしたサイト>
RSACng Class
CngKey.Open Method
CngKeyUsages Enum
CngExportPolicies Enum
CngKeyCreationOptions Enum
CngKeyCreationParameters.KeyCreationOptions Property
By default, if CngKeyCreationOptions.MachineKey is not specified, the key is created in the user store.
("CngExportPolicies","CngKey","CngKeyCreationOptions","CngKeyCreationParameters","CngKeyUsages","CngProperty","CngPropertyOptions","RSACng","RSAEncryptionPadding").ForEach{
[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Add($_, "System.Security.Cryptography.$_")
}
$KeyName = "New TPM Key"
$Provider = "Microsoft Platform Crypto Provider"
if (-not [CngKey]::Exists($KeyName, $Provider)) {
$KeySize = [CngProperty]::new("Length", [BitConverter]::GetBytes(2048), [CngPropertyOptions]::None)
$KeyParams = [CngKeyCreationParameters]::new()
$KeyParams.Parameters.Add($KeySize)
$KeyParams.Provider = $Provider
$KeyParams.KeyCreationOptions = [CngKeyCreationOptions]::OverwriteExistingKey
$KeyParams.ExportPolicy = [CngExportPolicies]::None
$KeyParams.KeyUsage = [CngKeyUsages]::AllUsages
[CngKey]::Create("RSA", $KeyName, $KeyParams)
}
$Cipher = [RSACng]::new([CngKey]::Open($KeyName, $Provider))
※ RSA における秘密鍵側の操作は Decryption(復号) と Signing(署名) なので、[CngKeyUsages]::AllUsages を指定しても KeyUsage : Decryption, Signing が設定されます。
$PlainText = "😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊♠190"
[Text.Encoding]::UTF8.GetByteCount($PlainText)
$ByteArray = [Text.Encoding]::UTF8.GetBytes($PlainText)
$EncryptedBytes = $Cipher.Encrypt($ByteArray, [RSAEncryptionPadding]::OaepSHA256)
[Convert]::ToBase64String($EncryptedBytes) | Tee-Object -Variable Base64
$r_EncryptedBytes = [Convert]::FromBase64String($Base64)
$r_ByteArray = $Cipher.Decrypt($r_EncryptedBytes, [RSAEncryptionPadding]::OaepSHA256)
$r_PlainText = [Text.Encoding]::UTF8.GetString($r_ByteArray)
$Cipher.Dispose()
$r_PlainText
TPM が使われているか判別してみる
<参考にしたサイト>
NCryptOpenStorageProvider function
NCRYPT_IMPL_TYPE_PROPERTY
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class NCryptApi {
[DllImport("ncrypt.dll", CharSet=CharSet.Unicode)]
public static extern int NCryptOpenStorageProvider(
out IntPtr phProvider, string pszProviderName, int dwFlags
);
[DllImport("ncrypt.dll", CharSet=CharSet.Unicode)]
public static extern int NCryptGetProperty(
IntPtr hObject, string pszProperty, byte[] pbOutput, int cbOutput, out int pcbResult, int dwFlags
);
[DllImport("ncrypt.dll")]
public static extern int NCryptFreeObject(IntPtr hObject);
public const int ERROR_SUCCESS = 0;
public const int NCRYPT_IMPL_HARDWARE_FLAG = 0x00000001;
}
"@
function Test-TPM {
param(
[string]$Provider = "Microsoft Platform Crypto Provider"
)
$PhProvider = [IntPtr]::Zero
$hr1 = [NCryptApi]::NCryptOpenStorageProvider([ref]$PhProvider, $Provider, 0)
if ($hr1 -ne [NCryptApi]::ERROR_SUCCESS) { throw "NCryptOpenStorageProvider failed: 0x{0:X}" -f $hr1 }
try {
$Size = 0
$hr2 = [NCryptApi]::NCryptGetProperty($PhProvider, "Impl Type", $null, 0, [ref]$Size, 0)
if ($hr2 -ne [NCryptApi]::ERROR_SUCCESS) { throw "NCryptGetProperty(Impl Type) size query failed: 0x{0:X}" -f $hr2 }
$Buffer = New-Object byte[] $Size
$OutSize = 0
$hr3 = [NCryptApi]::NCryptGetProperty($PhProvider, "Impl Type", $Buffer, $Buffer.Length, [ref]$OutSize, 0)
if ($hr3 -ne [NCryptApi]::ERROR_SUCCESS) { throw "NCryptGetProperty(Impl Type) failed: 0x{0:X}" -f $hr3 }
} finally {
if ($PhProvider -ne [IntPtr]::Zero) { [void][NCryptApi]::NCryptFreeObject($PhProvider) }
}
$Flags = [BitConverter]::ToInt32($Buffer, 0)
$Result = ($Flags -band [NCryptApi]::NCRYPT_IMPL_HARDWARE_FLAG) -ne 0
return "IsHardwareDevice : $Result"
}
Test-TPM "Microsoft Platform Crypto Provider"
他のユーザーから復号を試みる
# "New TPM Key" が存在しない場合
PS $Env:UserProfile> [Security.Cryptography.CngKey]::Open("New TPM Key", "Microsoft Platform Crypto Provider")
"2" 個の引数を指定して "Open" を呼び出し中に例外が発生しました: "キーセットがありません。"
発生場所 行:1 文字:1
+ [Security.Cryptography.CngKey]::Open("New TPM Key", "Microsoft Platf" ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : CryptographicException
PS $Env:UserProfile> $Base64 = "~~~~"
PS $Env:UserProfile> [Text.Encoding]::UTF8.GetString([Security.Cryptography.RSACng]::new([Security.Cryptography.CngKey]::Open("New TPM Key", "Microsoft Platform Crypto Provider")).Decrypt([Convert]::FromBase64String($Base64), [Security.Cryptography.RSAEncryptionPadding]::OaepSHA256))
"2" 個の引数を指定して "Decrypt" を呼び出し中に例外が発生しました: "TPM 2.0: 値が範囲外であるか、コンテキストに対して正しくありません。"
発生場所 行:1 文字:1
+ [Text.Encoding]::UTF8.GetString([Security.Cryptography.RSACng]::new([ ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : CryptographicException