browserforensics
Back to all articles

How Chromium encrypts cookies on Windows

2026-06-17 · 3 min

The Cookies database tells you which cookies existed and when. It does not, by itself, tell you their values — on Windows, Chromium has stored the encrypted_value column as ciphertext since v80. Recovering the plaintext is a two-key problem: a per-profile AES key that lives in Local State, wrapped by a Windows-user key you have to unwrap with DPAPI. This post covers the first half — the formats. The offline DPAPI chain covers the second.

The key lives in Local State

Chromium generates one AES-256 key per profile and stores it, base64-encoded, in the profile's parent Local State file (JSON):

%LOCALAPPDATA%\Google\Chrome\User Data\Local State
%LOCALAPPDATA%\Microsoft\Edge\User Data\Local State
{
  "os_crypt": {
    "encrypted_key": "RFBBUEkBAAAA0Iyd3wEV0RGM...",
    "app_bound_encrypted_key": "QVBQQgEAAADQ..."
  }
}

Base64-decode encrypted_key and the first five bytes are the ASCII tag DPAPI. Strip it and what remains is a DPAPI blob — the AES key encrypted under the user's Windows credentials. You cannot read the key out of Local State directly; it has to be unwrapped first.

The cookie value format

Each encrypted_value is a self-describing blob. The modern layout is:

BytesMeaning
0..3version tag — ASCII v10 or v20
3..1512-byte AES-GCM nonce
15..n-16ciphertext
n-16..n16-byte GCM authentication tag

So decryption is AES-256-GCM with the os_crypt key, the embedded nonce, and the trailing tag. The on-disk layout (ciphertext followed by tag) is exactly what WebCrypto's decrypt expects, which is why the in-browser decryptor can do it with no extra reassembly.

Two version tags matter:

  • v10 — the classic scheme. The key from Local State decrypts the value directly. Plaintext is the cookie value as-is.
  • v20 — app-bound encryption (Chrome 127+). The decrypted plaintext is prefixed with a 32-byte SHA-256 domain hash that you strip to get the value. v20 values are encrypted with the app-bound key (app_bound_encrypted_key), not the regular encrypted_key.

Values written before v80 have no version tag — they start with the DPAPI magic 01 00 00 00 and were encrypted per-value with CryptProtectData. Those need the same DPAPI unwrap, applied to each value rather than to a shared key.

What you can decrypt offline

SchemeKey sourceOffline from files?
v10encrypted_key (DPAPI-wrapped)Yes — with the masterkey + user secret
Pre-v80 per-valueDPAPI per valueYes — same inputs, per value
v20 (app-bound)app_bound_encrypted_keyPartly — see below

For v10, everything is recoverable from acquired files: the Local State key, the user's DPAPI masterkey, and the user's NT hash or password. That is the common case and the one the tool automates.

v20 adds a second layer. The app-bound key is wrapped with system DPAPI and then run through an app-bound service that ties it to the machine. The system-DPAPI layer is recoverable offline (the machine key comes out of the SECURITY hive), but the app-bound step is not pure file-based crypto on current builds. If you already hold the unwrapped v20 key, decrypting the values is straightforward GCM; deriving that key from files alone is the part that does not fully reduce to offline math yet.

Other platforms, briefly

This is a Windows story. On macOS the os_crypt key lives in the login Keychain (Chrome Safe Storage), unlocked with the user password. On Linux it is either a hard-coded peanuts key or a secret in GNOME Keyring / KWallet. Firefox does not encrypt cookie values at all, and Safari binarycookies are plaintext — see browser cookies in forensics.

Further reading