Keyring Format
The serialisation and load/save logic described here is implemented in
crates/keystore-rpg(backed byrpglib::key_material). For the trait-based multi-backend architecture see KeyStore Architecture.
rustpg stores all key material in a single file: ~/.config/rustpg/key_material.rpg. The file is AES-256-GCM encrypted using the same RPGS wire format as any other symmetric-encrypted file — open it with rpg --decrypt if you need to inspect it. The plaintext inside is a human-readable text format described below.
File locations
| File | Purpose |
|---|---|
~/.config/rustpg/key_material.rpg |
Primary store — always AES-256-GCM encrypted |
~/.config/rustpg/key_material.txt |
Legacy plaintext fallback — loaded on first run if .rpg is absent |
Both files are written with permissions 0600 (owner read/write only) on Unix. Writes are atomic: the new content is written to a .tmp file and renamed over the target.
Outer encryption
key_material.rpg is a standard RPGS ciphertext. The master password is the Argon2id passphrase; the KDF parameters (memory cost, iterations, parallelism) and the 16-byte salt are stored in the RPGS header as with any other encrypted file. See Wire Formats for the byte layout.
The master password is optionally cached in the OS keyring (GNOME Keyring on Linux, Credential Manager on Windows) under the service name rustpg, key passwd_keyring. If the OS keyring is unavailable the app prompts on each launch.
Plaintext wire format (format-version 1)
The decrypted content of key_material.rpg — or the contents of key_material.txt — is UTF-8 text with the following structure:
rustpg-keyring
format-version 1
identity <slug>
role <role>
name <display name>
email <address> ← optional
keypair <algo>:<bits>:v<version>
-----BEGIN <ALGO> PUBLIC KEY-----
<base64>
-----END <ALGO> PUBLIC KEY-----
-----BEGIN <ALGO> PRIVATE KEY-----
<base64>
-----END <ALGO> PRIVATE KEY-----
-----BEGIN RUSTPG REVOCATION----- ← optional
<base64>
-----END RUSTPG REVOCATION-----
pubkey <algo>:<bits>:v<version>
-----BEGIN <ALGO> PUBLIC KEY-----
<base64>
-----END <ALGO> PUBLIC KEY-----
-----BEGIN RUSTPG REVOCATION----- ← optional
<base64>
-----END RUSTPG REVOCATION-----
identity <next-slug>
...
Rules:
- Blank lines and lines beginning with
#are ignored. - The file header (
rustpg-keyring/format-version) ends at the firstidentityline. - Unknown header fields are skipped — forwards-compatible for new header additions.
- Each
identityblock runs until the nextidentityline or EOF. - A
keypairblock is consumed until the first blank line after all PEM blocks are closed. - Field order within an identity block is fixed: identity → role → name → email → key blocks.
Identity slug
The slug is the lookup key for all operations — it is used in CLI flags (--my-id, --recipient), settings (last_recipient_id), and internal references. It must consist entirely of [a-zA-Z0-9_-] characters. The parser rejects any slug containing other characters.
Roles
| Role string | Rust variant | Meaning |
|---|---|---|
self |
Role::Own |
Active own identity — sign, decrypt, encrypt to self |
self-archived |
Role::OwnArchived |
Rotated key — decrypt old files only |
contact |
Role::Contact |
External party — encrypt-to, verify signatures |
contact-archived |
Role::ContactArchived |
Retired contact — verify old signatures only |
Only self and self-archived identities may hold a keypair block (private key material). A contact or contact-archived identity with a keypair block is a parse error.
When exporting a public identity (Export Public Identity... / --export), own-role identities are downgraded: self → contact, self-archived → contact-archived. The recipient sees a contact entry without private key material.
Key blocks
keypair — both halves
Used for own identities that can sign or decrypt. The algo tag has the form <algo>:<bits>:v<version>:
| Currently used | Algo | Bits | Version |
|---|---|---|---|
| Encryption | x25519 |
256 |
v1 |
| Signing | ed25519 |
256 |
v1 |
PEM labels are custom (not OpenSSL-standard):
-----BEGIN X25519 PUBLIC KEY----- / -----BEGIN X25519 PRIVATE KEY-----
-----BEGIN ED25519 PUBLIC KEY----- / -----BEGIN ED25519 PRIVATE KEY-----
The PEM body is standard base64 of the raw 32-byte key. pem_encode() / pem_decode() are defined in crates/keystore/src/lib.rs and re-exported from rpglib::key_material and rpglib::app::keys — never read or write raw key bytes directly.
pubkey — public only
Used for contact identities, or when an own-role identity's private key is intentionally omitted (export path). Same algo tag format; only a public PEM block follows.
Revocation certificates
A revocation certificate is an Ed25519 signature over the literal bytes REVOKE, PEM-wrapped under the label RUSTPG REVOCATION:
-----BEGIN RUSTPG REVOCATION-----
<base64-encoded 64-byte Ed25519 signature over b"REVOKE">
-----END RUSTPG REVOCATION-----
The certificate is stored inline on the keypair or pubkey block of the Ed25519 key. Revocation is checked at runtime by verifying the cert against the stored Ed25519 public key — no timestamp, no expiry, no external trust anchor. An identity is revoked if and only if verify(pubkey, cert, "REVOKE") returns true.
Revocation is one-way: there is no un-revoke operation. Matching an incoming cert to a keyring identity is done by verifying the cert against every Ed25519 key in the keyring and returning the first match (find_revoke_match()).
Fingerprint
The fingerprint is SHA-256 of the raw Ed25519 public key bytes (32 bytes), displayed as 8 groups of 8 uppercase hex characters:
AABBCCDD EEFF0011 22334455 66778899 AABBCCDD EEFF0011 22334455 66778899
It is computed on demand from the stored PEM — not stored in the keyring file.
Import and export
| Operation | What is transferred |
|---|---|
| Export Public Identity | rustpg-keyring header + one identity block, public keys only, role downgraded to contact |
| Export Full Identity | Same but keypair blocks with private keys, role preserved |
| Import Identity | Parsed as a full keyring file; all identities in the file are merged. Slug conflicts are refused — delete the existing identity first. |
| Import Revocation Cert | PEM file verified against every identity; applied to the matching slug. |
| Backup Keyring | Full encrypted key_material.rpg copy to an arbitrary path. |
| Restore Keyring | Decrypts and replaces the live keyring entirely. |
OS keyring integration
Two passwords are cached in the OS keyring under the service name rustpg:
| Key | Contents |
|---|---|
passwd_keyring |
Master password protecting key_material.rpg |
passwd_symmetric |
Last-used symmetric passphrase (pre-fills encrypt/decrypt dialogs) |
Cache failures are silent — a missing daemon or permission error never prevents the app from starting. On Linux, GNOME Keyring requires the keyring crate compiled with features sync-secret-service and crypto-rust; without these it falls back to an in-memory mock that does not persist across sessions.