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:
| Bytes | Meaning |
|---|---|
0..3 | version tag — ASCII v10 |
3..n | AES-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:
| Parameter | Value |
|---|---|
| KDF | PBKDF2-HMAC-SHA1 |
| Password | the Safe Storage secret |
| Salt | saltysalt (ASCII) |
| Iterations | 1003 |
| Key length | 16 bytes (AES-128) |
| IV | 16 ASCII spaces (0x20 × 16) |
| Cipher | AES-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
| Platform | Tag | Cipher | Key source | Iterations |
|---|---|---|---|---|
| Windows | v10 / v20 | AES-256-GCM | os_crypt key, DPAPI-wrapped | n/a |
| macOS | v10 | AES-128-CBC | Safe Storage (Keychain) | 1003 |
| Linux | v10 | AES-128-CBC | Safe Storage or peanuts | 1 |
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
v10tag 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.