
Python DPAPI NG Decryptor for non-Windows Platforms
DPAPI NG(CNG DPAPI라고도 함)의 복호화 및 암호화를 위한 Python 라이브러리입니다. NCryptUnprotectSecret 및 NCryptProtectSecret의 동작을 재현하도록 설계되었습니다. Windows가 아닌 호스트에서 PFX 사용자 보호 암호 또는 LAPS 암호화 암호와 같은 DPAPI NG로 보호된 비밀값을 복호화/암호화하는 데 사용할 수 있습니다. 도메인의 루트 키 오프라인 사본을 사용하여 모든 DPAPI NG blob을 복호화하거나, 제공된 사용자의 자격 증명을 사용하여 RPC를 통해 필요한 정보를 검색하여 복호화/암호화할 수 있습니다.
현재 다음 보호 설명자만 지원됩니다:
| 유형 | 용도 |
|---|---|
| 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 .
blob을 복호화/암호화하기 위한 동기(sync) 및 비동기(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)
blob을 복호화하려면 blob에 지정된 키를 해당 blob을 생성한 도메인 컨트롤러에서 검색해야 합니다.
blob을 암호화하려면 지정된 대상 SID의 그룹 키를 검색해야 합니다.
복호화의 경우 DPAPI-NG blob에서 찾은 도메인 이름 또는 server kwarg에 지정된 값을 사용하여 _ldap._tcp.dc._msdcs.{domain_name}의 DNS SRV 조회를 통해 도메인 컨트롤러 호스트 이름이 자동으로 검색됩니다.
암호화의 경우 도메인 컨트롤러 호스트 이름은 DNS SRV 조회를 위한 domain_name kwarg, server kwarg를 직접 사용하거나 시스템의 검색 도메인(사용 가능한 경우)을 사용하여 결정됩니다.
현재 사용자 식별자로 인증을 시도하며, Linux에서는 사용자의 티켓을 검색하기 위해 kinit이 이미 호출된 경우에만 해당 식별자가 존재합니다.
그렇지 않아 사용 가능한 ID가 없는 경우 username 및 password kwarg를 사용하여 사용자 지정 사용자를 지정할 수 있습니다.
ncrypt_unprotect_secret, async_ncrypt_unprotect_secret, ncrypt_protect_secret 및 async_ncrypt_protect_secret에는 다음 kwarg를 사용할 수 있습니다.
server: 키를 검색해야 하는 경우 이 서버를 RPC 대상으로 사용username: RPC 연결에 사용할 인증 사용자 이름password: RPC 연결에 사용할 인증 암호auth_protocol: RPC 연결에 사용할 인증 프로토콜(negotiate, kerberos, ntlm)cache: 향후 작업을 위해 검색된 키를 저장하는 캐시추가로 ncrypt_protect_secret 및 async_ncrypt_protect_secret은 다음 추가 kwarg를 사용합니다.
domain_name: 키 정보를 검색할 때 사용할 RPC 대상을 조회할 때 사용할 도메인/포리스트 이름root_key_identifier: 이 함수들에서 캐시가 작동하는 데 필요한 루트 키 식별자 UUID도메인에 저장된 루트 키를 제공하여 DPAPI-NG blob을 암호화하고 복호화하는 것도 가능합니다. 이 키는 오프라인 공격을 사용하거나 도메인 관리자(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 비밀 합의(secret agreement) 알고리즘이 작동하는 것으로 테스트되었습니다.
ECDH_P521 비밀 합의 알고리즘도 작동해야 하지만 현재 이를 사용하여 테스트 환경을 만들 수 없어 테스트되지 않았습니다.
이 프로젝트에 도움을 주신 다음 분들(괄호 안은 GitHub 또는 Twitter 핸들)께 감사드립니다:
그들의 인내와 지식이 없었다면 이 프로젝트는 아마 가능하지 않았을 것입니다.