chrdmp is an offline profile decryption tool for the Google Chrome browser. It enables decryption of the Login Data file that contains user passwords and the Cookies file which contains the browser's cookies. This tool is specifically intended for use with systems that use a keyring to encrypt browser secrets (i.e., for all Linux distributions and NOT for Windows, which uses DPAPI).
Important features:
- Automatically detects and handles formatting quirks that change depending on the database version.
- Converts integer column values to text to make their meaning clear.
- Supports no-keyring setups that use the default secret.
To install, either:
- Linux: Download the latest binary from releases.
- Other: Clone the source code, install the required packages from
requirements.txt, and run the script using Python 3.
chrdmp is a CLI with two subcommands: logins and cookies.
Decrypt passwords
./chrdmp logins -d "Login Data" -o logins.csv -s <secret>
Decrypt cookies
./chrdmp cookies -d "Cookies" -o cookies.csv -s <secret>
Options (same for both commands)
| Flag | Default | Meaning |
|---|---|---|
-d, --db |
Login Data / Cookies |
Path to the input SQLite file (must exist) |
-s, --secret |
peanuts |
"Chrome Safe Storage" keyring secret. Leave default for no-keyring setups |
-o, --outfile |
— | Required. Output CSV path (overwrites if it exists) |
Getting the secret
The "Chrome Safe Storage" keyring secret is trivially obtainable from the target system using API calls run as the user (or possibly other offline/privesc techniques) and can be fed to the program at as a command-line argument. If no keyring was set on the target system, then a secret needn't be provided and the default value "peanuts" will be used.
Placeholders in the output
Some entries can't be decrypted to a normal value. In those cases the output shows a placeholder instead:
<EMPTY>: the entry had no value to decrypt (an empty blob).<FAILED: raw value>: decryption failed for that entry. The raw value is included.<SKIPPED: raw value>: the entry was skipped because it needs a non-default secret you didn't provide (see below). The raw value is included.
Skipped entries
Entries that use a randomly generated keyring secret can only be decrypted if you provide that secret. If you leave the secret at its default (peanuts), those entries are skipped instead of decrypted and marked as <SKIPPED: raw value>. The summary at the end counts them under "Skipped (no secret provided but uses non-default secret)". To decrypt them, run the tool again with the correct secret via -s.
Columns in the output CSV
logins.csvcontains only these columns (shown under friendlier names):origin_url(Origin URL),action_url(Action URL),username_value(Username),password_value(Password),date_created(Date Created),password_type(Password Type),times_used(Times Used),date_last_used(Date Last Used), anddate_password_modified(Date Modified). All other columns are omitted as they will likely be irrelevant in nearly every use case. If you require those columns, you can modify the source code to extract columns that might be relevant to your work using the decryption guide below.cookies.csvcontains every column from thecookiestable, with one exception: the emptyvaluecolumn is dropped, and the decryptedencrypted_valueis written in its place under the namevalue.
This code is distributed under the MIT License. Please note that this tool is meant for authorized use within the limits of the law and the developer(s) are not liable for any illegal or harmful use.
≻ ./chrdmp cookies -d data/Cookies -s "REDACTED" -o cookies.csv
Extracting cookies using:
Database: data/Cookies
Secret: REDACTED
Output File: cookies.csv
Loading database... 1630 entries loaded. DB version: 24
Summary:
Successfully decrypted: 1630 entries (0/1630 empty)
Skipped (no secret provided but uses non-default secret): 0
Decryption failures: 0
≻ ./chrdmp logins -d data/Login\ Data -s "REDACTED" -o psw.csv
Extracting passwords using:
Database: data/Login Data
Secret: REDACTED
Output File: psw.csv
Loading database... 125 entries loaded.
Summary:
Successfully decrypted: 125 entries (5/125 empty)
Skipped (no secret provided but uses non-default secret): 0
Decryption failures: 0
On Linux, Chrome protects sensitive files like cookies and passwords using the keyring. Any process run as the user can access the "Chrome Safe Storage" keyring secret, which can then be used to decrypt the files.
This guide walks you through decryption of the Login Data (usernames and passwords) and Cookies files. Obtaining the keyring secret is trivial and can be done through standard system API calls.
This decryption mechanism is common to both Cookies and Login Data, with the only difference being the tables/columns that are referenced in the process.
Before looking at those specific cases, it helps to understand the encryption algorithm in detail.
The Login Data and Cookies files are SQLite databases. The files are not encrypted and can be opened with an SQLite viewer. In fact, most of the fields do not contain encrypted values. However, the usernames, passwords and cookie values are encrypted using the keyring secret.
The secret is 16 random bytes stored in the keyring under the label "Chrome Safe Storage". If a keyring is not available on the system, Chrome uses a hardcoded value of "peanuts" (without the quotes) as the secret. If the blob starts with "v10", then the secret is "peanuts". If it starts with "v11", then it uses a randomly generated keyring secret.
The BLOBs are encrypted/decrypted using AES-128-CBC with a key generated using PBKDF2-HMAC-SHA1 with the keyring secret, a fixed salt "saltysalt" (no quotes), 1 iteration and key length 16.
More precisely,
KEY = PBKDF2-HMAC-SHA1(SECRET, "saltysalt", ITERATIONS=1, DKLEN=16)
To decrypt the blob, we run AES-128-CBC-DECRYPT with key=KEY and IV=" " * 16. The IV is always the same. Before decryption, the blob must be stripped of the version number prefix (v10 or v11). In pseudocode, we can represent this as:
PLAINTEXT = AES-128-CBC-DECRYPT(KEY, IV=" " * 16, DATA=blob[3:])
Note: In the pseudocode above the AES-DECRYPT function does both decryption and PKCS#7 unpadding.
Chrome stores saved login credentials in the logins table of the Login Data file. The table contains a lot of useful information about the saved passwords:
| Column | Type |
|---|---|
| origin_url | VARCHAR |
| action_url | VARCHAR |
| username_element | VARCHAR |
| username_value | VARCHAR |
| password_element | VARCHAR |
| password_value | BLOB |
| submit_element | VARCHAR |
| signon_realm | VARCHAR |
| date_created | INTEGER |
| blacklisted_by_user | INTEGER |
| scheme | INTEGER |
| password_type | INTEGER |
| times_used | INTEGER |
| form_data | BLOB |
| display_name | VARCHAR |
| icon_url | VARCHAR |
| federation_url | VARCHAR |
| skip_zero_click | INTEGER |
| generation_upload_status | INTEGER |
| possible_username_pairs | BLOB |
| id | INTEGER |
| date_last_used | INTEGER |
| moving_blocked_for | BLOB |
| date_password_modified | INTEGER |
| sender_email | VARCHAR |
| sender_name | VARCHAR |
| date_received | INTEGER |
| sharing_notification_displayed | INTEGER |
| keychain_identifier | BLOB |
| sender_profile_image_url | VARCHAR |
| date_last_filled | INTEGER |
| actor_login_approved | INTEGER |
password_value is the only encrypted field and can be decrypted using the method described above.
The password type field stores integers that map to the following:
| Value | Meaning |
|---|---|
| 0 | Form submission |
| 1 | Generated |
| 2 | Credential API |
| 3 | Manually added |
| 4 | Imported |
| 5 | Received via sharing |
| 6 | Imported via credential exchange |
| 7 | Change submission |
The date fields store the time in microseconds since 1601-01-01.
Other fields are self-explanatory and unencrypted.
The cookie data is stored in the cookies table in the Cookies file.
The table lists all the information Chrome stores about the cookies:
| Column | Type |
|---|---|
| creation_utc | INTEGER |
| host_key | TEXT |
| top_frame_site_key | TEXT |
| name | TEXT |
| value | TEXT |
| encrypted_value | BLOB |
| path | TEXT |
| expires_utc | INTEGER |
| is_secure | INTEGER |
| is_httponly | INTEGER |
| last_access_utc | INTEGER |
| has_expires | INTEGER |
| is_persistent | INTEGER |
| priority | INTEGER |
| samesite | INTEGER |
| source_scheme | INTEGER |
| source_port | INTEGER |
| last_update_utc | INTEGER |
| source_type | INTEGER |
| has_cross_site_ancestor | INTEGER |
encrypted_value is the only encrypted field and stores the value of the cookie.
The date fields store the time in microseconds since 1601-01-01.
It can be decrypted using the method described above. There is one caveat: on databases version 24 and up, the plaintext value is prefixed by a 32-byte metadata field.
I.e., to obtain the cookie value,
if db_version >= 24:
plaintext = plaintext[32:]
Several of the unencrypted integer columns are enums or boolean flags. They map to the following human-readable values:
| Column | Value | Meaning |
|---|---|---|
is_secure, is_httponly, is_persistent, has_expires, has_cross_site_ancestor |
0 | no |
| 1 | yes | |
priority |
0 | low |
| 1 | medium | |
| 2 | high | |
samesite |
-1 | unset |
| 0 | none | |
| 1 | lax | |
| 2 | strict | |
source_scheme |
0 | unset |
| 1 | http | |
| 2 | https | |
source_type |
0 | unknown |
| 1 | http header | |
| 2 | script | |
| 3 | other |