browserforensics
Back to all articles

Decrypting Chrome cookies on macOS (Safe Storage)

2026-06-21 · 4 min

macOS Chromium does not use DPAPI, and it does not use AES-GCM either. Cookie values carry the same v10 tag you see on Windows, but the crypto underneath is older and simpler: AES-128-CBC, keyed by a Keychain password run through PBKDF2. If you have that one password, the rest is deterministic. This post covers the format, the key derivation, and the honest limit of doing it from acquired files.

Where the value lives

The cookie store is the usual SQLite database, just under Network/:

~/Library/Application Support/Google/Chrome/<profile>/Network/Cookies

<profile> is Default, Profile 1, and so on. Edge sits at ~/Library/Application Support/Microsoft Edge/<profile>/Network/Cookies. The cookies table and its encrypted_value column are identical to the Windows layout — see Chromium cookie encryption on Windows for the schema. What differs is everything about the ciphertext.

The value format

Each encrypted_value is a tagged blob. On macOS the version that matters is v10:

BytesMeaning
0..3version tag — ASCII v10
3..nAES-128-CBC ciphertext

There is no embedded nonce and no authentication tag — CBC carries neither. The IV is fixed (see below), so the whole blob after the tag is pure ciphertext. Strip v10, decrypt, remove PKCS7 padding, and you have the cookie value.

Key derivation

The key is derived from a single secret: the Chrome Safe Storage password held in the login Keychain. The parameters are hard-coded in Chromium:

ParameterValue
KDFPBKDF2-HMAC-SHA1
Passwordthe Safe Storage secret
Saltsaltysalt (ASCII)
Iterations1003
Key length16 bytes (AES-128)
IV16 ASCII spaces (0x20 × 16)
CipherAES-128-CBC, PKCS7 padding

The iteration count is the trap. On Linux the same Chromium code path uses 1 iteration; on macOS it is 1003. Wire up a Linux-style derivation against a Mac profile and every decrypt fails with a padding error. The salt, IV, and cipher are otherwise shared between the two platforms.

Getting the Safe Storage password

The secret is not in the profile. It is a generic Keychain item that Chromium created on first run and unlocks with the login session. On the live, unlocked Mac:

security find-generic-password -ws 'Chrome Safe Storage'

For Edge the item is named differently:

security find-generic-password -ws 'Microsoft Edge Safe Storage'

The -w flag prints only the password; -s selects the service name. That one string is everything you need to derive the AES key for every v10 value in the profile.

What the tool does

BrowserForensics has a macOS (Safe Storage) method. You give it the Cookies database and paste the Safe Storage password; it runs PBKDF2 (1003 iterations) and AES-128-CBC entirely client-side, then strips PKCS7 padding and shows the plaintext. Nothing leaves the browser — same model as the in-browser decryptor for the Windows path.

Honest limit: parsing the raw login .keychain / .keychain-db file fully offline — unlocking it from the user password and pulling the Safe Storage item out of it — is out of scope. You supply the password yourself, which means one security command on the live machine (or an otherwise-recovered secret). Once you have the string, decryption is pure math and needs no Mac.

How the three platforms compare

PlatformTagCipherKey sourceIterations
Windowsv10 / v20AES-256-GCMos_crypt key, DPAPI-wrappedn/a
macOSv10AES-128-CBCSafe Storage (Keychain)1003
Linuxv10AES-128-CBCSafe Storage or peanuts1

macOS and Linux share the AES-128-CBC scheme and the saltysalt salt; only the iteration count and the secret source differ. Windows is the odd one out with GCM and a DPAPI-wrapped per-profile key. On Linux the fallback secret when no keyring is present is the literal string peanuts — see decrypting Chrome cookies on Linux.

Pitfalls

  • Wrong iteration count is the number-one failure. 1003, not 1, not 1000.
  • A bad password produces a padding error, not garbage — Chromium's PKCS7 check fails cleanly. Treat padding failures as "wrong secret" before suspecting the data.
  • Older Mac values may predate the v10 tag and sit in cleartext; do not try to decrypt an untagged value.
  • The IV is sixteen spaces, not sixteen zero bytes. Easy to get wrong when porting code from a different platform.

Further reading