188 lines
6.4 KiB
Markdown
188 lines
6.4 KiB
Markdown
# HeroDB AGE usage: Stateless vs Key‑Managed
|
||
|
||
This document explains how to use the AGE cryptography commands exposed by HeroDB over the Redis protocol in two modes:
|
||
- Stateless (ephemeral keys; nothing stored on the server)
|
||
- Key‑managed (server‑persisted, named keys)
|
||
|
||
If you are new to the codebase, the exact tests that exercise these behaviors are:
|
||
- [rust.test_07_age_stateless_suite()](herodb/tests/usage_suite.rs:495)
|
||
- [rust.test_08_age_persistent_named_suite()](herodb/tests/usage_suite.rs:555)
|
||
|
||
Implementation entry points:
|
||
- [herodb/src/age.rs](herodb/src/age.rs)
|
||
- Dispatch from [herodb/src/cmd.rs](herodb/src/cmd.rs)
|
||
|
||
Note: Database-at-rest encryption flags in the test harness are unrelated to AGE commands; those flags control storage-level encryption of DB files. See the harness near [rust.start_test_server()](herodb/tests/usage_suite.rs:10).
|
||
|
||
## Quick start
|
||
|
||
Assuming the server is running on localhost on some $PORT:
|
||
```bash
|
||
~/code/git.ourworld.tf/herocode/herodb/herodb/build.sh
|
||
~/code/git.ourworld.tf/herocode/herodb/target/release/herodb --dir /tmp/data --debug --$PORT 6381 --encryption-key 1234 --encrypt
|
||
```
|
||
|
||
|
||
```bash
|
||
export PORT=6381
|
||
# Generate an ephemeral keypair and encrypt/decrypt a message (stateless mode)
|
||
redis-cli -p $PORT AGE GENENC
|
||
# → returns an array: [recipient, identity]
|
||
|
||
redis-cli -p $PORT AGE ENCRYPT <recipient> "hello world"
|
||
# → returns ciphertext (base64 in a bulk string)
|
||
|
||
redis-cli -p $PORT AGE DECRYPT <identity> <ciphertext_b64>
|
||
# → returns "hello world"
|
||
```
|
||
|
||
For key‑managed mode, generate a named key once and reference it by name afterwards:
|
||
|
||
```bash
|
||
redis-cli -p $PORT AGE KEYGEN app1
|
||
# → persists encryption keypair under name "app1"
|
||
|
||
redis-cli -p $PORT AGE ENCRYPTNAME app1 "hello"
|
||
redis-cli -p $PORT AGE DECRYPTNAME app1 <ciphertext_b64>
|
||
```
|
||
|
||
## Stateless AGE (ephemeral)
|
||
|
||
Characteristics
|
||
|
||
- No server‑side storage of keys.
|
||
- You pass the actual key material with every call.
|
||
- Not listable via AGE LIST.
|
||
|
||
Commands and examples
|
||
|
||
1) Ephemeral encryption keys
|
||
|
||
```bash
|
||
# Generate an ephemeral encryption keypair
|
||
redis-cli -p $PORT AGE GENENC
|
||
# Example output (abridged):
|
||
# 1) "age1qz..." # recipient (public key) = can be used by others e.g. to verify what I sign
|
||
# 2) "AGE-SECRET-KEY-1..." # identity (secret) = is like my private, cannot lose this one
|
||
|
||
# Encrypt with the recipient public key
|
||
redis-cli -p $PORT AGE ENCRYPT "age1qz..." "hello world"
|
||
|
||
# → returns bulk string payload: base64 ciphertext (encrypted content)
|
||
|
||
# Decrypt with the identity (secret) in other words your private key
|
||
redis-cli -p $PORT AGE DECRYPT "AGE-SECRET-KEY-1..." "<ciphertext_b64>"
|
||
# → "hello world"
|
||
```
|
||
|
||
2) Ephemeral signing keys
|
||
|
||
> ? is this same as my private key
|
||
|
||
```bash
|
||
|
||
# Generate an ephemeral signing keypair
|
||
redis-cli -p $PORT AGE GENSIGN
|
||
# Example output:
|
||
# 1) "<verify_pub_b64>"
|
||
# 2) "<sign_secret_b64>"
|
||
|
||
# Sign a message with the secret
|
||
redis-cli -p $PORT AGE SIGN "<sign_secret_b64>" "msg"
|
||
# → returns "<signature_b64>"
|
||
|
||
# Verify with the public key
|
||
redis-cli -p $PORT AGE VERIFY "<verify_pub_b64>" "msg" "<signature_b64>"
|
||
# → 1 (valid) or 0 (invalid)
|
||
```
|
||
|
||
When to use
|
||
- You do not want the server to store private keys.
|
||
- You already manage key material on the client side.
|
||
- You need ad‑hoc operations without persistence.
|
||
|
||
Reference test: [rust.test_07_age_stateless_suite()](herodb/tests/usage_suite.rs:495)
|
||
|
||
## Key‑managed AGE (persistent, named)
|
||
|
||
Characteristics
|
||
- Server generates and persists keypairs under a chosen name.
|
||
- Clients refer to keys by name; raw secrets are not supplied on each call.
|
||
- Keys are discoverable via AGE LIST.
|
||
|
||
Commands and examples
|
||
|
||
1) Named encryption keys
|
||
|
||
```bash
|
||
# Create/persist a named encryption keypair
|
||
redis-cli -p $PORT AGE KEYGEN app1
|
||
# → returns [recipient, identity] but also stores them under name "app1"
|
||
|
||
> TODO: should not return identity (security, but there can be separate function to export it e.g. AGE EXPORTKEY app1)
|
||
|
||
# Encrypt using the stored public key
|
||
redis-cli -p $PORT AGE ENCRYPTNAME app1 "hello"
|
||
# → returns bulk string payload: base64 ciphertext
|
||
|
||
# Decrypt using the stored secret
|
||
redis-cli -p $PORT AGE DECRYPTNAME app1 "<ciphertext_b64>"
|
||
# → "hello"
|
||
```
|
||
|
||
2) Named signing keys
|
||
|
||
```bash
|
||
# Create/persist a named signing keypair
|
||
redis-cli -p $PORT AGE SIGNKEYGEN app1
|
||
# → returns [verify_pub_b64, sign_secret_b64] and stores under name "app1"
|
||
|
||
> TODO: should not return sign_secret_b64 (for security, but there can be separate function to export it e.g. AGE EXPORTSIGNKEY app1)
|
||
|
||
# Sign using the stored secret
|
||
redis-cli -p $PORT AGE SIGNNAME app1 "msg"
|
||
# → returns "<signature_b64>"
|
||
|
||
# Verify using the stored public key
|
||
redis-cli -p $PORT AGE VERIFYNAME app1 "msg" "<signature_b64>"
|
||
# → 1 (valid) or 0 (invalid)
|
||
```
|
||
|
||
3) List stored AGE keys
|
||
|
||
```bash
|
||
redis-cli -p $PORT AGE LIST
|
||
# Example output includes labels such as "encpub" and your key names (e.g., "app1")
|
||
```
|
||
|
||
When to use
|
||
- You want centralized key storage/rotation and fewer secrets on the client.
|
||
- You need names/labels for workflows and can trust the server with secrets.
|
||
- You want discoverability (AGE LIST) and simpler client commands.
|
||
|
||
Reference test: [rust.test_08_age_persistent_named_suite()](herodb/tests/usage_suite.rs:555)
|
||
|
||
## Choosing a mode
|
||
|
||
- Prefer Stateless when:
|
||
- Minimizing server trust for secret material is the priority.
|
||
- Clients already have a secure mechanism to store/distribute keys.
|
||
- Prefer Key‑managed when:
|
||
- Centralized lifecycle, naming, and discoverability are beneficial.
|
||
- You plan to integrate rotation, ACLs, or auditability on the server side.
|
||
|
||
## Security notes
|
||
|
||
- Treat identities and signing secrets as sensitive; avoid logging them.
|
||
- For key‑managed mode, ensure server storage (and backups) are protected.
|
||
- AGE operations here are application‑level crypto and are distinct from database-at-rest encryption configured in the test harness.
|
||
|
||
## Repository pointers
|
||
|
||
- Stateless examples in tests: [rust.test_07_age_stateless_suite()](herodb/tests/usage_suite.rs:495)
|
||
- Key‑managed examples in tests: [rust.test_08_age_persistent_named_suite()](herodb/tests/usage_suite.rs:555)
|
||
- AGE implementation: [herodb/src/age.rs](herodb/src/age.rs)
|
||
- Command dispatch: [herodb/src/cmd.rs](herodb/src/cmd.rs)
|
||
- Bash demo: [herodb/examples/age_bash_demo.sh](herodb/examples/age_bash_demo.sh)
|
||
- Rust persistent demo: [herodb/examples/age_persist_demo.rs](herodb/examples/age_persist_demo.rs)
|
||
- Additional notes: [herodb/instructions/encrypt.md](herodb/instructions/encrypt.md) |