Decrypting Chrome cookies on Linux
2026-06-21 · 4 min
The Windows cookie story is AES-GCM keys wrapped in DPAPI. Linux Chromium
is a different beast: the encrypted_value column is AES-128-CBC, not
GCM, and in a large fraction of real acquisitions there is no secret to
recover at all — the key is hard-coded. If you have only ever decrypted
Windows cookies, the Linux
case will feel almost too easy.
Where the files live
Same SQLite cookies table you know from everywhere else; only the path
changes:
~/.config/google-chrome/<profile>/Network/Cookies
~/.config/chromium/<profile>/Network/Cookies
<profile> is Default, Profile 1, and so on. The encrypted_value
column holds a version-tagged blob just like the other platforms — but the
tag tells you which secret you need, not which cipher.
The blob format
Linux values carry a v10 or v11 prefix, then ciphertext. There is no
nonce and no GCM tag — the IV is fixed, so the layout is just:
| Bytes | Meaning |
|---|---|
0..3 | version tag — ASCII v10 or v11 |
3..n | AES-128-CBC ciphertext (PKCS7-padded plaintext) |
The two tags map directly onto where the key comes from:
v10— encrypted with the hard-codedpeanutskey. No secret needed. Fully offline.v11— encrypted with a per-user secret stored in GNOME Keyring or KWallet. You must supply that secret.
Key derivation (same for both tags)
The cipher and KDF are identical regardless of tag — only the input secret differs:
| Parameter | Value |
|---|---|
| KDF | PBKDF2-HMAC-SHA1 |
| Salt | saltysalt (ASCII) |
| Iterations | 1 |
| Derived key length | 16 bytes (AES-128) |
| Cipher | AES-128-CBC |
| IV | 16 bytes of 0x20 (ASCII spaces) |
| Padding | PKCS7 |
One iteration is not a typo — Linux Chromium really does derive the key
with a single PBKDF2 round. Contrast that with
macOS, which uses the same
saltysalt / CBC construction but 1003 iterations.
The peanuts case: nothing to recover
When Chromium cannot reach a keyring — headless installs, servers, CI
runners, Docker images, and plenty of plain desktop setups using the
"basic" password store — it falls back to the literal string peanuts
as the secret. That string is compiled into the browser. So for every
v10 value:
key = PBKDF2-HMAC-SHA1("peanuts", "saltysalt", 1, 16)
plaintext = AES-128-CBC-decrypt(key, IV=0x20*16, ciphertext)
value = strip_pkcs7(plaintext)
No keyring dump, no live host, no user password. If the acquisition has
the Cookies file, you have the values. This is the single biggest
practical difference from Windows and the reason Linux Chrome cookies are
often trivially readable.
The v11 case: you need the keyring secret
If the profile was created with GNOME Keyring or KWallet available, values
are tagged v11 and the secret is a random per-user string sitting in the
keyring (Chrome Safe Storage / Chromium Safe Storage entry). That
secret is not in the profile directory — it lives in the user's keyring
store and is unlocked with the login password. Recover it from a live or
mounted session (e.g. secret-tool lookup application chrome, or reading
the keyring DB with the password), then feed it in exactly where peanuts
went above. The KDF and cipher do not change.
Decrypting in the browser
The in-browser decryptor
has a Linux (no key needed) method: leave the secret field blank and
it uses peanuts. For v11, paste the keyring secret instead. Everything
runs client-side over WebCrypto — PBKDF2 then AES-CBC — so nothing leaves
the machine.
A useful property of CBC + PKCS7 here is that decryption is self-validating: a wrong secret produces invalid padding and the decrypt fails loudly rather than returning garbage. If a value decrypts to clean UTF-8 with valid padding, your secret is correct.
How the three platforms compare
| Platform | Cipher | KDF / key source | Offline from files alone? |
|---|---|---|---|
Linux v10 | AES-128-CBC | PBKDF2(peanuts, 1 iter) | Yes — no secret needed |
Linux v11 | AES-128-CBC | PBKDF2(keyring secret, 1 iter) | No — need keyring + password |
| macOS | AES-128-CBC | PBKDF2(Keychain secret, 1003 iter) | No — need Keychain + password |
| Windows | AES-256-GCM | os_crypt key wrapped in DPAPI | v10: yes with masterkey + secret |