166 lines
6.4 KiB
Markdown
166 lines
6.4 KiB
Markdown
# 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).*
|