Chrome v20 app-bound encryption, explained
2026-06-21 · 4 min
Chrome 127 added a second cookie-encryption scheme on top of the classic
DPAPI one. The goal was explicit: stop other processes — infostealers
included — from reading a logged-in user's cookies just because they run
as that user. The mechanism, app-bound encryption, ties the key to a
signed chrome.exe and pushes part of the unwrap behind a privileged
service. This post covers what v20 actually is and how far it reduces to
offline crypto, building on
how Chromium encrypts cookies on Windows.
What changed in Local State
The old v10 key is still there. v20 adds a second wrapped key alongside it:
{
"os_crypt": {
"encrypted_key": "RFBBUEkBAAAA...",
"app_bound_encrypted_key": "QVBQQgEAAADQ..."
}
}
Base64-decode app_bound_encrypted_key and the first four bytes are the
ASCII tag APPB (compare the v10 key's DPAPI prefix). Strip it and what
remains is a multi-layer blob — not a single DPAPI envelope.
The v20 cookie value format
On the wire a v20 value looks almost identical to v10:
| Bytes | Meaning |
|---|---|
0..3 | version tag — ASCII v20 |
3..15 | 12-byte AES-GCM nonce |
15..n-16 | ciphertext |
n-16..n | 16-byte GCM authentication tag |
Same AES-256-GCM, same nonce-then-tag layout. Two differences matter:
- It is decrypted with the app-bound key, not the
encrypted_key. - The decrypted plaintext is prefixed with a 32-byte SHA-256 hash of the cookie's host. Strip those 32 bytes to get the actual value. (Chrome uses it as a binding check; for recovery you just discard it.)
Unwrapping app_bound_encrypted_key
After stripping APPB, the key is wrapped in three nested layers:
- SYSTEM DPAPI — outer layer, sealed under the machine DPAPI key.
- User DPAPI — inner layer, sealed under the user's DPAPI masterkey (the same family of secret as the v10 chain).
- App-bound transform — the innermost blob, whose first byte is a flag selecting how the final key was sealed.
The flag byte decides everything downstream:
| Flag | Scheme | Offline? |
|---|---|---|
1 | AES-256-GCM with a hard-coded key (publicly known, b31c6e24…0284787) | Yes |
2 | ChaCha20-Poly1305 with another hard-coded key | Yes, in principle |
3 | Key wrapped via the online CNG / elevation service | No |
Flags 1 and 2 are reducible to math: the keys are public, so once both DPAPI layers are off, decrypting the innermost blob is a plain AEAD operation. Flag 3 is not — the actual unwrap happens inside a privileged service.
The elevation service is the whole point
For flag 3, the innermost step is handled by Chrome's elevation service,
which verifies the caller is a signed chrome.exe before returning the
key. That signature check is the design intent of app-bound encryption: it
is what stops a stealer process — running as the same user, holding the
same DPAPI masterkey — from getting the key the way it could with v10.
There is no file you can carry away that substitutes for a live, trusted
call to that service.
What is actually offline
Honest summary: v20 is partial and version-dependent offline.
| Layer | Needed input | From acquired files? |
|---|---|---|
| SYSTEM DPAPI | machine DPAPI key | Yes — from the SECURITY hive |
| User DPAPI | user masterkey + NT hash/SID | Yes — see the DPAPI chain |
| Flag 1 / 2 transform | hard-coded key (public) | Yes |
| Flag 3 transform | live elevation service | No |
So even with a perfect disk image, a flag-3 key does not come out without the running machine. Flag-1 (and 2) keys do — provided you can peel both DPAPI layers first. Which flag a given profile uses depends on the Chrome build, so treat "can I recover this v20 key offline?" as a per-image question, not a yes/no for the scheme.
What the tool does (and does not)
BrowserForensics ships an experimental v20 method. It does not attempt the DPAPI layers in the browser — give it the app-bound blob with both DPAPI layers already removed (do that with your existing offline DPAPI workflow). From there it:
- Reads the flag byte.
- For flag 1, unwraps the key in-browser with WebCrypto AES-GCM using the public hard-coded key. The GCM tag self-validates — a wrong input fails authentication rather than returning garbage, the same trust property as the DPAPI HMAC check.
- For flags 2 and 3, it reports a clear error rather than pretending.
The recovered key then decrypts v20 cookie values directly — the 32-byte host-hash strip is handled for you, so what you get back is the cookie value. End to end this runs in a tab, like the v10 in-browser decryptor; no key or password leaves the page.