add: guide on ssh-tresor for encrypting secrets with SSH agent
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
This commit is contained in:
parent
a784869051
commit
d779a54e63
1 changed files with 166 additions and 0 deletions
166
content/2026-01-21-ssh-tresor.md
Normal file
166
content/2026-01-21-ssh-tresor.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# ssh-tresor: Encrypting Secrets with Nothing but Your SSH Agent
|
||||
|
||||
*DISCLAIMER: This article and most of the code was written with claude code. Use at your own risk.*
|
||||
|
||||
## The Problem
|
||||
|
||||
If you work on remote servers, you've probably faced this dilemma: you need to store secrets—API keys, database
|
||||
passwords, access tokens—but your options are limited.
|
||||
|
||||
On a remote machine accessed via SSH, you typically have:
|
||||
|
||||
- A shell
|
||||
- Your forwarded SSH agent (`ssh -A`)
|
||||
- Nothing else
|
||||
|
||||
No password manager. No hardware token plugged in. No GPG key (and even if you had one, GPG agent forwarding is
|
||||
notoriously painful). Just you and your SSH keys.
|
||||
|
||||
### The Usual "Solutions"
|
||||
|
||||
**Plain text files with restrictive permissions**: The classic `chmod 600 secrets.txt`. Quick, easy, and utterly
|
||||
insecure. Anyone with root access, any leaked backup, any misconfigured service—your secrets are exposed.
|
||||
|
||||
**Environment variables**: Slightly better, but they show up in `/proc`, get logged by accident, and persist in shell
|
||||
history.
|
||||
|
||||
**GPG encryption**: Powerful, but requires setting up GPG, managing a separate keyring, and dealing with GPG agent
|
||||
forwarding if you want to use it remotely. The friction is real.
|
||||
|
||||
**SOPS, age, or similar tools**: Good options, but they require you to have encryption keys on the remote machine—which
|
||||
defeats the purpose if you're trying to avoid storing key material there.
|
||||
|
||||
**Password managers with CLI tools**: Great locally, but integrating them over SSH typically means storing yet another
|
||||
credential on the remote machine.
|
||||
|
||||
The common thread? They all either store key material on the remote machine or require additional infrastructure beyond
|
||||
what's already there.
|
||||
|
||||
## The Realization
|
||||
|
||||
I kept staring at my SSH agent. It was *right there*—holding my private keys, forwarded to the remote machine, ready to
|
||||
sign anything I asked. SSH agents are designed to be secure: the private key never leaves the agent, even when
|
||||
forwarded.
|
||||
|
||||
What if I could use the SSH agent itself as my decryption mechanism?
|
||||
|
||||
## Introducing ssh-tresor
|
||||
|
||||
ssh-tresor encrypts secrets using keys held in your SSH agent. The private key never leaves the agent. There's no key
|
||||
material stored on disk. If your SSH agent is forwarded, you can decrypt your secrets. If it's not, you can't. It's that
|
||||
simple.
|
||||
|
||||
### How It Works
|
||||
|
||||
The core insight is that SSH agents can sign arbitrary data. ssh-tresor exploits this:
|
||||
|
||||
1. **Encryption**: Generate a random challenge. Ask the SSH agent to sign it. Use the signature (via HKDF-SHA256) as key
|
||||
material for AES-256-GCM encryption.
|
||||
|
||||
2. **Decryption**: Present the same challenge to the agent. If the same key signs it, you get the same signature, derive
|
||||
the same key, and decrypt successfully.
|
||||
|
||||
The actual data encryption uses a master key with AES-256-GCM. Each SSH key gets its own "slot" that encrypts this
|
||||
master key—similar to how LUKS handles multiple passphrases for disk encryption.
|
||||
|
||||
### Key Features
|
||||
|
||||
**No key material on disk**: Your private keys stay in the SSH agent. The encrypted file contains only challenges and
|
||||
ciphertexts—nothing that reveals your key.
|
||||
|
||||
**Works seamlessly over SSH**: If you have agent forwarding enabled (`ssh -A`), you can decrypt secrets on any remote
|
||||
machine. No additional setup required.
|
||||
|
||||
**Multi-key support (LUKS-style slots)**: Encrypt a secret for multiple SSH keys. Have a backup YubiKey? Add it. Use
|
||||
different keys on different machines? Add them all. Rotate keys by adding a new one and removing the old. Each slot
|
||||
independently encrypts the master key.
|
||||
|
||||
**Armored output**: Optional base64 format with headers for easy embedding in config files or version control.
|
||||
|
||||
**Simple CLI**: Follows Unix conventions—reads from stdin, writes to stdout, works in pipelines.
|
||||
|
||||
## Practical Usage
|
||||
|
||||
```bash
|
||||
# List keys in your agent
|
||||
ssh-tresor list-keys
|
||||
|
||||
# Encrypt a secret (uses first available key)
|
||||
echo -n "my-api-key-12345" | ssh-tresor encrypt -a > api-key.tresor
|
||||
|
||||
# Decrypt it
|
||||
ssh-tresor decrypt api-key.tresor
|
||||
|
||||
# Encrypt for multiple keys (e.g., laptop key + YubiKey)
|
||||
echo -n "my-api-key" | ssh-tresor encrypt -k SHA256:abc... -k SHA256:def... -o api-key.tresor
|
||||
|
||||
# Add another key to an existing tresor
|
||||
ssh-tresor add-key -k SHA256:newkey -i secret.tresor
|
||||
|
||||
# Remove a key
|
||||
ssh-tresor remove-key -k SHA256:oldkey -i secret.tresor
|
||||
```
|
||||
|
||||
### Integration with Tools
|
||||
|
||||
Many applications support fetching passwords from a command:
|
||||
|
||||
```toml
|
||||
# Email client config
|
||||
server_password_command = "ssh-tresor decrypt ~/.config/mail/imap.tresor"
|
||||
```
|
||||
|
||||
```json
|
||||
// Claude Code API key
|
||||
{
|
||||
"apiKeyHelper": "ssh-tresor decrypt ~/.config/claude/api-key.tresor"
|
||||
}
|
||||
```
|
||||
|
||||
The pattern works anywhere you can shell out for a password.
|
||||
|
||||
## Security Properties
|
||||
|
||||
- **AES-256-GCM** for authenticated encryption
|
||||
- **HKDF-SHA256** for key derivation from signatures
|
||||
- **Per-encryption random challenges**—replaying a captured tresor against a different agent instance won't help an
|
||||
attacker
|
||||
- **Security keys (FIDO2) supported**—they're tried last during decryption since they require user presence (touch)
|
||||
- **Zeroization**—master keys and derived keys are zeroed from memory after use
|
||||
|
||||
## When ssh-tresor Makes Sense
|
||||
|
||||
- You regularly work on remote machines via SSH with agent forwarding
|
||||
- You want to store secrets in dotfiles or git repos without them being readable if leaked
|
||||
- You have multiple SSH keys (laptop, desktop, backup YubiKey) and want any of them to decrypt your secrets
|
||||
- You want secrets protected by your SSH key without managing GPG or other encryption tools
|
||||
- You use FIDO2/security keys and want hardware-bound encryption
|
||||
|
||||
## When It Doesn't
|
||||
|
||||
- You need to decrypt secrets when no SSH agent is available
|
||||
- You need encryption that doesn't depend on any external service (the agent)
|
||||
- You're working in an environment where SSH agent forwarding is prohibited
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Via cargo
|
||||
cargo install ssh-tresor
|
||||
|
||||
# Via nix
|
||||
nix run github:haraldh/ssh-tresor -- --help
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
ssh-tresor was born from a simple observation: on remote machines, the SSH agent is often the only secure key storage
|
||||
available. By leveraging it for symmetric encryption, we get a secret management solution that requires no additional
|
||||
infrastructure, no key material on disk, and works seamlessly wherever your SSH agent is forwarded.
|
||||
|
||||
The SSH agent was always there. Now it can guard more than just your logins.
|
||||
|
||||
---
|
||||
|
||||
*ssh-tresor is open source under MIT/Apache-2.0 license. Find it
|
||||
at [github.com/haraldh/ssh-tresor](https://github.com/haraldh/ssh-tresor).*
|
||||
Loading…
Add table
Add a link
Reference in a new issue