picoclaw/docs/credential_encryption.md
sky5454 4921690b5e feat(credential): add AES-GCM encryption, SecureStore, and onboard keygen
- pkg/credential: new package with AES-256-GCM enc:// credential format,
  HKDF-SHA256 key derivation (passphrase + optional SSH key binding),
  ErrPassphraseRequired / ErrDecryptionFailed sentinel errors,
  and PassphraseProvider hook for runtime passphrase injection

- pkg/credential/store: lock-free SecureStore via atomic.Pointer[string];
  passphrase never written to disk or os.Environ

- pkg/credential/keygen: ed25519 SSH key generation helper used by onboard

- pkg/config: replace os.Getenv(PassphraseEnvVar) with
  credential.PassphraseProvider() at all three call sites so that
  LoadConfig and SaveConfig use whatever passphrase source is active

- cmd/picoclaw/onboard: prompt for passphrase with echo-off, generate
  picoclaw-specific SSH key, re-encrypt existing config on re-onboard

- docs/credential_encryption.md: design doc for the enc:// format
2026-03-14 04:52:39 +08:00

4.8 KiB

Credential Encryption

PicoClaw supports encrypting api_key values in model_list configuration entries. Encrypted keys are stored as enc://<base64> strings and decrypted automatically at startup.


Quick Start

1. Set your passphrase

export PICOCLAW_KEY_PASSPHRASE="your-passphrase"

2. Encrypt an API key (using the built-in CLI subcommand, or Go API)

picoclaw encrypt sk-your-openai-key
# outputs: enc://AAAA...base64...

3. Paste the output into your config

{
  "model_list": [
    {
      "model_name": "gpt-4o",
      "api_key": "enc://AAAA...base64...",
      "base_url": "https://api.openai.com/v1"
    }
  ]
}

Supported api_key Formats

Format Example Behaviour
Plaintext sk-abc123 Used as-is
File reference file://openai.key Content read from the same directory as the config file
Encrypted enc://<base64> Decrypted at startup using PICOCLAW_KEY_PASSPHRASE
Empty "" Passed through unchanged (used with auth_method: oauth)

Cryptographic Design

Key Derivation

Encryption uses HKDF-SHA256 with an optional SSH private key as a second factor.

Without SSH key (passphrase only):

  ikm     = SHA256(passphrase)
  aes_key = HKDF-SHA256(ikm, salt, info="picoclaw-credential-v1", 32 bytes)


With SSH key (recommended):

  sshHash = SHA256(ssh_private_key_file_bytes)
  ikm     = HMAC-SHA256(key=sshHash, message=passphrase)
  aes_key = HKDF-SHA256(ikm, salt, info="picoclaw-credential-v1", 32 bytes)

Encryption

AES-256-GCM(key=aes_key, nonce=random[12], plaintext=api_key)

Wire Format

enc://<base64( salt[16] + nonce[12] + ciphertext )>
Field Size Description
salt 16 bytes Random per encryption; fed into HKDF
nonce 12 bytes Random per encryption; AES-GCM IV
ciphertext variable AES-256-GCM ciphertext + 16-byte authentication tag

The GCM authentication tag is appended to the ciphertext automatically. Any tampering causes decryption to fail with an error rather than returning corrupt plaintext.

Performance

Operation Time (ARM Cortex-A)
Key derivation (HKDF) < 1 ms
AES-256-GCM decrypt < 1 ms
Total startup overhead < 2 ms per key

Two-Factor Security with SSH Key

When a SSH private key is provided, breaking the encryption requires both:

  1. The passphrase (PICOCLAW_KEY_PASSPHRASE)
  2. The SSH private key file

This means a leaked config file alone is not sufficient to recover the API key, even if the passphrase is weak. The SSH key contributes 256 bits of entropy (Ed25519) regardless of passphrase strength.

Threat Model

Attacker Has Can Decrypt?
Config file only No — needs passphrase + SSH key
SSH key only No — needs passphrase
Passphrase only No — needs SSH key
Config file + SSH key + passphrase Yes — full compromise

Environment Variables

Variable Required Description
PICOCLAW_KEY_PASSPHRASE Yes (for enc://) Passphrase used for key derivation
PICOCLAW_SSH_KEY_PATH No Path to SSH private key. Set to "" to disable auto-detection and use passphrase-only mode

SSH Key Auto-Detection

If PICOCLAW_SSH_KEY_PATH is not set, PicoClaw looks for the picoclaw-specific key:

~/.ssh/picoclaw_ed25519.key

This dedicated file avoids conflicts with the user's existing SSH keys. Run picoclaw onboard to generate it automatically.

os.UserHomeDir() is used for cross-platform home directory resolution (reads USERPROFILE on Windows, HOME on Unix/macOS).

To explicitly disable SSH key usage and use passphrase-only mode:

export PICOCLAW_SSH_KEY_PATH=""

Migration

Because the only secret material is PICOCLAW_KEY_PASSPHRASE and the SSH private key file, migration is straightforward:

  1. Copy the config file to the new machine.
  2. Set PICOCLAW_KEY_PASSPHRASE to the same value.
  3. Copy the SSH private key file to the same path (or set PICOCLAW_SSH_KEY_PATH to its new location).

No re-encryption is needed.


Security Considerations

  • Passphrase strength matters in passphrase-only mode. Without an SSH key, a weak passphrase can be brute-forced offline. Use PICOCLAW_SSH_KEY_PATH="" only in environments where no SSH key is available and the passphrase is sufficiently strong (≥ 32 random characters).
  • The SSH key is read-only at runtime. PicoClaw never writes to or modifies the SSH key file.
  • Plaintext keys remain supported. Existing configs without enc:// are unaffected.
  • The enc:// format is versioned via the HKDF info field (picoclaw-credential-v1), allowing future algorithm upgrades without breaking existing encrypted values.