GNOME keyring has a problem. I don't know why but there is absolutely
no utility or library for reading and writing from a keyring file.
You would think secret-tool
would be able to do that, but lmao no it
can't (it doesn't even have a way to list all the entries in the
keyring, you can only look up a specific one by filter). You would
think that libsecret
would do if you write some C code, but lmao no
that doesn't even work either (it doesn't actually know how to parse
the keyring file, it only knows how to make a dbus request). The only
thing that knows how to read and write keyring files directly is
apparently gnome-keyring-daemon
. So you would be stuck with setting
up some kind of isolated environment where you can run a second
gnome-keyring-daemon
pointed at the keyring file of interest, and
then talk to the daemon in that environment.
In principle you can inspect the files yourself, because in principle
all that's needed to decrypt them is your passphrase, but in practice
this page that could only with great generosity be called
"""documentation"""
is the only extant reference for what format those files are in and
what the cryptography is, aside from the source code of
gnome-keyring-daemon
itself.
This is all a giant mess, and I concluded somebody needed to do the public service of making a simple utility that can read and write these files from the command line in a no-nonsense way with zero dbus or environment dependencies.
You can use this project to:
- Access the data in keyring files that you have on a backup drive (not installed in your live session).
- Make changes to keyring file entries that aren't supported by
secret-tool
or the GNOME user interface. - Merge two keyring files together.
- Discover how the keyring files are formatted and encrypted since there is no documentation on it anywhere else.
This project is a single command-line script gnome_keyloop.py
written in Python. It has a single Python dependency,
cryptography. You can set
that up yourself or use the provided
Poetry configuration to set up a
suitable virtual environment.
The utility is a converter between two formats for the keyring data:
binary and JSON. The binary format is what is used on disk by GNOME,
it has the file extension .keyring
. The JSON format is something I
made up and nothing else uses it, but it is human-readable and
lossless. You can convert the binary format into the JSON format and
back, and get a byte-for-byte identical keyring (unless there's a bug
in my code).
The way that you make changes to your keyring (or merge two keyrings
together, etc) is by converting the keyring from binary to JSON,
making whatever changes you want to make to the JSON files (there are
many existing tools for this, see e.g. jq), and
finally converting the JSON back into the binary format. My
understanding is that after installing a new keyring file into
~/.local/share/keyrings
, you have to log out and back in again for
it to be used.
At present I do not bother to strictly validate JSON inputs, so while all JSON outputs are valid JSON inputs, if you make changes, you could trigger unexpected errors or generate broken keyring files.
Here are interesting notes about what you can change. When in doubt,
refer to the source code; I promise it's more readable than the
gnome-keyring-daemon
source that I had to reverse engineer.
- You can change the
salt
orhash_iterations
freely (salt must be exactly 8 bytes and represented in base64). The new values will be used for encryption when converting the keyring back to binary format. - There is support for sub-second resolution on all the timestamps in the keyring data format, but my system does not make use of this, so I have not tested what happens if a keyring actually has timestamps that do not fall on even seconds.
- The
acl
field is optional. Again my system is not using it, so while I implemented support for this, I don't know if it works. - All strings can support binary data, not just the
salt
that I encoded explicitly to base64 (since it is always binary). On my system none of the strings contained non-UTF-8 data, but it should work, I guess. - I would assume that if you are merging two keyring files then you
should adjust the
id
fields on each item so that there are no duplicates. - There is only support for version 0 of the keyring format with AES encryption and MD5 hash. This corresponds to a (0, 0, 0) header, and a keyring that does not conform will be rejected with an error. Are there other allowable values? Maybe I would know if there was any documentation.
- Every item and attribute has a
type
which is a 32-bit integer that is always zero on my system, so I omitted it from the JSON and had my converter throw an error if it is ever non-zero. I guess that could be adjusted if needed. The """documentation""" for the binary file format suggests that sometimes MD5 hashes might be represented as 32-bit integers instead of strings, with no explanation of why or when this would happen, so maybe that happens when there is a non-zerotype
.
Provide two positional arguments to the script: input file and output
file. The output file can be -
to print to stdout. The input file
can be either JSON or binary format. Files that start with {
are
assumed to be JSON, otherwise they are assumed to be binary. Yes, I
know that's rather silly, but it works well enough.
Set the environment variable KEYRING_PASSPHRASE
to the passphrase
that decrypts your keyring. If this variable is not set when
converting a binary keyring to JSON, then the resulting JSON will say
"decrypted": false
and will be missing some properties including the
actual secrets and attribute values for each item. Non-decrypted JSON
cannot be converted back into binary format because the data is not
all there. Also, the environment variable is mandatory when converting
back into binary format, since there is no way to construct the binary
format without using encryption (for which a passphrase is needed).
When I was searching for a tool to extract GNOME keyring entries from a backup drive and rescue my live system, I found a GitHub repo that said it did exactly what I needed, and even had full documentation about the process. Only, once I compiled and ran it, I found that no code was ever actually written, it was just a glorified "Hello, world". I will not be naming names, but the experience stuck with me.
So, just to prove that this thing actually is functional at least for some keyrings, I have included a sample GNOME keyring file with fake data, which can be converted from the binary format to JSON and back and verified that it is byte for byte identical.
Verify that JSON converts to the binary format as shown:
% rm test/example.keyring
% KEYRING_PASSPHRASE="$(< test/example.pass)" poetry run ./gnome_keyloop.py test/example.json test/example.keyring
% git status
Verify that binary converts to the JSON format as shown:
% rm test/example.json
% KEYRING_PASSPHRASE="$(< test/example.pass)" poetry run ./gnome_keyloop.py test/example.keyring test/example.json
% git status