age-plugin-keystore
An age plugin that stores X25519 or Hybrid
Post-Quantum private keys in Linux Keyrings using the Secret Service D-Bus API.
Overview
age-plugin-keystore integrates age encryption with Linux keyrings that uses
freedesktop Secret Service D-Bus
API (like the
GNOME Keyring), allowing you to:
- Generate X25519 key pairs with private keys stored securely in keyring
- Generate mlkem768x25519 Hybrid Post-Quantum key with private keys stored
securely in keyring
- Encrypt files to keystore recipients
- Decrypt files using keys retrieved from the keyring automatically
Rationale
Ideally, encryption and decryption operations should be performed within a
secure enclave, an isolated hardware-protected environment that shields
cryptographic operations from the rest of the system. However, Linux user
keyrings provide a reasonable alternative for lighter security requirements,
offering kernel-managed key storage without the need for expensive hardware
security modules or physical tokens.
The keyring offers several advantages over storing keys directly on disk. Keys
stored in the keyring are tied to user authentication, they become accessible
only after login. Unlike plaintext files that persist indefinitely and may be
inadvertently copied through backups or synchronization, keyring-stored keys
exist only in kernel memory and never touch the filesystem in unencrypted form.
This protection model emphasizes defense against the remote threat scenario: an
attacker who gains access to your repository (through a server breach, backup
leak, or misconfigured permissions) will find only encrypted data they cannot
decrypt. The keys remain in the keyring on your local machine, separate from the
encrypted content.
It is important to understand the limitations of this approach. A malicious
process running under your user account on the same machine can potentially
access the keyring and decrypt your secrets. While a true hardware secure
enclave offers stronger protection against such local attacks, the added
complexity and cost make it impractical for many use cases. The Linux keyring
strikes a pragmatic balance as it defends well against remote threats and casual
local snooping, while accepting that a fully compromised local environment
remains difficult to protect against without specialized hardware and systems.
Prerequisites
- Go 1.22 or later
- GNOME Keyring or another Secret Service API implementation
Installation
go install github.com/arouene/age-plugin-keystore@latest
Or build from source:
git clone https://github.com/arouene/age-plugin-keystore
cd age-plugin-keystore
go build -o age-plugin-keystore .
Make sure the binary is in your PATH for age to find it automatically:
cp age-plugin-keystore ~/.local/bin/
# or
sudo cp age-plugin-keystore /usr/local/bin/
Usage
Generate a New Key
age-plugin-keystore -g
This will:
- Generate a new X25519 key pair
- Generate a random 8-byte key ID
- Store the private key associated to the key ID in a Keyring using the secret
service API
- Print the identity string (for identity files) and public key (recipient with
embedded key ID)
Example output:
# key ID: a1b2c3d4e5f6g7h8
age1...
AGE-PLUGIN-KEYSTORE-1...
This generate a separate identity, it means that the recipient is a generic
X25519 public key, and can be used without this plugin installed, useful if you
want to share it. But the decryption needs this plugin to be installed.
Save the identity string to a file:
age-plugin-keystore -g > identity.txt 2> recipient.txt
For Hybrid Post-Quantum keys, use the -pq option
age-plugin-keystore -g -pq >> identity.txt 2>> recipient.txt
Encrypt a File
Use the public key (recipient) printed during key generation:
age -r age1... plaintext.txt > encrypted.age
# or
age -R recipient.txt plaintext.txt > encrypted.age
Decrypt a File
Use the identity file created during key generation:
age -d -i identity.txt encrypted.age > plaintext.txt
The plugin will automatically retrieve the private key from the secret service.
List Stored Keys
List all stored keys in your keystore.
age-plugin-keystore -l
Delete a Key
This will delete the stored key in your keystore.
age-plugin-keystore -d YOUR_KEY_ID
Add an existing Key to as Keystore
For backup / restore purpose, you can pass an age key to the stdin with option
-i, the plugin will store the key into the keystore.
echo -n 'AGE-SECRET-KEY-1...' | age-plugin-keystore -i
Security Considerations
- Private Key Storage: Private keys are stored in a Keyring, implementation
varies but generally the private key is encrypted at rest
- Key Access: Keys are accessible to any process running as the same user
when the keyring is unlocked
- Keyring Unlocking: The keyring is typically unlocked automatically when
you log in
- No Passphrases: Unlike standard age keys, keystore keys are protected by
the keyring's authentication, not individual passphrases
age1<bech32-encoded-data>
Where the encoded data contains: X25519-public-key
AGE-PLUGIN-KEYSTORE-1<bech32-encoded-data>
Where the encoded data contains: key-ID
In the keystore is stored the standard private key, identified by the key ID:
AGE-SECRET-KEY-1<bech32-encoded-data>
Development
Building
go build -v ./...
Testing
go test -v ./...
Integration tests
go test -tags=integration -v ./test/
Project Structure
age-plugin-keystore/
├── main.go # Plugin entry point
├── go.mod # Go module definition
├── README.md # This file
├── LICENSE.md # MIT License
└── internal/
└── keystore/ # GNOME Keyring integration
├── keystore.go
└── keystore_test.go
Dependencies
- Go standard library
filippo.io/age - age encryption library and plugin framework
github.com/godbus/dbus/v5 - D-Bus bindings for native Secret Service communication
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.