1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DPAPI を使って暗号文の作成・復号を行ってみる

Last updated at Posted at 2025-11-10

直接使う

## 説明 ##
 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 keysArchitecture などを参考にしてみてください。

<参考にしたサイト>
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.

CngPropertyOptions Enum

事前準備
("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"

他のユーザーから復号を試みる

PowerShell
# "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
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?