# DPAPI and DPAPI-NG: The Credential Vault Under Everything

> A 25-year tour of Windows Data Protection API: the four-stage classic chain, the 2012 DPAPI-NG redesign, the KDS root key, and the five structural ceilings the design cannot close.

*Published: 2026-05-11*
*Canonical: https://paragmali.com/blog/dpapi-and-dpapi-ng-the-credential-vault-under-everything*
*License: CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/*

---
<TLDR>
**Classic DPAPI ([Windows 2000, February 17, 2000](https://en.wikipedia.org/wiki/Windows_2000)) wraps every per-user Windows secret -- browser cookies, WiFi keys, EFS file keys, Outlook passwords, Credential Manager entries -- in a four-stage chain rooted at the user's password.** [DPAPI-NG (Windows 8 / Server 2012)](https://en.wikipedia.org/wiki/Windows_Server_2012) replaces the (user, machine) decryption boundary with a [protection descriptor](https://learn.microsoft.com/en-us/windows/win32/seccng/protection-descriptors) and rides the [Microsoft Key Distribution Service](https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview) to give every domain controller in the forest the same per-(group, period) key without inter-DC sync. Group Managed Service Accounts, [delegated Managed Service Accounts](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-overview), Windows Hello for Business on TPM-less devices, and Credential Guard's isolated-secret persistence all sit on top. The 2022-2025 [Golden gMSA](https://www.semperis.com/blog/golden-gmsa-attack/), [Golden dMSA](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/), and [Chromium app-bound encryption](https://thehackernews.com/2024/08/google-chrome-adds-app-bound-encryption.html) disclosures all admit the same thing: DPAPI's structural ceilings (user-context binding, single-password derivation, KDS-root-key irrotability, hibernation, domain-backup-key concentration) are the design, not bugs.
</TLDR>

## 1. A Thumb Drive, a Profile Directory, and Three Mimikatz Commands

A DFIR analyst slides a stolen laptop's drive into a write-blocker. The volume mounts because BitLocker's recovery key was already in the corporate KMS. Six files in `C:\Users\alice\AppData\Roaming\Microsoft\Protect\S-1-5-21-...` -- five GUIDs and a `Preferred` pointer -- are the only thing standing between the analyst and a decade of Alice's saved Windows secrets.

Three [Mimikatz](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi) commands later (`dpapi::masterkey /in:&lt;GUID&gt; /sid:S-1-5-21-... /password:<known>`, then `dpapi::cred /in:<credfile> /masterkey:<unwrapped>`, then `dpapi::chrome /in:"Login Data"`) the analyst has Alice's saved RDP password to the production jump host, her Microsoft 365 session cookie, the home WiFi PSK, the KeePass "Windows User Account" master password, and the EFS keys for her protected documents (each item is itemised with primary citations in §5's eleven-row credential-vault inventory). No kernel exploit. No live login. Just one (account-password, SID) pair recovered offline from last week's NTDS.dit backup.

The trick that makes that scene possible is older than most working engineers. It shipped in [Windows 2000 GA on February 17, 2000](https://en.wikipedia.org/wiki/Windows_2000), it has been the same shape for 25 years, and its single-secret design assumption has been [public and tractable since February 3, 2010](https://elie.net/talk/reversing-dpapi-and-stealing-windows-secrets-offline). The trick has a name: the **Data Protection API**, or DPAPI.

This article walks the API end-to-end at the level of detail an academic survey would, but with the working-engineer's framing the topic deserves. We open the four-stage classic-DPAPI chain at the SHA-1-of-NT-hash-into-PBKDF2-with-the-SID-as-UTF-16LE-salt level. We open the [2012 DPAPI-NG redesign](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi) and the [Microsoft Key Distribution Service's L0/L1/L2 derivation chain](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) at the same level of precision.

We name the four production consumers that ride the new chain in 2026: gMSAs, dMSAs, [Windows Hello for Business](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works) software-KSP credentials, and [Credential Guard](https://paragmali.com/blog/when-system-isnt-enough-the-windows-secure-kernel-and-the-en/)'s isolated-secret persistence. We name the five structural ceilings the [2022 Golden gMSA](https://www.semperis.com/blog/golden-gmsa-attack/), [2024 Chromium app-bound encryption](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html), and [2025 Golden dMSA](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/) disclosures all admit out loud. And we close with what a 2026 practitioner -- developer, defender, red-teamer, platform engineer -- actually does with all of it.

A note on adjacent topics: the companion *Credential Guard* article in this series covers the LSAISO trustlet's isolation boundary; the companion [*VBS Trustlets*](https://paragmali.com/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/) article covers the trustlet model itself; the [*TPM in Windows*](https://paragmali.com/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/) article covers TPM-bound key storage providers; the [*BitLocker on Windows*](https://paragmali.com/blog/bitlocker-on-windows-architecture-attacks-and-the-limits-of-/) article covers full-volume encryption; the [*NTLMless*](https://paragmali.com/blog/ntlmless-the-death-of-ntlm-in-windows/) article covers Kerberoasting and Golden Ticket disambiguation. Where we touch those topics, we touch them briefly and refer out -- the goal here is to make DPAPI's chain legible from `CryptProtectData` all the way to the four-phase `GoldenDMSA` pipeline.

If you can read those six files in Alice's `Protect` directory and you have her password's SHA-1 hash, you have everything Windows ever encrypted for her. The next eleven sections explain why -- and why the 2012 redesign that was supposed to fix it produced a new ceiling that, by 2022, turned out to be even harder to live with.

## 2. Why Windows 2000 Needed a Credential Vault: Generation 0 and Generation 1

Three years before DPAPI shipped, an attacker with a logged-in user's session could read every Internet Explorer auto-complete password, every Outlook Express account password, every saved-FTP credential, and every dial-up RAS phonebook entry on the machine -- without breaking any cryptography. The late-1990s reversing tradition (the original `pwdump`, *L0phtCrack*, Cain & Abel's "Protected Storage PassView," the ad-hoc Outlook Express and IE 4 form-fill stealers documented across Bugtraq and ntbugtraq mailing lists at the time) defeated all of it uniformly. The "encryption" applications used was honoured by the OS, faithfully, for the attacker's process as for the legitimate one -- because there was no system primitive that distinguished one from the other. Each application baked its own key into the binary; every reverser who pulled the binary apart pulled the key out with it.

<Definition term="Data Protection API (DPAPI)">
The system-provided per-user and per-machine secret-storage primitive that ships in every Windows release from [Windows 2000 onward](https://en.wikipedia.org/wiki/Data_Protection_API). The classic API surface is two flat-C functions -- [`CryptProtectData`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata) and `CryptUnprotectData` -- that take a plaintext, an optional caller entropy parameter, and return a self-contained opaque BLOB. The cryptographic chain inside those two functions is rooted at the user's login password and is what every "encrypted" Windows secret of the next 25 years sits on top of.
</Definition>

If the attacker is the user's session, what does "encrypt this for the user" even mean? That is the question every Generation 0 design dodged and every modern credential-vault design has to answer head-on. The 1990s answer (XOR-with-a-baked-in-key) and Microsoft's first attempt at a real system primitive (Protected Storage / PStore) both missed the same point in different ways.

### Generation 1: Protected Storage

Protected Storage shipped with Internet Explorer 4 and stayed in the OS until Microsoft formally deprecated it. The [`pstore.dll`](https://learn.microsoft.com/en-us/windows/win32/devnotes/pstore) item taxonomy is a four-tuple `(Key, Type, Subtype, Name)` -- a folder hierarchy of named secret entries. The API was the first system-level secret store on Windows that any application could use without writing its own key derivation; the conceptual contribution survived even after the implementation was abandoned.

PStore had two ideas the post-Vista world kept and one it dropped. The two it kept: secrets live in a *system primitive*, not in each application; secrets are addressed by *name*, not by raw key handle. The one it dropped: an *Authenticode access-rule* clause that would have bound a stored item to the signing identity of the application that created it. No application ever used the access-rule clause in production. Microsoft's developer notes are blunt about how the story ended: *"Pstore uses an older implementation of data protection. Developers are strongly encouraged to take advantage of the stronger data protection provided by the [`CryptProtectData`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata) and `CryptUnprotectData` functions"*; PStore is *"only available for read-only operations in Windows Server 2008 and Windows Vista, but may be unavailable in subsequent versions."*

<MarginNote>The PStore code-identity-pinning idea (Authenticode access rules) was abandoned in 2000 and re-invented twenty-six years later by Chromium 2024 app-bound encryption, which we will reach in §10.3.</MarginNote>

### What survived into DPAPI

The Generation 2 design that shipped with Windows 2000 in February 2000 made four moves at once. It kept the two PStore ideas worth keeping ("system-level, not per-application" and "secret addressed by structured name"). It dropped the unused Authenticode access-rule clause. It pushed the cryptographic chain down to a key derived directly from the user's login password. And it added a domain-recovery sidecar (the [BackupKey Remote Protocol](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/), which we open in §5) so managed enterprises would adopt it.

The [canonical first public design document](https://learn.microsoft.com/en-us/previous-versions/ms995355(v=msdn.10)) -- NAI Labs / Network Associates' *"Windows Data Protection"* whitepaper, MSDN ms995355, October 2001 -- is unambiguous about the layering: *"DPAPI initially generates a strong key called a MasterKey, which is protected by the user's password. DPAPI uses a standard cryptographic process called Password-Based Key Derivation, described in PKCS #5, to generate a key from the password."* And: *"DPAPI is a password-based data protection service. It requires a password to provide protection."*

<Sidenote>Some secondary sources attribute a "Microsoft Windows Data Protection API" whitepaper to Niels Ferguson at `niels.ferguson.com/research/dpapi.html`. That URL has been TCP-unreachable for years, has zero Wayback captures across four candidate variants, and the [Wikipedia *Niels Ferguson* article](https://en.wikipedia.org/wiki/Niels_Ferguson) lists no DPAPI publication for him -- his named Microsoft paper is the 2006 BitLocker / Elephant-diffuser paper, not anything DPAPI-related. The verifiable Microsoft-blessed first design document is the [NAI Labs ms995355 whitepaper](https://learn.microsoft.com/en-us/previous-versions/ms995355(v=msdn.10)).</Sidenote>

The two-function flat-C API Microsoft shipped with Windows 2000 in February 2000 is what every Windows secret of the next 25 years has been encrypted with. The four-stage chain it hides behind those two function names is what we open up next.

## 3. The Four-Stage Chain: How `CryptProtectData` Actually Encrypts

A [`CryptProtectData`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata) call goes in with a plaintext buffer and an optional entropy parameter; out comes a self-contained opaque BLOB whose header adds roughly 100-150 bytes to the plaintext (the exact size depends on the algorithm choice and the master-key GUID encoding; the field-by-field BLOB layout is documented in the [Bursztein-Picod 2010 paper](https://elie.net/static/files/reversing-dpapi-and-stealing-windows-secrets-offline/reversing-dpapi-and-stealing-windows-secrets-offline-paper.pdf) and parsed by the [Mimikatz `dpapi::blob`](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi) module). There is no `pszProvider` argument, no `hKey`, no algorithm choice exposed to the caller. Behind those two parameters is a four-stage cryptographic chain that has been the same shape for a quarter-century. Each stage takes the previous stage's output and one new input; the *only* secret in the entire chain that an offline attacker has to guess is the user's password.

<Mermaid caption="The four-stage classic-DPAPI chain: pre-key, master key, session key, BLOB wrap.">
flowchart TD
    Password["User password"] --> NTHash["NT hash<br/>(MD4 of UTF-16LE password)"]
    NTHash --> Sha1NT["SHA-1(NT hash)"]
    SID["User SID<br/>(UTF-16LE)"] --> PBKDF2
    Sha1NT --> PBKDF2["Stage 1: PBKDF2<br/>(HMAC-SHA1 / HMAC-SHA512)"]
    PBKDF2 --> PreKey["Pre-key"]
    MK["Stage 2: Master key<br/>(64 random bytes)"] -->|encrypted under| AESCBC["AES-CBC wrap"]
    PreKey --> AESCBC
    AESCBC --> MKFile["%APPDATA%/Microsoft/Protect/&lt;SID&gt;/&lt;GUID&gt;"]
    MK --> SessionKey["Stage 3: Session key<br/>HMAC(MK, salt || entropy)"]
    SessionKey --> Wrap["Stage 4: BLOB wrap<br/>(3DES or AES-256, salt, HMAC)"]
    Plaintext["Plaintext"] --> Wrap
    Wrap --> Blob["DPAPI BLOB"]
</Mermaid>

### Stage 1: Pre-key derivation

The pre-key is a function of three values. The user-account password (or its NT-hash equivalent, supplied by `LSASS` to the local DPAPI provider) is hashed with SHA-1; the SHA-1 result is fed into PBKDF2 as the input keying material; the user's [security identifier (SID)](https://learn.microsoft.com/en-us/previous-versions/ms995355(v=msdn.10)) UTF-16LE-encoded is the salt; and a Windows-version-dependent iteration count completes the call. The output is a fixed-width pre-key that Stage 2 will use to wrap the master key.

The chain has changed *parameters* across Windows versions but has kept the four-stage *shape* since 2000. The [Passcape master-key analysis table](https://www.passcape.com/windows_password_recovery_dpapi_master_key) records the migration verbatim:

| Windows version | Symmetric cipher | HMAC | PBKDF2 iterations |
|---|---|---|---|
| Windows 2000 | RC4 | SHA-1 | 1 |
| Windows XP | 3DES | SHA-1 | 4 000 |
| Windows Vista | 3DES | SHA-1 | 24 000 |
| Windows 7 | AES-256 | SHA-512 | 5 600 |
| Windows 10 / 11 | AES-256 | SHA-512 | 8 000 |

The shift from PBKDF2-HMAC-SHA1 / 3DES (Windows 2000 -- Vista) to PBKDF2-HMAC-SHA512 / AES-256 (Windows 7 onward) happened at Windows 7, not Windows 10; Bursztein and Picod's [2010 USENIX WOOT paper](https://www.usenix.org/conference/woot10/recovering-windows-secrets-and-efs-certificates-offline) documented the SHA-1 / 3DES / 4 000-iteration era because their study predated Windows 7's release.

> **Note:** 8 000 iterations of HMAC-SHA-512 is not strong against a modern wordlist attack on a leaked NT hash. Cumulative updates can raise the iteration count further; the actual count is recorded in the master-key file's header and is not implicit. When you read someone's master key, read the iteration count from the file -- do not assume.

### Stage 2: The master key

The *master key* is a 64-byte cryptographically random secret; one is generated per user, on first use of DPAPI, and a fresh one is generated every 90 days by default. Master keys live as files inside `%APPDATA%\Microsoft\Protect\&lt;SID&gt;\&lt;GUID&gt;`, where the GUID is the master-key identifier embedded in every BLOB that uses it. The file is divided into four slots: a header (with the iteration count and algorithm IDs), the user-master-key blob (encrypted under the Stage 1 pre-key with AES-CBC), the local-encryption-key or CREDHIST GUID, and the [domain-backup-key blob](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/) (encrypted to the DC's backup public key, see §5).

<Definition term="Master key (DPAPI)">
A 64-byte random secret per user, persisted as a file under `%APPDATA%\Microsoft\Protect\&lt;SID&gt;\&lt;GUID&gt;`, encrypted under a pre-key derived from the user's password and SID. Every DPAPI BLOB the user ever creates is wrapped under a session key derived from one of these master keys. Master keys rotate every 90 days by default per the [NAI Labs design document](https://learn.microsoft.com/en-us/previous-versions/ms995355(v=msdn.10)) and the [Passcape master-key analysis](https://www.passcape.com/windows_password_recovery_dpapi_master_key), but old master keys remain on disk so old BLOBs can still be decrypted.
</Definition>

### Stage 3: The per-call session key

For every call to `CryptProtectData`, DPAPI generates a fresh per-blob salt, computes an HMAC of the master key with the salt and the optional caller entropy, and uses that HMAC as the session key for the actual symmetric encryption. The session key is never stored; it is derivable from (master key, salt, entropy) at unwrap time per the [Bursztein-Picod 2010 paper](https://elie.net/static/files/reversing-dpapi-and-stealing-windows-secrets-offline/reversing-dpapi-and-stealing-windows-secrets-offline-paper.pdf) §3.3 and the [Mimikatz `dpapi::blob` parser](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi). The salt is in the BLOB header; the entropy, if any, must be supplied by the caller at unwrap time.

### Stage 4: The BLOB wrap

The output BLOB is a self-describing structure with a fixed header. The provider GUID `{df9d8cd0-1501-11d1-8c7a-00c04fc297eb}` identifies it as a classic-DPAPI blob; the master-key GUID names the master key under which it was wrapped; an `algCrypt` algorithm identifier records which symmetric cipher was used (`0x6603` for `CALG_3DES` on legacy builds, `0x6610` for `CALG_AES_256` on later builds); the salt, ciphertext, and HMAC fill the rest. The [Mimikatz `dpapi` module wiki](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi) documents the verbatim field layout that every offline DPAPI tool parses to this day.

<Definition term="algCrypt and the BLOB provider GUID">
The `algCrypt` field in the DPAPI BLOB header is a CryptoAPI algorithm identifier from `wincrypt.h`: `0x6603` is `CALG_3DES` (the historical default, encoded as `ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES`); `0x6610` is `CALG_AES_256` (used on Windows 7 and later, encoded as `ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256`). The provider GUID `{df9d8cd0-1501-11d1-8c7a-00c04fc297eb}` is the magic constant that marks every classic-DPAPI BLOB and is the same value the [Mimikatz `dpapi::blob` module](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi) and the [Bursztein-Picod 2010 paper](https://elie.net/static/files/reversing-dpapi-and-stealing-windows-secrets-offline/reversing-dpapi-and-stealing-windows-secrets-offline-paper.pdf) §3.3.1 print when they parse one.
</Definition>

<RunnableCode lang="python" title="Stage 1 PBKDF2 pre-key derivation (synthetic inputs)">{`

    Parse --> Cert&#123;"CERTIFICATE="&#125;
    Parse --> Local&#123;"LOCAL="&#125;
    Parse --> Web&#123;"WEBCREDENTIALS="&#125;
    Sid --> KSP["Microsoft Key Protection Provider<br/>+ kdssvc.dll [MS-GKDI]"]
    Cert --> CertKSP["Cert's KSP<br/>(TPM-backed possible)"]
    Local --> LocalProv["Microsoft Key Protection Provider<br/>(LOCAL=user / LOCAL=machine)"]
    Web --> Broker["Microsoft Client Key Protection Provider<br/>(credential broker)"]
    KSP --> Wrap
    CertKSP --> Wrap
    LocalProv --> Wrap
    Broker --> Wrap
    Wrap["Derive wrapping key,<br/>encrypt content"] --> Blob["Self-describing blob<br/>(descriptor + provider info<br/>+ key id + ciphertext + HMAC)"]
</Mermaid>

The output blob is self-describing per the [protected-data-format reference](https://learn.microsoft.com/en-us/windows/win32/seccng/protected-data-format): descriptor, provider info, key identifier, ciphertext, HMAC. Any device with a CNG implementation that can satisfy the descriptor decrypts. There is no out-of-band key shipping. There is no "encrypt the blob and ship the key separately." The descriptor *is* the key-distribution policy.

> **Key idea:** DPAPI-NG separates *who can decrypt* (the descriptor) from *where the key material lives* (the provider). The descriptor is the contract; the provider is the implementation. This is the structural innovation that lets a blob protected on one machine be decrypted on another without any application-layer key-management code.

<Sidenote>The CNG DPAPI overview page lists only *two* principal classes -- AD group and web credentials -- whereas the protection-descriptors page enumerates three (adding the certificate-store class). Both are correct: the certificate descriptor maps to a different provider, hence the two-principal framing on the higher-level overview page and the three-keyword framing on the descriptors-grammar page.</Sidenote>

The `LOCAL=user` and `CERTIFICATE=` cases are interesting but mostly variations on themes that classic DPAPI or the Windows certificate store could already do. The case that required Microsoft to ship a new DC-side daemon -- the case that turned DPAPI-NG into the substrate for [gMSAs](https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview), [dMSAs](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-overview), Hello for Business, and Credential Guard's persistence layer -- is the `SID=` AD-group descriptor. The next section opens its substrate: the Microsoft Key Distribution Service and the `[MS-GKDI]` protocol.

## 7. The Microsoft Key Distribution Service: How `[MS-GKDI]` Computes the Same Group Key on Every DC Without Talking to Any of Them

Imagine a forest with seven writable domain controllers. A laptop in Singapore protects a DPAPI-NG blob with `SID=S-1-5-21-...XYZ` (some AD group). Three months later, a phone in Seoul -- a member of the same group, on a different DC -- needs to decrypt it. Neither DC has ever heard of the blob. Both DCs derive *exactly the same group key* and hand it to the requesting member. No inter-DC synchronisation. No key-distribution code in either application. The mechanism is one forest-wide root key plus a deterministic key-derivation function.

<Definition term="Microsoft Key Distribution Service (kdssvc.dll)">
The DC-side daemon that ships in every writable domain controller from [Windows Server 2012 onward](https://en.wikipedia.org/wiki/Windows_Server_2012). Implements the [`[MS-GKDI]` Group Key Distribution Protocol](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) (current revision class 10.0, April 2024) and is the substrate for every DPAPI-NG `SID=` blob, every gMSA password, every dMSA password, and the TPM-less software-KSP path of Windows Hello for Business. The service has one job: given a (group, time-period) request from a member computer, derive and return the per-(group, period) key from a single forest-wide root key.
</Definition>

### Provisioning the root key

Every forest needs exactly one KDS root key before any DPAPI-NG `SID=` consumer can use it. The PowerShell cmdlet [`Add-KdsRootKey`](https://learn.microsoft.com/en-us/powershell/module/kds/add-kdsrootkey) is the provisioning entry point. The Microsoft Learn page is verbatim about the constraints: *"The Add-KdsRootKey cmdlet generates a new root key for the Microsoft Group Key Distribution Service (KdsSvc) within Active Directory... It is required to run this only once per forest."* The default `EffectiveTime` is *"10 days after the current date"* to allow Active Directory replication to converge across all writable DCs before any consumer tries to derive against the new key.

<Sidenote>The `Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))` override is for *single-DC test forests only*. Production forests should let the 10-day default replicate; using the back-dated override means the first consumer to call into the KDS may target a DC that has not yet received the new root key from replication.</Sidenote>

The root key lives at `CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,<forest-DN>` and carries four attributes that downstream offensive-research tools enumerate: `msKds-RootKeyData` (the actual key bytes), `msKds-KDFAlgorithmID` (the KDF identifier, currently SP800-108 HMAC-SHA512), `msKds-KDFParam` (the KDF parameter block), and `msKds-PrivateKeyLength`. These four attributes, together, are everything a deterministic-KDF derivation needs to recompute every group key the forest will ever produce.

<Definition term="KDS root key">
The single forest-wide secret that anchors every per-(group, period) key the Microsoft Key Distribution Service will ever derive, for every DPAPI-NG `SID=` blob, every gMSA, every dMSA, every Hello-for-Business software-KSP container, and every Credential-Guard isolated-secret persistence wrap. Provisioned with [`Add-KdsRootKey`](https://learn.microsoft.com/en-us/powershell/module/kds/add-kdsrootkey), exactly once per forest. Stored as four attributes (`msKds-RootKeyData`, `msKds-KDFAlgorithmID`, `msKds-KDFParam`, `msKds-PrivateKeyLength`) under `CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,<forest-DN>`. Currently, [Microsoft documents no rotation procedure](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi-backup-keys-on-ad-domain-controllers); see §9.3.
</Definition>

### The L0 / L1 / L2 derivation chain

The [`[MS-GKDI]` specification](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) (current revision class 10.0, April 23 2024) describes the protocol. Internally, the KDS computes a three-level derivation:

- **L0 seed key**: derived from the root key and a label that includes the period-in-hours-thousands tier and the group-related input, via a NIST SP 800-108 KDF in counter mode using HMAC-SHA-512 (see [NIST SP 800-108r1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf), August 2022).
- **L1 seed key**: derived from L0 with a second-tier label.
- **L2 seed key**: derived from L1 with the third-tier label. This is the actual symmetric key the DPAPI-NG blob's content key wraps under (or the seed for a per-period group ECDH key pair, in the public-key DPAPI-NG mode).

<Definition term="L0 seed key">
The first level of the [`[MS-GKDI]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) three-tier derivation chain. Computed deterministically from the KDS root key and a label that combines the time-period tier (in units of `period-in-hours / 1000`) and a group-related input. The whole point of the chain is that any DC that holds the same root key, given the same label, derives the same L0 seed key without coordination with any other DC -- which is also the whole reason a single root-key compromise compromises every key the forest will ever derive.
</Definition>

<Mermaid caption="The KDS L0/L1/L2 chain. One forest-wide root key plus a deterministic SP800-108 KDF means every DC computes the same per-(group, period) key with no inter-DC traffic.">
flowchart TD
    Root["KDS root key<br/>(forest-wide secret)"] --> L0["L0 seed key<br/>(period_hours / 1000 + group input)"]
    L0 --> L1["L1 seed key<br/>(second-tier label)"]
    L1 --> L2["L2 seed key<br/>(third-tier label)"]
    L2 --> Symmetric["Per-(group, period)<br/>symmetric key OR<br/>ECDH key-pair seed"]
    KDF["SP800-108 counter-mode<br/>KDF, HMAC-SHA-512"] -.derives.-> L0
    KDF -.derives.-> L1
    KDF -.derives.-> L2
</Mermaid>

### The `GetKey` round trip

A member computer's local KDS-NG provider calls the `GetKey` RPC (the primary opnum of `[MS-GKDI]`) against any reachable writable DC. The DC computes the L0/L1/L2 chain on demand and returns the per-(group, period) key material. No inter-DC synchronisation is needed because the KDF is deterministic.

<Mermaid caption="GetKey round trip: any member, any DC, same answer.">
sequenceDiagram
    participant Member as "Member computer (Microsoft Key Protection Provider)"
    participant DC as "Nearest writable DC (kdssvc.dll)"
    participant Root as Root key (AD-replicated)
    Member->>DC: GetKey(group_sid, period_id)
    DC->>Root: Read msKds-RootKeyData + KDF params
    Root-->>DC: Root-key material
    DC->>DC: Compute L0(period, group) -> L1 -> L2
    DC-->>Member: Per-(group, period) key material
    Member->>Member: Wrap (or unwrap) DPAPI-NG blob
</Mermaid>

<Aside label="Why deterministic SP800-108 is exactly the structural feature that creates Golden gMSA">
Because the KDF is deterministic by design -- this is exactly the security property [NIST SP 800-108r1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf) §5 establishes -- an attacker who reads the four root-key attributes once can derive every per-(group, period) key the KDS will ever produce, *for that root key*, without further DC interaction. The same property that lets a Singapore laptop and a Seoul phone derive the same key without talking to each other lets an attacker who reads the root key derive every gMSA password the forest will ever issue. This is the same property that makes [Golden gMSA](https://www.semperis.com/blog/golden-gmsa-attack/) work in §10.
</Aside>

Every DC in the forest runs the same `kdssvc.dll` over the same root key with the same KDF; every authorised member of the named group can ask any DC for the per-(group, period) key and receive the same answer. The architecture is elegant. It is also, by structural necessity, the architecture that makes a one-shot read of the root key into a one-shot compromise of every key the forest will ever derive. Hold that thought; it is what §10 is built on. First we look at what actually rides on this substrate today.

## 8. The Four Things That Ride DPAPI-NG in 2026

One protocol, four production consumers. The same KDS root key that protects a `SID=` DPAPI-NG blob is also the root from which every gMSA password, every dMSA password, every TPM-less Windows Hello private key, and every Credential Guard isolated NT-hash is derived.

<Definition term="Group Managed Service Account (gMSA)">
A Windows-Server-2012-introduced Active Directory account class whose password is computed automatically by the [Microsoft Key Distribution Service](https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview) on the DC, rotated every 30 days, 256 bytes long, and shared across multiple service hosts via the `msDS-GroupMSAMembership` SDDL gate. From the Microsoft Learn overview verbatim: *"For a gMSA, the domain controller computes the password on the key that the Key Distribution Services provides, along with other attributes of the gMSA."* `Install-ADServiceAccount` on each member computer caches the derivation locally so the service can boot under the account.
</Definition>

<Mermaid caption="One root key, four production consumers: every shipping multi-host or multi-cycle Windows credential primitive sits on the same KDS substrate.">
flowchart TD
    Root["KDS root key"] --> Chain["[MS-GKDI] L0/L1/L2 chain"]
    Chain --> GMSA["gMSA password<br/>(30-day rotation,<br/>256 bytes)"]
    Chain --> DMSA["dMSA password<br/>(machine-bound,<br/>Server 2025)"]
    Chain --> WHfB["WHfB software-KSP<br/>(TPM-less devices,<br/>SID + device descriptor)"]
    Chain --> CG["Credential Guard<br/>LsaIso-isolated secret<br/>(trustlet identity descriptor)"]
</Mermaid>

### 8.1 Group Managed Service Accounts

gMSAs shipped in Windows Server 2012 (October 2012). The model: one AD account, multiple service hosts, no admin-managed password rotation, no per-service password file. The [Microsoft Learn overview](https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview) is verbatim about the chain: *"The Microsoft Key Distribution Service (kdssvc.dll) lets you securely obtain the latest key or a specific key with a key identifier for an Active Directory account... For a gMSA, the domain controller computes the password on the key that the Key Distribution Services provides, along with other attributes of the gMSA."*

The constructed attribute that surfaces the password to authorised member computers is [`msDS-ManagedPassword`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a9019740-3d73-46ef-a9ae-3ea8eb86ac2e); it is computed on demand by the DC from the KDS chain when an authorised member queries it. The authorisation gate is the `msDS-GroupMSAMembership` security descriptor on the gMSA object: only principals whose SIDs satisfy that SDDL get the password back. Rotation is every 30 days. The password length is 256 bytes -- *"a randomly generated password of 256 bytes, making it infeasible to crack"* per the [Semperis Golden gMSA write-up](https://www.semperis.com/blog/golden-gmsa-attack/), which is true *if and only if* the KDS root key is intact. We come back to that *if and only if* in §10.

### 8.2 Delegated Managed Service Accounts

dMSAs shipped in Windows Server 2025 ([GA November 1, 2024](https://en.wikipedia.org/wiki/Windows_Server_2025)). The same KDS chain; a different authorisation gate. Rather than binding to AD group membership, dMSA authentication binds to *machine identity*: the [Microsoft Learn overview](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-overview) describes dMSAs as *"a machine account with managed and fully randomized keys, while disabling original service account passwords. Authentication for dMSA is linked to the device identity, which means that only specified machine identities mapped in Active Directory (AD) can access the account."* The `msDS-ManagedPasswordId` attribute carries a machine-binding component. Microsoft's marketing framing positions dMSA as the "Kerberoast-immune" replacement for static service accounts.

The Server 2025 dMSA design has its own §10 footnote: the [July 2025 Semperis Golden dMSA disclosure](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/) found that the `msDS-ManagedPasswordId` time-component has predictable structure with only ~1 024 plausible values, making offline brute-forcing tractable once the KDS root key is in hand.

### 8.3 Windows Hello for Business software-KSP credentials

The [Hello for Business *how it works* page](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works) describes the credential as a per-user per-device asymmetric key pair. On TPM-equipped devices the private key lives in the TPM and DPAPI-NG is not in the wrap path. On TPM-less devices (the structural worst case) the WHfB private key sits in a CNG software Key Storage Provider container persisted as a [DPAPI-NG-protected file](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works) whose protection descriptor binds it to the user SID + device. The *TPM in Windows* article in this series covers the TPM-bound case; here we record only that the software-KSP fallback rides on DPAPI-NG and inherits the KDS root-key dependency for the SID-bound branch.

<Definition term="Software KSP (CNG Software Key Storage Provider)">
A non-hardware-bound CNG key-storage provider that persists key material in DPAPI-NG-protected files in the user profile. Used by [Windows Hello for Business on TPM-less devices](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works) and as the fallback when no hardware-bound KSP (TPM, smart card, Secure Enclave equivalent) is available. The structural worst case for the WHfB credential, because the private key lives in a file the OS can read in any user-context process, wrapped under a DPAPI-NG blob whose protection descriptor reduces back to the KDS root key for SID-anchored bindings.
</Definition>

### 8.4 Credential Guard isolated-secret persistence

The companion *Credential Guard* article in this series covers `LsaIso.exe` and the LSAISO trustlet's isolation boundary in depth; what matters here is that the trustlet's persistence layer is DPAPI-NG. `LsaIso.exe`, running in VTL1 IUM, stores the isolated NT one-way function outputs and Kerberos session keys in DPAPI-NG blobs whose protection descriptor binds them to the trustlet's own identity. The VSM master key (TPM-bound on TPM-2.0 systems per Microsoft Learn's [Credential Guard](https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/) overview, which describes how Credential Guard "uses [Virtualization-based security (VBS)](https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-vbs) to isolate secrets") is what the trustlet seals its DPAPI-NG protection state under across reboots. The end result is that even though the Credential Guard model puts the credential outside `lsass.exe`, the *persistence* of that isolated secret rides on the same KDS-rooted DPAPI-NG chain every other consumer in this section uses.

| Consumer | Rotation | Authorisation gate | On-disk artefact | Recovery story |
|---|---|---|---|---|
| gMSA | 30 days | `msDS-GroupMSAMembership` SDDL | Cached on member via `Install-ADServiceAccount` | Recompute via KDS at any time |
| dMSA | Managed by KDS | Machine identity in AD | Cached on member; `msDS-ManagedPasswordId` carries machine binding | Recompute via KDS |
| WHfB software-KSP | Per-credential lifetime | User SID + device | DPAPI-NG-wrapped key container in user profile | New enrollment if lost |
| LsaIso DPAPI-NG persistence | Boot-cycle bound | Trustlet identity | Trustlet-managed VTL1 store, sealed under VSM master key | Re-derive on next logon |

Every shipping Windows credential primitive that *just works* across multiple devices, multiple service hosts, or multiple cold-boot cycles -- without per-application key-management code -- is sitting on the same KDS root key. The architectural bet is that the root key never leaks. The next two sections are about what happens when it does.

## 9. The Five Structural Ceilings DPAPI Cannot Close

A reader who has followed the chain through §§3-8 has earned the right to a sharp question: where does this whole architecture *fail*? Not where does it have bugs (it has very few cryptographic ones); where does the *design* draw a line that no implementation patch can move? There are exactly five such lines.

### 9.1 The user-context ceiling

Any process running as the user can call [`CryptUnprotectData`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata) or [`NCryptUnprotectSecret`](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret) against any blob the user could decrypt. The chain binds the secret to the *user identity*, not the *consuming process identity*. This is the structural reason every modern browser-cookie-stealer family (Lokibot, Vidar, RedLine, Lumma, StealC) works against DPAPI-protected Chrome state keys; it is also the reason Google introduced [Chrome app-bound encryption](https://thehackernews.com/2024/08/google-chrome-adds-app-bound-encryption.html) in July 2024 *outside* DPAPI. Will Harris of the Chrome security team is verbatim about why the patch had to live outside DPAPI rather than inside it: *"On Windows, Chrome uses the Data Protection API (...). However, the DPAPI does not protect against malicious applications able to execute code as the logged in user -- which info-stealers take advantage of."*

<PullQuote>
"On Windows, Chrome uses the Data Protection API (DPAPI). However, the DPAPI does not protect against malicious applications able to execute code as the logged in user -- which info-stealers take advantage of."
-- Will Harris, Chrome security team, July 2024
</PullQuote>

### 9.2 The single-password-derivation ceiling

The classic-DPAPI master-key wrap is `PBKDF2-HMAC-SHA512(SHA1(NT-hash), SID-as-UTF16LE, ~8000 iterations)` in the modern (Windows 10/11) era per the [Passcape table](https://www.passcape.com/windows_password_recovery_dpapi_master_key). 8 000 iterations of HMAC-SHA-512 is not strong against a modern wordlist attack on a leaked NT hash. The structural limit is *the user's password*, not the cryptographic primitive. The KDF parameter is tunable; the *single secret in the chain* is not -- a strong password makes the chain strong, a weak password makes the chain weak, and there is no architectural way to recover from a weak password short of re-deriving every user's master key under a stronger one.

### 9.3 The KDS-root-key irrotability ceiling

Once a KDS root key is in production use, rotating it would invalidate every gMSA `msDS-ManagedPassword`, every dMSA password, every `SID=` blob, every Hello-for-Business software-KSP container, and every Credential-Guard isolated-secret blob ever produced under it. Microsoft's documented mitigation is *preventative* (a system access control list on `msKds-RootKeyData`), not recoverable. This is the same structural ceiling that the [Microsoft Learn DPAPI-backup-keys page](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi-backup-keys-on-ad-domain-controllers) admits for the older `[MS-BKRP]` keys, in identical language: *"There currently is no officially supported way of changing or rotating these DPAPI backup keys on the domain controllers."* Burn-the-forest-and-rebuild for `[MS-BKRP]`; SACL-the-attribute-and-hope for KDS root keys.

### 9.4 The hibernation / S4 ceiling

[`CryptProtectMemory` / `CryptUnprotectMemory`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectmemory) provide an in-memory scrub primitive that scopes a secret across same-process / cross-process / cross-session lifetimes. They cannot scrub RAM written to `hiberfil.sys` on suspend-to-disk. On resume the master key is in plaintext in the page cache. The structural defence is BitLocker on the system volume (the *BitLocker on Windows* article in this series covers full-volume encryption end-to-end); within DPAPI itself the ceiling cannot move because the OS has to be able to resume.

### 9.5 The domain-backup-key concentration ceiling

The [`[MS-BKRP]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/) backup key is the recoverability story for the password-reset case -- and it is *also* the structural reason any DA who can dump LSASS on a writable DC has every user's master key in the forest. `mimikatz "lsadump::backupkeys /system:dc.contoso.local /export"` is the canonical primitive, documented in the [Mimikatz DPAPI wiki](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi). The architectural answer (HSM-backing the DC's RSA private key) is not in Microsoft's mainline guidance; the [recommendation when these keys are compromised](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi-backup-keys-on-ad-domain-controllers) is the burn-the-forest-and-rebuild line we just quoted.

> **Key idea:** These five ceilings are not bugs. They are the design's price for the design's other guarantees. The user-context ceiling buys ubiquitous adoption. The single-password ceiling buys a usable recovery path. The KDS-root-key ceiling buys cross-DC determinism. The hibernation ceiling buys process performance and resumability. The domain-backup-key ceiling buys enterprise recovery. Every one of the next five years' DPAPI incidents will hit one of these five ceilings.

The 2022-2025 disclosures are not five surprises. They are five different attackers naming five different ceilings out loud. The next section walks the named incidents one ceiling at a time.

## 10. The 2022-2025 Residual Class: Golden gMSA, Golden dMSA, and Chromium App-Bound Encryption

In March 2022, Yuval Gordon at Semperis published [*Introducing the Golden GMSA Attack*](https://www.semperis.com/blog/golden-gmsa-attack/) and the [`GoldenGMSA`](https://github.com/Semperis/GoldenGMSA) C# tool. In July 2024, Google announced [Chrome's app-bound encryption](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html). In July 2025, Adi Malyanker (also Semperis) published [*Golden dMSA: What Is dMSA Authentication Bypass?*](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/) and the [`GoldenDMSA`](https://github.com/Semperis/GoldenDMSA) tool, mirrored on PR Newswire with a [July 16, 2025 dateline](https://www.prnewswire.com/news-releases/semperis-research-uncovers-critical-flaw-in-windows-server-2025-exposing-managed-service-accounts-to-golden-dmsa-attack-302506258.html). Three disclosures in three years, three different ceilings -- but the same underlying pattern: each one is an admission that the structural ceiling cannot be patched inside DPAPI.

<Mermaid caption="The five ceilings -> the named 2022-2025 incidents.">
flowchart LR
    C1["§9.1 user-context"] --> Chromium["Chromium app-bound encryption<br/>(July 2024, Will Harris)"]
    C2["§9.3 KDS-root-key irrotability"] --> GGMSA["Golden gMSA<br/>(March 2022, Y. Gordon)"]
    C2 --> GDMSA["Golden dMSA<br/>(July 2025, A. Malyanker)"]
    C3["§9.5 domain-backup-key"] --> MimikatzBKK["mimikatz lsadump::backupkeys"]
    C4["§9.4 hibernation / S4"] --> BL["BitLocker article cross-link"]
</Mermaid>

### 10.1 Golden gMSA (Yuval Gordon, Semperis, March 2022)

Targets the §9.3 KDS-root-key irrotability ceiling. Gordon is verbatim about the two-step nature of the attack: *"An attacker with high privileges can obtain all the ingredients for generating the password of any gMSA in the domain at any time with two steps: Retrieve several attributes from the KDS root key in the domain... Use the GoldenGMSA tool to generate the password of any gMSA associated with the key, without a privileged account."* Step 1 is a one-shot read of the KDS root key attributes. Step 2 is offline derivation. There is no further DC interaction, ever, because the [`[MS-GKDI]` chain is deterministic](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) by NIST SP 800-108r1 design.

The defensive answer is [in the GoldenGMSA repository](https://github.com/Semperis/GoldenGMSA): *"configure a SACL on the KDS root key objects for everyone reading the msKds-RootKeyData attribute. Once the system access control list (SACL) is configured, any attempt to dump the key data of a KDS root key will generate security event 4662 on the DC where the object type is msKds-ProvRootKey and the account name is not a DC."* Plus the cross-trust SACL on `msDS-ManagedPasswordId` with property GUID `{0e78295a-c6d3-0a40-b491-d62251ffa0a6}`. This is a *detective* control, not a *recovery* control. Once the root key has been read, every gMSA password the forest has ever issued or will ever issue under that root key is offline-derivable.

<PullQuote>
"An attacker with high privileges can obtain all the ingredients for generating the password of any gMSA in the domain at any time with two steps."
-- Yuval Gordon, Semperis, March 2022
</PullQuote>

<Sidenote>The Golden gMSA SACL detects same-trust reads only. Cross-trust reads of `msDS-ManagedPasswordId` from a forest the auditing forest does not control are the documented detection blind-spot. If your gMSA-using forest has a one-way trust from a forest you do not own, you cannot see its reads.</Sidenote>

### 10.2 Golden dMSA (Adi Malyanker, Semperis, July 2025)

Targets the §9.3 ceiling, plus a fresh dMSA-specific surface. Malyanker is verbatim about the structural flaw: *"a critical design flaw: a structure that's used for the password-generation computation contains predictable time-based components with only 1,024 possible combinations, making brute-force password generation computationally trivial."* The four-phase pipeline is enumerated in the [GoldenDMSA repository](https://github.com/Semperis/GoldenDMSA): *"Phase 1: Key Material Extraction (pre requirement of the attack) -- Dump the KDS Root Key from the DC. Phase 2: Enumerate dMSA accounts ... Phase 3: ManagedPasswordID guessing ... Phase 4: Password Generation."* The tool exposes commands `wordlist`, `info`, `kds`, `bruteforce`, `compute`, and `convert` -- the operational vocabulary the four-phase pipeline needs.

Semperis' own rating is MODERATE with the explicit caveat *"to exploit it, attackers must possess a KDS root key available only to only the most privileged accounts: root Domain Admins, Enterprise Admins, and SYSTEM."* That is exactly the §9.3 ceiling re-stated. The novelty in dMSA is the 1 024-combination time-component flaw -- a design weakness on top of the structural ceiling, not a substitute for it.

### 10.3 Chromium app-bound encryption (Will Harris, Google Chrome, July 30, 2024)

Targets the §9.1 user-context ceiling. The [Google Security Blog announcement](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html) describes a COM-elevation service that wraps the Chrome state key with both DPAPI *and* a per-binary identity check the COM service enforces. The verbatim quote, mirrored via [The Hacker News](https://thehackernews.com/2024/08/google-chrome-adds-app-bound-encryption.html): *"Because the app-bound service is running with system privileges, attackers need to do more than just coax a user into running a malicious app. Now, the malware has to gain system privileges, or inject code into Chrome, something that legitimate software shouldn't be doing."*

The architecturally important word is *"app-bound."* Chromium's response *to the user-context ceiling* lives outside DPAPI. DPAPI itself is unchanged. The 2024 patch is application-layer code-identity pinning -- exactly what Generation-1 Protected Storage's abandoned Authenticode-access-rule clause was supposed to be in 2000, exactly what [Apple Keychain](https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web) has shipped on macOS for over two decades, exactly what the §11 wishlist asks for.

### 10.4 The recurring pattern

Each disclosure does *not* break a cryptographic primitive. Each is a re-statement of "the design's ceiling is the design's ceiling." The defensive answers are *detection* (SACL audit; cross-trust read alerting; binary-identity check) and *workaround at a higher layer* (the COM-elevation service Chrome wraps DPAPI in), never *cryptographic strengthening of DPAPI itself*. The 2026 reader's job is to recognise which ceiling each new incident hits.

| Year | Disclosure | Ceiling hit | Tool reference | Defensive answer |
|---|---|---|---|---|
| 2022 | Golden gMSA (Y. Gordon, Semperis) | §9.3 KDS irrotability | [`GoldenGMSA`](https://github.com/Semperis/GoldenGMSA) | SACL on `msKds-RootKeyData`; Event 4662 |
| 2024 | Chromium app-bound encryption (W. Harris, Google) | §9.1 user-context | [Chrome 127 release notes](https://chromereleases.googleblog.com/2024/07/stable-channel-update-for-desktop_23.html) | COM-elevation per-binary identity check, outside DPAPI |
| 2025 | Golden dMSA (A. Malyanker, Semperis) | §9.3 + dMSA `msDS-ManagedPasswordId` predictability | [`GoldenDMSA`](https://github.com/Semperis/GoldenDMSA) | SACL plus monitoring `msDS-ManagedPasswordId` brute-force |

By 2026 the pattern is clear: DPAPI's structural ceilings produce a steady drip of disclosures, each defensively answerable but none cryptographically fixable inside DPAPI itself. The open question is what the *successor* would even look like -- and that is the next section.

## 11. Open Problems

A few sharp open problems remain at the design layer -- problems that a future Generation 4 of the credential-vault tradition would have to solve. None of them is in Microsoft's published roadmap as of 2026.

### KDS root-key rotation

A hybrid wrap-then-re-wrap-on-first-decrypt-under-new-root scheme could in principle restore rotation without invalidating existing blobs: every consumer would carry a tag indicating which root-key generation last unwrapped it; on first unwrap under a generation-N+1 root the system would re-wrap the consumer-side cache. No standard or product implements this today; no public proposal revises [`[MS-GKDI]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) to add it.

### Code-identity pinning

[Apple Keychain](https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web) enforces *"keychain items can be shared only between apps from the same developer ... enforced through code signing, provisioning profiles, and the Apple Developer Program."* DPAPI / DPAPI-NG have no equivalent. A `CALLER_ATTRIBUTION=Publisher:<wdac-rule-hash>` descriptor would, in principle, give Windows the same property -- the SHA-256 hash of the [WDAC rule](https://paragmali.com/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/) that authorises the calling binary, baked into the descriptor, checked at unwrap time. No one is shipping it. The 2024 [Chromium app-bound encryption response](https://thehackernews.com/2024/08/google-chrome-adds-app-bound-encryption.html) is the application-layer workaround that proves the design gap is real.

### Post-quantum migration

DPAPI-NG today uses RSA-OAEP (`CERTIFICATE=` with RSA private keys) or ECDH P-256 / P-384 (`SID=` group descriptors) per the [protected-data-format](https://learn.microsoft.com/en-us/windows/win32/seccng/protected-data-format) reference. Both wrap algorithms are vulnerable to Shor's algorithm on a sufficiently large quantum computer, and the persistent on-disk blob format is the harvest-now-decrypt-later target -- a framing surfaced across NIST's broader post-quantum migration corpus, including the [NIST PQC project page](https://csrc.nist.gov/projects/post-quantum-cryptography) and the linked NIST IR 8547 migration-timeline document. The migration story for the symmetric chain is comparatively easy (the SP800-108 KDF is parameterised; HMAC-SHA-512 has no quantum-cliff weakness for the relevant key sizes). The migration story for the public-key wrap is the hard part. NIST published [FIPS 203 (ML-KEM)](https://csrc.nist.gov/pubs/fips/203/final) and [FIPS 204 (ML-DSA)](https://csrc.nist.gov/pubs/fips/204/final) in August 2024; [OpenSSH 9.9](https://www.openssh.com/releasenotes.html) (September 2024) and TLS deployments shipped hybrid post-quantum key exchange. Windows added experimental post-quantum TLS support in 2024 but has not yet announced ML-KEM CNG-DPAPI providers; see the companion [*Post-Quantum Cryptography on Windows*](https://paragmali.com/blog/post-quantum-cryptography-on-windows-the-thirty-year-migrati/) article in this series for the broader Windows migration story. The shape that fits DPAPI's ceiling-laden design is hybrid wrap-then-re-wrap-on-first-decrypt: a hybrid wrap (`RSA-OAEP || ML-KEM` or `ECDH || ML-KEM`) -- protect under (RSA+ML-KEM) or (ECDH+ML-KEM) today; let consumers re-wrap to the new combiner on first unwrap; phase out the classical half on a long horizon -- is the only forward-compatible answer; Microsoft's published [DPAPI-NG protection-providers](https://learn.microsoft.com/en-us/windows/win32/seccng/protection-providers) have not yet announced one.

### Credential roaming for Entra-joined-only / unmanaged-device estates

Classic DPAPI's cross-device story has always been "use roaming profiles" (deprecated) or "use the [`[MS-BKRP]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/) backup key" (admin-recovery, not user-driven). DPAPI-NG's `SID=` solves it cleanly for AD-joined estates but the modern Entra-only estate has no native equivalent -- the `LOCAL=user` descriptor is per-machine. An `ENTRAGROUP=<object-id>` descriptor that resolved through Entra ID Token Service the way `SID=` resolves through KDS would close the gap. No public roadmap announces this.

### Hibernation / S4

[BitLocker](https://en.wikipedia.org/wiki/BitLocker) on the system volume defends the disk; suspending fully to ROM (or refusing S4 entirely) defends the in-RAM master key. Hardware-bound key derivation (TPM-released-only-while-PCRs-stable) would close more of the gap; the *TPM in Windows* article in this series covers TPM-bound primitives that approximate this property.

Every one of these is a *design* gap -- a property the architecture would need a new primitive to satisfy. None is on Microsoft's announced roadmap. The architecture we have is the architecture we will have for at least the next five years; the practitioner's job is to know which ceilings their estate is exposed to and how to detect each of them.

## 12. Practical Guide and Closing

The four-audience guide for the 2026 practitioner.

### 12.1 For a developer

Use [`CryptProtectData` / `CryptUnprotectData`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata) for per-user-on-this-device secrets. Pass `pOptionalEntropy` to *bind* the blob to a per-application secret -- but understand it is security-by-obscurity, not a code-identity check; any reader of the [SharpDPAPI source](https://github.com/GhostPack/SharpDPAPI) who knows your constant entropy can reproduce the unprotect call as the user.

Use [`NCryptProtectSecret`](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret) with the appropriate descriptor for cross-device or multi-principal cases. `LOCAL=user` mirrors classic DPAPI on a single machine. `SID=<group-sid>` reaches AD groups via KDS. `CERTIFICATE=HashID:<sha1>` reaches a named certificate (TPM-backed for the high-security case). Use the WebAuthn / FIDO2 path for *authentication* secrets; do not store passwords in DPAPI when WHfB / passkey paths are available.

<RunnableCode lang="js" title="DPAPI-NG descriptor selection logic (no real credentials)">{`
// Returns the appropriate DPAPI-NG protection-descriptor string for a use case.
// Reference: learn.microsoft.com windows win32 seccng protection-descriptors
function descriptorFor(useCase, ctx) {
  switch (useCase) {
    case "single-device-single-user":
      return "LOCAL=user";

    case "ad-group":
      // Multiple machines, AD-authorized group members can decrypt.
      return "SID=" + ctx.groupSid;

    case "tpm-backed-cert":
      // Decrypter must hold the named certificate's private key (TPM-bound KSP).
      return "CERTIFICATE=HashID:" + ctx.certThumbprintSha1;

    case "web-credential":
      // Resolves through the Windows credential broker.
      return "WEBCREDENTIALS=" + ctx.credName;

    default:
      throw new Error("Unknown use case: " + useCase);
  }
}

console.log(descriptorFor("single-device-single-user"));
console.log(descriptorFor("ad-group", { groupSid: "S-1-5-21-...-5101" }));
`}</RunnableCode>

> **Note:** The `pOptionalEntropy` parameter to `CryptProtectData` lets you tag a blob with an extra secret the unwrap call must supply. It does not bind the blob to a process or a publisher. If your "entropy" is a constant compiled into your binary, every reverse-engineer who reads the binary can reproduce your unprotect call as the user. For real per-application separation today, use the [Chromium 2024 pattern](https://thehackernews.com/2024/08/google-chrome-adds-app-bound-encryption.html): wrap your DPAPI / DPAPI-NG blob in a SYSTEM-elevated COM service that enforces a per-binary identity check.

### 12.2 For a defender or DFIR analyst

Triage the credential-vault inventory in §5 first. The high-value paths are `%APPDATA%\Microsoft\Protect\&lt;SID&gt;\`, `%APPDATA%\Microsoft\Protect\CREDHIST`, `%APPDATA%\Microsoft\Credentials\`, `%LOCALAPPDATA%\Microsoft\Vault\`, the Chromium / Edge profile databases, and the AD `CN=Master Root Keys,...` container.

The SACL guidance from the [GoldenGMSA repository](https://github.com/Semperis/GoldenGMSA) is the only detective control today: *"configure a SACL on the KDS root key objects for everyone reading the msKds-RootKeyData attribute. Once the system access control list (SACL) is configured, any attempt to dump the key data of a KDS root key will generate security event 4662 on the DC where the object type is msKds-ProvRootKey and the account name is not a DC."* Plus the cross-trust SACL on `msDS-ManagedPasswordId` with property GUID `{0e78295a-c6d3-0a40-b491-d62251ffa0a6}`.

Tooling: [Mimikatz](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi) `dpapi::*` modules, [SharpDPAPI](https://github.com/GhostPack/SharpDPAPI), [DPAPIck](https://elie.net/talk/reversing-dpapi-and-stealing-windows-secrets-offline) (Bursztein-Picod 2010), [GoldenGMSA](https://github.com/Semperis/GoldenGMSA), [GoldenDMSA](https://github.com/Semperis/GoldenDMSA), and the [Volatility](https://github.com/volatilityfoundation/volatility3) `lsadump` / `cachedump` / `hashdump` plugins for live-memory extraction of `DPAPI_SYSTEM` and the other LSA secrets that seed SYSTEM-context master-key derivation.

<RunnableCode lang="python" title="Synthetic credential-vault triage walker (no real disk image)">{`
# Walk a synthetic profile-directory layout and emit a DPAPI-relevant triage report.
import os
from collections import defaultdict

# Synthetic profile layout for demonstration only.
synthetic = {
    "Users/alice/AppData/Roaming/Microsoft/Protect/S-1-5-21-1234-1001/Preferred": "8KB",
    "Users/alice/AppData/Roaming/Microsoft/Protect/S-1-5-21-1234-1001/0d4a...": "740B",
    "Users/alice/AppData/Roaming/Microsoft/Protect/CREDHIST": "176B",
    "Users/alice/AppData/Roaming/Microsoft/Credentials/abcdef...": "300B",
    "Users/alice/AppData/Local/Microsoft/Vault/4BF4C442-9B8A-41A0-B380-DD4A704DDB28/Policy.vpol": "180B",
    "Users/alice/AppData/Local/Google/Chrome/User Data/Default/Cookies": "4MB",
    "Users/alice/AppData/Local/Google/Chrome/User Data/Local State": "12KB",
}

categories = {
    "Master keys":          "/Microsoft/Protect/S-1-",
    "CREDHIST chain":       "/Protect/CREDHIST",
    "Credential Manager":   "/Microsoft/Credentials/",
    "Windows Vault":        "/Microsoft/Vault/",
    "Chrome state key":     "/Google/Chrome/User Data/Local State",
    "Chrome cookies":       "/Google/Chrome/User Data/Default/Cookies",
}

report = defaultdict(list)
for path, size in synthetic.items():
    for label, marker in categories.items():
        if marker in path:
            report[label].append((path, size))

for label, items in report.items():
    print("==", label)
    for path, size in items:
        print("  ", path, "(" + size + ")")
`}</RunnableCode>

### 12.3 For a red-team operator

The chain of primitives most-commonly used (verbatim from the [harmj0y operational guide](https://blog.harmj0y.net/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/) and the [Mimikatz wiki](https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi)):

<Spoiler kind="solution" label="Operator command sequence (defensive context only)">
The operational vocabulary, in order of dependency:

1. `mimikatz "sekurlsa::dpapi"` -- enumerate cached master keys from a live `lsass.exe`.
2. `mimikatz "dpapi::masterkey /in:<MK> /sid:&lt;SID&gt; /password:<known>"` -- unwrap a master-key file.
3. `mimikatz "dpapi::cred /in:<credfile>"` -- decrypt a Credential Manager entry.
4. `mimikatz "lsadump::backupkeys /system:dc.contoso.local /export"` -- export the [`[MS-BKRP]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/) RSA private key from a writable DC.
5. `SharpDPAPI triage /pvk:key.pvk` -- offline triage with the domain backup key.
6. `SharpChrome cookies /pvk:key.pvk` -- decrypt Chrome / Edge cookies offline.
7. `GoldenGMSA gmsainfo` then `GoldenGMSA compute -k <root-key-guid> -s <gmsa-sid> -m <managed-password-id>` -- offline gMSA password derivation per the [March 2022 disclosure](https://www.semperis.com/blog/golden-gmsa-attack/).
8. `GoldenDMSA wordlist` / `bruteforce` / `compute` -- the four-phase Server 2025 dMSA pipeline per the [July 2025 disclosure](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/).
</Spoiler>

The post-Credential-Guard scope reality: LSASS-isolated NT-hashes are gone (the *Credential Guard* article in this series covers what `LsaIso.exe` actually computes); on-disk DPAPI master keys, Chrome cookies, and Vault credentials are still there. The credential-vault inventory in §5 is the operator's map; the §10 disclosure list is the operator's playbook.

### 12.4 For a platform or identity engineer

Provision the KDS root key carefully. Use the [`Add-KdsRootKey`](https://learn.microsoft.com/en-us/powershell/module/kds/add-kdsrootkey) default 10-day `EffectiveTime` for production forests so AD replication converges before any consumer derives against the new key; the `-EffectiveTime ((Get-Date).AddHours(-10))` override is for single-DC test forests only, never production.

> **Note:** The Golden gMSA defensive answer is detective, not preventive. Configure the `msKds-RootKeyData` SACL *before* any production gMSA exists, so every read of the root-key attributes generates Security Event 4662 and you have a baseline of "DC accounts only, no humans, ever." Add the `msDS-ManagedPasswordId` cross-trust audit on day 1 too. After-the-fact SACL provisioning leaves a window during which the key may have been read silently.

For Server 2025 dMSA, monitor the [`msDS-ManagedPasswordId`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a9019740-3d73-46ef-a9ae-3ea8eb86ac2e) brute-force surface until Microsoft addresses the time-component predictability the [Golden dMSA disclosure](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/) named.

For Hello for Business, prefer the TPM-bound KSP (`MS_PLATFORM_CRYPTO_PROVIDER`); the [software-KSP DPAPI-NG fallback](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works) is the structural worst case (per §8.3) and inherits the KDS root-key dependency on every TPM-less device.

Cross-platform context: [Apple Keychain](https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web) reaches a stronger upper bound (Secure-Enclave-bound + code-identity-pinned via the Apple Developer Program); [GNOME libsecret](https://gnome.pages.gitlab.gnome.org/libsecret/) covers the analogous Linux primitive over the Secret Service D-Bus interface. Neither is a drop-in replacement; both have shapes worth borrowing if Microsoft ever publishes the Generation-4 design.

### 12.5 The closing reflection

The credential vault under everything has a single-sentence summary in 2026: classic DPAPI is as strong as the user's password; DPAPI-NG is as strong as the KDS root key's life-cycle SOP is; both architectures admit ceilings the cryptography cannot move. The literacy a practitioner needs is the ability to recognise which ceiling any new incident hits. Twelve sections later, you have it.

## Frequently Asked Questions

<FAQ title="Frequently asked questions">

<FAQItem question="Does Credential Guard mean Mimikatz cannot dump my browser cookies?">
No. [Credential Guard](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works) protects LSA-isolated secrets only. Chrome cookies live in the user's profile under DPAPI / DPAPI-NG and are decryptable by any process running as the user, exactly as before. The [Chromium 2024 app-bound encryption](https://thehackernews.com/2024/08/google-chrome-adds-app-bound-encryption.html) is a per-process workaround for the §9.1 user-context ceiling, not a fix inside DPAPI itself.
</FAQItem>

<FAQItem question="If I reset my Microsoft Account password from the web, why do my saved Windows credentials disappear?">
A web reset of a *consumer* Microsoft Account password does not append to CREDHIST and does not benefit from [`[MS-BKRP]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/) backup -- there is no domain in the consumer scenario. Master keys encrypted under the previous password become irrecoverable for consumer Microsoft Accounts, full stop. Domain-joined enterprise users escape this because the domain backup key still works.
</FAQItem>

<FAQItem question="Are Group Managed Service Account passwords unguessable?">
True if the [KDS root key](https://learn.microsoft.com/en-us/powershell/module/kds/add-kdsrootkey) is intact -- the [Semperis write-up](https://www.semperis.com/blog/golden-gmsa-attack/) records the *"randomly generated password of 256 bytes, making it infeasible to crack"* claim. False under the [Golden gMSA / Golden dMSA](https://www.semperis.com/blog/golden-dmsa-what-is-dmsa-authentication-bypass/) assumption that any DA / SYSTEM on a DC can read `msKds-RootKeyData`. A 256-byte random password is irrelevant if the attacker can derive it offline.
</FAQItem>

<FAQItem question="Is DPAPI-NG just DPAPI on a newer crypto suite?">
No. [DPAPI-NG](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi) is a redesign whose protection model is *descriptor-based* (multi-principal, multi-device) rather than *user-and-machine-bound*. The two APIs coexist; classic DPAPI is still the default for [`CryptProtectData`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata) callers, and DPAPI-NG is the path for [`NCryptProtectSecret`](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret) callers.
</FAQItem>

<FAQItem question="Are Windows Hello for Business private keys always TPM-bound?">
No. On TPM-less devices the WHfB private key sits in a CNG software-KSP container persisted as a DPAPI-NG blob whose protection descriptor binds it to user SID + device, per the [Hello for Business architecture](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/how-it-works). The TPM-bound case is the preferred deployment; the software-KSP fallback is the structural worst case and inherits the §9.3 KDS root-key dependency.
</FAQItem>

<FAQItem question="Does CryptProtectMemory protect against hibernation?">
No. [`CryptProtectMemory`](https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectmemory) scrubs in-memory secrets between same-process / cross-process / cross-session lifetimes but cannot prevent the OS from writing the page-protected RAM into `hiberfil.sys` on suspend-to-disk. BitLocker on the system volume is the structural defence (the *BitLocker on Windows* article in this series covers full-volume encryption end-to-end).
</FAQItem>

<FAQItem question="Did the Bursztein/Picod 2010 paper break DPAPI?">
No. It broke the *secrecy of DPAPI's design*. The [2010 disclosure](https://www.usenix.org/conference/woot10/recovering-windows-secrets-and-efs-certificates-offline) made the master-key chain public and tractable for offline forensics; it did not weaken the cryptography. The "break" was always structural -- DPAPI is as strong as the user's password is. The two-author byline is Bursztein and Picod (Black Hat DC 2010, USENIX WOOT 10), not "Bursztein, Picod and Aussel."
</FAQItem>

</FAQ>

<StudyGuide slug="dpapi-and-dpapi-ng-the-credential-vault-under-everything" keyTerms={[
  { term: "DPAPI", definition: "The Data Protection API; the per-user / per-machine secret-storage primitive in every Windows release from Windows 2000 onward." },
  { term: "Master key", definition: "A 64-byte random secret per user, stored under %APPDATA%/Microsoft/Protect/&lt;SID&gt;/&lt;GUID&gt;, encrypted under a pre-key derived from the user's password and SID." },
  { term: "[MS-BKRP] BackupKey Remote Protocol", definition: "The LSASS-hosted RPC interface that lets a member computer dual-wrap its master key under both the user password pre-key and a DC RSA backup public key; the canonical universal-decryption primitive available to Domain Admins." },
  { term: "CREDHIST", definition: "The previous-password hash chain stored in %APPDATA%/Microsoft/Protect/CREDHIST; one entry per self-initiated password change; broken by administrative reset and consumer-Microsoft-Account web reset." },
  { term: "Protection descriptor (DPAPI-NG)", definition: "The DPAPI-NG self-describing string (SID, SDDL, LOCAL, WEBCREDENTIALS, CERTIFICATE) that names the set of principals permitted to remove protection from a blob." },
  { term: "Microsoft Key Distribution Service (kdssvc.dll)", definition: "The DC-side daemon that implements the [MS-GKDI] protocol and computes per-(group, period) keys deterministically from a single forest-wide root key." },
  { term: "KDS root key", definition: "The single forest-wide secret that anchors every per-(group, period) key the KDS will ever derive; provisioned exactly once per forest with Add-KdsRootKey; documented as having no rotation procedure." },
  { term: "Group Managed Service Account (gMSA)", definition: "A Server-2012-introduced AD account whose 256-byte password is derived from the KDS chain and rotated every 30 days, gated by the msDS-GroupMSAMembership SDDL." },
  { term: "Software KSP", definition: "The non-hardware-bound CNG Key Storage Provider that persists key material as DPAPI-NG-protected files; used as the WHfB fallback on TPM-less devices." },
  { term: "Golden gMSA / Golden dMSA", definition: "The 2022 / 2025 Semperis offline-derivation attacks that compute any gMSA / dMSA password the forest will ever issue, given a one-shot read of the four KDS root-key attributes." }
]} />
