browserforensics
Back to all articles

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:

BytesMeaning
0..3version tag — ASCII v10 or v11
3..nAES-128-CBC ciphertext (PKCS7-padded plaintext)

The two tags map directly onto where the key comes from:

  • v10 — encrypted with the hard-coded peanuts key. 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:

ParameterValue
KDFPBKDF2-HMAC-SHA1
Saltsaltysalt (ASCII)
Iterations1
Derived key length16 bytes (AES-128)
CipherAES-128-CBC
IV16 bytes of 0x20 (ASCII spaces)
PaddingPKCS7

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

PlatformCipherKDF / key sourceOffline from files alone?
Linux v10AES-128-CBCPBKDF2(peanuts, 1 iter)Yes — no secret needed
Linux v11AES-128-CBCPBKDF2(keyring secret, 1 iter)No — need keyring + password
macOSAES-128-CBCPBKDF2(Keychain secret, 1003 iter)No — need Keychain + password
WindowsAES-256-GCMos_crypt key wrapped in DPAPIv10: yes with masterkey + secret

Further reading