browserforensics
Back to all articles

Offline Windows DPAPI: unwrapping the os_crypt key

2026-06-17 · 4 min

Chromium's cookie key is a DPAPI blob: an AES-256 key encrypted under the Windows user's credentials. The naive way to unwrap it is to call CryptUnprotectData while logged in as that user. On an acquired image you do not have that luxury — but DPAPI is fully reversible offline if you have the right inputs. Here is the whole chain.

What DPAPI protects with

DPAPI never encrypts your data with your password directly. It uses a masterkey — a 64-byte secret stored, itself encrypted, in:

%APPDATA%\Microsoft\Protect\<SID>\<GUID>

One file per masterkey, named by GUID; the folder is named by the user's SID. A DPAPI blob records, in its header, the GUID of the masterkey that sealed it — so the blob tells you which masterkey file you need.

To decrypt a blob you therefore need three things on disk plus one secret:

  1. the blob (here, Local State's encrypted_key),
  2. the masterkey file for the GUID the blob names,
  3. the user's SID (the \Protect\ folder name),
  4. the user's NT hash or password.

Step 1 — decrypt the masterkey

The masterkey file is encrypted with a key derived from the user secret and the SID. impacket's deriveKeysFromUserkey is the reference:

preKey = HMAC-SHA1( NT_hash, UTF16LE(sid + "\0") )

(From a password instead, the equivalent uses SHA1(password) and MD4(password) — and MD4(password) is the NT hash, which is why the hash alone is enough.) That preKey then feeds Microsoft's masterkey derivation, which is not standard PBKDF2 — it XOR-accumulates PRF(P, derived) rather than PRF(P, U_prev). Get that loop wrong and everything downstream is garbage.

The derived bytes split into an AES-256 key and IV, AES-CBC-decrypt the masterkey body, and — crucially — the result carries an HMAC that you verify. That self-check is what makes offline DPAPI trustworthy: a wrong SID or wrong hash fails the HMAC, so a successful decrypt is provably correct. There is no "looks plausible" failure mode.

Modern Windows 10/11 masterkeys use AES-256 with an HMAC-SHA1 or HMAC-SHA512 PRF. Older ones used 3DES; that path is rare and not worth porting for a cookie key.

Step 2 — decrypt the blob

With the 64-byte masterkey in hand, the encrypted_key blob decrypts on its own terms:

keyHash    = SHA1(masterkey)
sessionKey = HMAC-SHA512(keyHash, blob.salt)
plaintext  = AES-256-CBC-decrypt(blob.data, key = sessionKey[:32], iv = 0)

Strip PKCS#7 padding and you have the 32-byte AES key — the same key that decrypts every v10 cookie value in the profile. Chrome calls CryptProtectData with no entropy, so there is no extra secret to supply.

Where the NT hash comes from

The chain needs the user's NT hash (or password). On a disk image you recover it the same way you would for any credential work: from the SAM hive, decrypted with the boot key from SYSTEM. That is exactly what a secretsdump-style tool produces. So the practical pairing is:

  • SYSTEM + SAM → user NT hash,
  • Local State + masterkey file + SID + that NT hash → Chrome AES key,
  • AES key + Cookies → plaintext.

For a domain account whose password you do not have, the user's plaintext password (if known) works directly; otherwise the masterkey may be recoverable via the domain backup key — out of scope here.

A note on app-bound (v20) keys

Everything above unwraps the classic v10 encrypted_key. The v20 app_bound_encrypted_key adds a system-DPAPI layer (recoverable from the machine key in the SECURITY hive) plus an app-bound service step that is not pure offline crypto. Treat v20 as partially-offline until tooling catches up.

Doing it without a Windows box

None of this needs the original machine — it is HMAC, SHA, AES-CBC and a quirky KDF. That is precisely why it can run entirely in a browser tab, with no password or key ever leaving the page.

Further reading