
Python DPAPI NG Decryptor for non-Windows Platforms
DPAPI NG(CNG DPAPI としても知られる)の暗号化・復号化を Python で行うためのライブラリです。 NCryptUnprotectSecret および NCryptProtectSecret の動作を再現することを目的としています。 これを使用すると、Windows 以外のホストでも、PFX ユーザー保護パスワードや LAPS 暗号化パスワードなどの DPAPI NG で保護されたシークレットを暗号化・復号化できます。 ドメインのルートキーのオフラインコピーを使用して任意の DPAPI NG ブロブを復号化するか、指定されたユーザーの資格情報を使用して RPC 経由で必要な情報を取得することで暗号化・復号化が可能です。
現在サポートされている保護記述子は次のものだけです:
| Type | 目的 |
|---|---|
| SID | SID ユーザーまたは SID グループのメンバーのみがシークレットを復号化できます |
これは MS-GKDI Group Key Distribution Protocol を実装しています。
すべての基本機能を含む dpapi-ng をインストールするには、次を実行します
python -m pip install dpapi-ng
Kerberos 認証サポートはシステムライブラリと有効なコンパイラに依存するため、デフォルトではインストールされません。 krb5 ライブラリとコンパイラは以下のパッケージをインストールすることで導入できます:
# Debian/Ubuntu
apt-get install gcc python3-dev libkrb5-dev
# Centos/RHEL
yum install gcc python-devel krb5-devel
# Fedora
dnf install gcc python-devel krb5-devel
# Arch Linux
pacman -S gcc krb5
インストール後、Kerberos 用の Python 拡張機能は次のようにインストールできます
python -m pip install dpapi-ng[kerberos]
また、Kerberos がドメインと通信できるように設定する必要がありますが、これについてはこのページの範囲外です。
git clone https://github.com/jborean93/dpapi-ng.git
cd dpapi-ng
pip install -e .
ブロブを暗号化・復号化するための同期 API と asyncio API の両方が利用可能です。
import dpapi_ng
### DECRYPTION ###
dpapi_ng_blob = b"..."
decrypted_blob = dpapi_ng.ncrypt_unprotect_secret(dpapi_ng_blob)
# async equivalent to the above
decrypted_blob = await dpapi_ng.async_ncrypt_unprotect_secret(dpapi_ng_blob)
### ENCRYPTION ###
data = b"..."
target_sid = "S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXX"
dpapi_ng_blob = dpapi_ng.ncrypt_protect_secret(data, target_sid)
# async equivalent to the above
dpapi_ng_blob = await dpapi_ng.async_ncrypt_protect_secret(data, target_sid)
ブロブを復号化するには、ブロブで指定されたキーを、そのブロブを生成したドメインコントローラーから取得する必要があります。
ブロブを暗号化するには、指定されたターゲット SID のグループキーを取得する必要があります。
復号化の場合、ドメインコントローラーのホスト名は、DPAPI-NG ブロブ内にあるドメイン名を使用した DNS SRV ルックアップ _ldap._tcp.dc._msdcs.{domain_name} によって自動的に取得されるか、server キーワード引数で指定された値が使用されます。
暗号化の場合、ドメインコントローラーのホスト名は、domain_name キーワード引数による DNS SRV ルックアップ、server キーワード引数による直接指定、またはシステムの検索ドメイン(利用可能な場合)のいずれかで決定されます。
現在のユーザー識別子での認証が試みられます。Linux では、ユーザーのチケットを取得するために kinit が既に呼び出されている場合にのみこの識別子が存在します。
それ以外で識別情報が利用できない場合は、username および password キーワード引数を使用してカスタムユーザーを指定できます。
ncrypt_unprotect_secret、async_ncrypt_unprotect_secret、ncrypt_protect_secret、async_ncrypt_protect_secret では、以下のキーワード引数を使用できます。
server: キーを取得する必要がある場合に、このサーバーを RPC ターゲットとして使用しますusername: RPC 接続で認証するユーザー名password: RPC 接続で認証に使用するパスワードauth_protocol: RPC 接続に使用する認証プロトコル(negotiate、kerberos、ntlm)cache: 将来の操作のために取得したキーを格納するキャッシュさらに、ncrypt_protect_secret と async_ncrypt_protect_secret は次の追加のキーワード引数を受け取ります。
domain_name: キー情報を取得する際に使用する RPC ターゲットを検索するときに使用するドメイン/フォレストの名前root_key_identifier: ルートキー識別子の UUID。これらの関数でキャッシュを機能させるために必要ですまた、ドメインに保存されているルートキーを提供することで、DPAPI-NG ブロブを暗号化・復号化することも可能です。 これは、オフライン攻撃によって取得するか、Domain Admin ユーザーとして実行している場合は LDAP クエリによって取得できます。 PowerShell を使用してドメインのルートキーを取得するには、次を実行します:
$configurationContext = (Get-ADRootDSE).configurationNamingContext
$getParams = @{
LDAPFilter = '(objectClass=msKds-ProvRootKey)'
SearchBase = "CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,$configurationContext"
SearchScope = 'OneLevel'
Properties = @(
'cn'
'msKds-KDFAlgorithmID'
'msKds-KDFParam'
'msKds-SecretAgreementAlgorithmID'
'msKds-SecretAgreementParam'
'msKds-PrivateKeyLength'
'msKds-PublicKeyLength'
'msKds-RootKeyData'
)
}
Get-ADObject @getParams | ForEach-Object {
[PSCustomObject]@{
Version = 1
RootKeyId = [Guid]::new($_.cn)
KdfAlgorithm = $_.'msKds-KDFAlgorithmID'
KdfParameters = [System.Convert]::ToBase64String($_.'msKds-KDFParam')
SecretAgreementAlgorithm = $_.'msKds-SecretAgreementAlgorithmID'
SecretAgreementParameters = [System.Convert]::ToBase64String($_.'msKds-SecretAgreementParam')
PrivateKeyLength = $_.'msKds-PrivateKeyLength'
PublicKeyLength = $_.'msKds-PublicKeyLength'
RootKeyData = [System.Convert]::ToBase64String($_.'msKds-RootKeyData')
}
}
Windows 以外では、次の ldapsearch コマンドを使用できます:
ldapsearch \
-b 'CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,DC=domain,DC=test' \
-s one \
'(objectClass=msKds-ProvRootKey)' \
cn \
msKds-KDFAlgorithmID \
msKds-KDFParam \
msKds-SecretAgreementAlgorithmID \
msKds-SecretAgreementParam \
msKds-PrivateKeyLength \
msKds-PublicKeyLength \
msKds-RootKeyData
注: ldapsearch が成功するには、ほとんどの場合 -H オプションとユーザーバインド情報が必要です。
そこで取得した情報はキャッシュに保存し、後続の ncrypt_protect_secret および ncrypt_unprotect_secret 呼び出しで使用できます:
import uuid
import dpapi_ng
cache = dpapi_ng.KeyCache()
root_key_id = uuid.UUID("76ec8b2d-d444-4f67-9db7-2f62b4358b35")
cache.load_key(
b"...", # msKds-RootKeydata
root_key_id, # cn
version=1,
kdf_algorithm="SP800_108_CTR_HMAC", # msKds-KDFAlgorithmID
kdf_parameters=b"...", # msKds-KDFParam
secret_algorithm="DH", # mskds-SecretAgreementAlgorithmID
secret_parameters=b"...", # msKds-SecretAgreementParam
private_key_length=512, # msKds-PrivateKeyLength
public_key_length=2048, # msKds-PublicKeyLength
)
dpapi_ng.ncrypt_unprotect_secret(b"...", cache=cache)
現在、SP800_108_CTR_HMAC KDF アルゴリズムと、DH、ECDH_P256、ECDH_P384 シークレットアグリーメントアルゴリズムがテスト済みで動作します。
ECDH_P521 シークレットアグリーメントアルゴリズムも動作するはずですが、現在はテスト環境を作成できないため未テストです。
このプロジェクトに協力してくれた以下の人々(括弧内は GitHub または Twitter のハンドル)に感謝します:
彼らの忍耐と知識がなければ、これはおそらく実現しなかったでしょう。