Chromium: Linux, keyrings && Secret Service, passwords encryption and store
Tags: security, linux, todayilearned
Published: 2019-12-10 10:03:24

---
title: Chromium: Linux, keyrings && Secret Service, passwords encryption and store
published: true
date: 2019-12-10 09:45:48 UTC
tags: security,explainlikeimfive,linux,todayilearned
canonical_url: https://rtfm.co.ua/en/chromium-linux-keyrings-secret-service-passwords-encryption-and-store/
---

![](https://rtfm.co.ua/wp-content/uploads/2018/09/130px-Chromium_11_Logo.png)One of the motives to go deeper into the keyrings (see the [What is: Linux keyring, gnome-keyring, Secret Service, and D-Bus post](https://rtfm.co.ua/en/what-is-linux-keyring-gnome-keyring-secret-service-and-d-bus/)) was the fact that Chromium, surprise-surprise, keep passwords unencrypted if a Linux system has no keyring and/or Secret Service enabled.

So, let’s try to find how and where Chromium store passwords, and the main question – are they stored encrypted, or no?

### Chromium and keyring

The [documentation](https://chromium.googlesource.com/chromium/src/+/master/docs/security/faq.md#Does-the-Password-Manager-store-my-passwords-encrypted-on-disk) states that:

> On Linux, Chrome previously stored credentials directly in the user‘s Gnome Keyring or KWallet, but for technical reasons, it has switched to storing the credentials in “Login Data” in the Chrome user’s profile directory, but encrypted on disk with a key that is then stored in the user’s Gnome Keyring or KWallet. If there is no available Keyring or KWallet, the data is not encrypted when stored.

I.e.:

1. Chromium stores passwords in a local SQLite database called “Login Data” (`'/home/setevoy/.config/chromium/Default/Login Data'`)
2. if `gnome-keyring` or KWallet are installed then Chromium will encrypt passwords in its database using a generated password which will be stored in such a keyring
3. in other case and if Secret Service is not active at all – passwords will be kept “as is”, plain text

Well – let’s check it.

#### Chromium without a keyring

At first, let’s try to obtain a password from an SQLite when a system has no keyring configured.

To specify which backend to use we can add the `google-chrome --password-store=basic` option to store passwords as a “plain text”.

Also, check for other parameters – _gnome_ or _kwallet,_ see the `man chromium`:

```
--password-store=<basic|gnome|kwallet>
Set the password store to use.  The default is to  automatically detect  based  on  the  desktop  environment.  basic selects the built in,  unencrypted  password  store.   gnome  selects  Gnome keyring.  kwallet selects (KDE) KWallet.  (Note that KWallet may not work reliably outside KDE.)
```

Besides that, let’s check D-Bus services to make sure we have no Secret Service enabled:

```
$ qdbus --session org.freedesktop.DBus / org.freedesktop.DBus.GetConnectionUnixProcessID org.freedesktop.secrets
Error: org.freedesktop.DBus.Error.NameHasNoOwner
Could not get PID of name 'org.freedesktop.secrets': no such name
```

Okay.

Now, create a new directory for Chromium’s data:

```
$ mkdir /tmp/data-chrome-test-1
```

Run the browser with this directory as its home and with the –password-store=basic option to keep passwords “unencrypted”:

```
$ chromium --user-data-dir=/tmp/data-chrome-test-1 --password-store=basic
```

Now, log in to some website, close the browser, and check its database:

```
$ sqlite3 /tmp/data-chrome-test-1/Default/Login\ Data 'select username_value, password_value from logins;'
testuser|v10�#�c�yF�b�f���
```

Cool – the `password_value` field of the SQLite database now ahs some password stored.

By the way, you can get a table’s scheme by using the `.schema` command:

```
$ sqlite3 ~/.config/chromium/Default/Login\ Data '.schema logins'

CREATE TABLE IF NOT EXISTS "logins" (origin_url VARCHAR NOT NULL, action_url VARCHAR, \
username_element VARCHAR, username_value VARCHAR, password_element VARCHAR, password_value BLOB \
...));
CREATE INDEX logins_signon ON logins (signon_realm);
```

#### Chromium with a keyring

Now, let’s go further and let’s try to obtain a password from a Chromium’s SQLite database when a keyring is enabled. For this, [enable Secret Service support in the KeePass](https://rtfm.co.ua/en/what-is-linux-keyring-gnome-keyring-secret-service-and-d-bus/#KeePass), which we will use instead of the “standard”  `gnome-keyring`, and create a new directory for Chromium’s data:

```
$ mkdir /tmp/data-chrome-test-2
```

Run browser again, but this time specify `--password-store=gnome`, and change the `user-data-dir` to use a new, clear SQLite database:

```
$ chromium --user-data-dir=/tmp/data-chrome-test-2 --password-store=gnome
```

Log in somewhere again, save a password, check the database:

```
$ sqlite3 /tmp/data-chrome-test-2/Default/Login\ Data 'select username_value, password_value from logins;'
testuser|v11���H!@���2�jA�
```

Aha!

At-first – the prefix here is _v11_, instead of the _v10_, as it was in the password from the “unencrypted” database, at second – the string itself is longer.

So – passwords are stored in different ways. But – what about their encryption here?

### Chromium passwords decrypt

Let’s try to figure out the following:

1. first – we got the password from the “unencrypted” storage not as plain text – why so?
2. second – how can we get a readable password from the “encrypted” storage?

To see this – let’s go to the Chromium’s [source code](https://cs.chromium.org/chromium/src/components/os_crypt/).

#### Chrome v10 vs v11

First – what are those v10 and v11 prefixes?

Go to the `os_crypt_linux.cc` file content – [https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=38](https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=38):

```
...// Password version. V10 means that the hardcoded password will be used.// V11 means that a password is/will be stored using an OS-level library (e.g// Libsecret). V11 will not be used if such a library is not available.// Used for array indexing.enum Version { V10 = 0, V11 = 1,};...
```

Aha, so _v10_ is a prefix for unencrypted passwords, while _v11_ – for encrypted.

Although in both cases we got our passwords as some weird symbols string, not a password in a clear way.

Will see now why so.

Start from the end of the file, from the `DecryptString()` function:

1. `DecryptString()`: stores a version in the `version` variable (_v10_ или _v11_):
```
...
  if (base::StartsWith(ciphertext, kObfuscationPrefix[Version::V10],
                       base::CompareCase::SENSITIVE)) {
    version = Version::V10;
...
```

[https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=197](https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=197)

2. `DecryptString()` trying to get an encryption key by calling the `GetEncryptionKey()` function passing the version
```
...
  std::unique_ptr<crypto::SymmetricKey> encryption_key(
      GetEncryptionKey(version));
  if (!encryption_key) {
    VLOG(1) << "Decryption failed: could not get the key";
    return false;
  }
...
```

[https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?gsn=GetEncryptionKey&l=211](https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?gsn=GetEncryptionKey&l=211)``

3. `GetEncryptionKey()` in its turn calls the `g_get_password()` function:
```
...
  std::string* password = g_get_password[version]();
  if (!password)
    return nullptr;
...
```

[https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?gsn=GetEncryptionKey&l=119](https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?gsn=GetEncryptionKey&l=119)

4. а `g_get_password()` is a pointer to the `GetPasswordV10()` and `GetPasswordV11()` functions:
```
...
std::string* (*g_get_password[])() = {
    &GetPasswordV10, &GetPasswordV11,
};
...
```

[https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=109](https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=109)

5. and inside of the `GetPasswordV10()`… We are seeing… What??? Password?!?
```
...
  if (!g_cache.Get().password_v10_cache.get()) {
    g_cache.Get().password_v10_cache.reset(new std::string("peanuts"));
  }
...
```

[https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=86](https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_linux.cc?l=86)

So, it was much easier:

1. if a keyring service is present – then Chromium will generate a password, will store it in this keyring
2. if no keyring present and/or Secret Service is not enabled – then Chromium will use its hardcoded master-password “_peanuts_“

And later using this password – will be constructed a master-key to be used to encrypt passwords in the SQLite database.

#### Python script to obtain Chromium’s passwords

Now, let’s check all of this in a practice, to see is it works as we suggested from the code above.

Among other things, we can see that here is AES 128 bit CBC encryption used:

```
...// Key size required for 128 bit AES.const size_t kDerivedKeySizeInBits = 128;...
```

Next, in the `GetEncryptionKey()` we can see how the encryption key is constructed:

```
...
  // Create an encryption key from our password and salt.
  std::unique_ptr<crypto::SymmetricKey> encryption_key(
      crypto::SymmetricKey::DeriveKeyFromPasswordUsingPbkdf2(
          crypto::SymmetricKey::AES, *password, salt, kEncryptionIterations,
          kDerivedKeySizeInBits));
  DCHECK(encryption_key);
  return encryption_key;
...
```

Thus, to decrypt a record from the database we need in the following data, which is set in the file’s const variables:

1. мастер-пароль password – именно он возвращается в `GetPasswordV10()` или `GetPasswordV11()`
2. соль – `const char kSalt[] = "saltysalt"`
3. итерации – `const size_t kEncryptionIterations = 1`
4. длина ключа – `const size_t kDerivedKeySizeInBits = 128`

To decrypt passwords – googled such a simple script [here>>>](https://stackoverflow.com/a/23727331/2720802), after a bit updating it for self-usage I  got the next code:

```
#! /usr/bin/env python3                                                                                                                                                                                                                       
import sqlite3
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
def get_encrypted_data(db_path):
    # choose a database
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    # connect and egt exncypted data
    data = cursor.execute('SELECT action_url, username_value, password_value FROM logins')
    
    return data
# to get rid of padding
def clean(x): 
    return x[:-x[-1]].decode('utf8')
def get_decrypted_data(encrypted_password):
    print("Decrypting the string: {}".format(encrypted_password))
    # trim off the 'v10' that Chrome/ium prepends
    encrypted_password = encrypted_password[3:]
    # making the key
    salt = b'saltysalt'
    iv = b' ' * 16
    length = 16
    iterations = 1
    pb_pass = "peanuts".encode('utf8')
    key = PBKDF2(pb_pass, salt, length, iterations)
    cipher = AES.new(key, AES.MODE_CBC, IV=iv)
    
    decrypted = cipher.decrypt(encrypted_password)
    print(clean(decrypted))
if __name__ == "__main__":
    
    db_path = '/tmp/data-chrome-test-1/Default/Login Data'
    for url, user, encrypted_password in get_encrypted_data(db_path):
        get_decrypted_data(encrypted_password)
```

Run it:

```
$ ./get_chrome\_pass.py
Decrypting the string: b'v10\xd4#\xd6c\xabyF\xc6b\xdef\x06\xce\x14\xe3\xc5'
test911911
```

Great – we got our password from the unencrypted database.

### Chromium && Secret Service

Now, enable Secret Storage support in the KeePass (see the [What is: Linux keyring, gnome-keyring, Secret Service, и D-Bus](https://rtfm.co.ua/what-is-linux-keyring-gnome-keyring-secret-service-i-d-bus/#KeePass)) to emulate an installed `gnome-keepass` and restart KeePass.

Add a new directory for the Chromium’s data:

```
$ mkdir /tmp/data-chrome-test-2/
```

Run Chromium with the _data-chrome-test-2_ data-directory and specify the `--password-store=gnome` option:

```
$ chromium --user-data-dir=/tmp/data-chrome-test-2/ --password-store=gnome
```

Log in somewhere, save password again, and now we can observe that Chromium created two new records in the KeePass database:

1. _Chrome Safe Storage Control_
2. _Chromium Safe Storage_

Check their attributes for more information about each:

[![](https://rtfm.co.ua/wp-content/uploads/2019/12/Screenshot_20191209_164136.png)](https://rtfm.co.ua/wp-content/uploads/2019/12/Screenshot_20191209_164136.png)

Also, you can check the D-Bus and Secret Service to see which collection is used now:

```
$ secret-tool search Title 'Chromium Safe Storage'
[/org/freedesktop/secrets/collection/Main/f0fdc4706ef44958b716e28c13d66bed]
label = Chromium Safe Storage
secret = P5pUwxbWaIBBVU0+LATOcw==
...
schema = chrome_libsecret_os_crypt_password_v2
...
attribute.Title = Chromium Safe Storage
...
attribute.application = chromium
...
attribute.Path = /Chromium Safe Storage
```

Now, let’s try to use the password from the _secret_ attribute from the _Chromium Safe Storage_ entry as the `pb_pass` variable’s value in our script.

Exit from the Chromium to unlock the database, otherwise, you’ll see the following error:

```
$ ./get_chrome_pass.py
Traceback (most recent call last):
File "./get_chrome_pass.py", line 50, in <module>
for url, user, encrypted_password in get_encrypted_data(db_path):
File "./get_chrome_pass.py", line 15, in get_encrypted_data
data = cursor.execute('SELECT action_url, username_value, password_value FROM logins')
sqlite3.OperationalError: database is locked
```

Update the script – a database’s path:

```
...
if __name__ == "__main__":
#   db_path = '/tmp/data-chrome-test-1/Default/Login Data'
    db_path = '/tmp/data-chrome-test-2/Default/Login Data'
    for url, user, encrypted_password in get_encrypted_data(db_path):
        get_decrypted_data(encrypted_password
```

And the password – instead of the “_peanuts_” in the `pb_pass` set the value taken from the KeePass’s _Chromium Safe Storage_ entry:

```
...
#    pb_pass = "peanuts".encode('utf8')                                                                                                                                                                                                       
    pb_pass = "P5pUwxbWaIBBVU0+LATOcw==".encode('utf8')
...
```

Try it:

```
$ ./get_chrome_pass.py
Decrypting the string: b'v11\xfc\x82\xf7H\n!@\x86\xb7\x982\xa8\x1fjA\xfd'
test911911
```

Cool! We got our decrypted password.

### Summary

Actually, the main thing I tried to figure out: password in the Chromium’s SQLite database are always stored in an encrypted way, not just a plain-text.

Just for the systems with no keyring service like `gnome-keyring` with the Secret Service enabled – the password used for the encryption will be the same overall Chromium’s user, which is obviously can not be assumed as a secure approach.

### Similar posts
 
- <small>12/01/2019</small> [Linux: the Nextcloud client, qtkeychain and the “The name org.freedesktop.secrets was not provided by any .service files” error](https://rtfm.co.ua/en/linux-the-nextcloud-client-qtkeychain-and-the-the-name-org-freedesktop-secrets-was-not-provided-by-any-service-files-error/)  
- <small>12/07/2019</small> [What is: Linux keyring, gnome-keyring, Secret Service, and D-Bus](https://rtfm.co.ua/en/what-is-linux-keyring-gnome-keyring-secret-service-and-d-bus/)