Skip to main content

DPAPI NG decryption for Python

Project description

dpapi_ng - Python DPAPI-NG Decryption Library

Test workflow codecov PyPI version License

Library for DPAPI NG, also known as CNG DPAPI, decryption in Python. It is designed to replicate the behaviour of NCryptUnprotectSecret. This can be used on non-Windows hosts to decrypt DPAPI NG protected secrets, like PFX user protected password, or LAPS encrypted password. It can either decrypt any DPAPI NG blobs using an offline copy of the domain's root key or by using the credentials of the supplied user to retrieve the required information over RPC.

Currently only these protection descriptors are supported:

Type Purpose
SID Only the SID user or members of the SID group can decrypt the secret

This implements the MS-GKDI Group Key Distribution Protocol.

Requirements

How to Install

To install dpapi-ng with all the basic features, run

python -m pip install dpapi-ng

Kerberos Authentication

Kerberos authentication support won't be installed by default as it relies on system libraries and a valid compiler to be present. The krb5 library and compiler can be installed by installing these packages:

# 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

Once installed, the Kerberos Python extras can be installed with

python -m pip install dpapi-ng[kerberos]

Kerberos also needs to be configured to talk to the domain but that is outside the scope of this page.

From Source

git clone https://github.com/jborean93/dpapi-ng.git
cd dpapi-ng
pip install -e .

Examples

There is both a sync and asyncio API available to decrypt the blob.

import dpapi_ng

blob_bytes = b"..."
dpapi_ng.ncrypt_unprotect_secret(blob_bytes)

# The async equivalent to the above
await dpapi_ng.async_ncrypt_unprotect_secret(blob_bytes)

These functions return the decrypt bytes of the DPAPI-NG blob. To decrypt the blob, the key specified in the blob needs to be retrieved from the domain controller the blob was generated by. The domain controller hostname is retrieved through an SRV lookup of _ldap._tcp.dc._msdcs.{domain_name} or with the value specified in the server kwarg. It will attempt to authenticate with the current user identifier which on Linux will only exist if kinit has already been called to retrieve a user's ticket. Otherwise if no identity is available, the username and password kwargs can be used to specify a custom user.

The following kwargs can be used for both ncrypt_unprotect_secret and async_ncrypt_unprotect_secret.

  • server: Use this server as the RPC target if a key needs to be retrieved
  • username: The username to authenticate as for the RPC connection
  • password: The password to authenticate with for the RPC connection
  • auth_protocol: The authentication protocol (negotiate, kerberos, ntlm) to use for the RPC connection
  • cache: A cache to store keys retrieved for future operation

It is also possible to decrypt the DPAPI-NG blob by providing the root key stored in the domain. This can either be retrieved using an offline attack or through an LDAP query if running as a Domain Admin user. To retrieve the domain root keys using PowerShell the following can be run:

$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')
    }
}

The following ldapsearch command can be used outside of Windows:

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

Note: ldapsearch will most likely need the -H and user bind information to succeed.

The information retrieved there can be stored in a cache and used for subsequent ncrypt_unprotect_secret calls:

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)

Currently the SP800_108_CTR_HMAC KDF algorithm and DH, ECDH_P256, and ECDH_P384 secret agreement algorithms have been tested to work. The ECDH_P521 secret agreement algorithm should also work but has been untested as a test environment cannot be created with it right now.

Special Thanks

I would like to thank the following people (Twitter handles in brackets) for their help on this project:

  • Grzegorz Tworek (@0gtweet) and Michał Grzegorzewski for providing more information on the internal BCrypt* API workflow used in DPAPI-NG
  • Marc-André Moreau (@awakecoding) for their help with reverse engineering some of the Windows APIs and talking through some theories
  • SkelSec (@SkelSec) for help on the RPC calls and being available as a general sounding board for my theories
  • Steve Syfuhs (@SteveSyfuhs) for connecting me with some Microsoft engineers to help understand some undocumented logic

Without their patience and knowledge this probably would not have been possible.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dpapi-ng-0.1.0.tar.gz (54.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dpapi_ng-0.1.0-py3-none-any.whl (49.2 kB view details)

Uploaded Python 3

File details

Details for the file dpapi-ng-0.1.0.tar.gz.

File metadata

  • Download URL: dpapi-ng-0.1.0.tar.gz
  • Upload date:
  • Size: 54.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.1 CPython/3.11.3

File hashes

Hashes for dpapi-ng-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3cfaf84cc9136d4c0a08792207bcf92611eda3b2729a6dad3086d48aab65c3b7
MD5 d32796553104e8bd870344627b311da5
BLAKE2b-256 99efccd50d2266709f5261ee1497de384c7630ec3a845de48cd2751b9cd47492

See more details on using hashes here.

File details

Details for the file dpapi_ng-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: dpapi_ng-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 49.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.1 CPython/3.11.3

File hashes

Hashes for dpapi_ng-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1bfe7d2390a545d4e8d8e96d5d908608a663c0ee21bc338d2a4ea3f988a10cd3
MD5 742d648d277b0a9c502251a11b7f71d0
BLAKE2b-256 3a1edd7b480ecf9371b0d535aba5a79e9d2feefa4f3b452b51c40e0f1e189df6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page