tinyhumansai/openhuman · error
encryption key must decode to 32 raw bytes
Error message
encryption key must decode to 32 raw bytes (raw, base64, or base64url accepted; got len={}) What it means
key_bytes_from_string accepts a 32-byte AES-256 key either as raw text or as base64 (URL_SAFE_NO_PAD, URL_SAFE, STANDARD, and STANDARD_NO_PAD are all tried in turn); if none of the decodings yields exactly 32 bytes it bails reporting the raw input length. A wrong-size key (16 or 24 bytes), a corrupted base64 string, or an unsupported encoding like hex fails here - before any decryption is attempted.
Solutions
- Generate exactly 32 bytes, e.g. `openssl rand -base64 32`, and store the resulting base64 string.
- Strip surrounding whitespace and newlines from the stored secret, then verify it decodes to 32 bytes.
- If using the raw form, ensure the string is exactly 32 characters.
- Do not hex-encode the key - that encoding is not tried.
Example fix
# before (16-byte key, hex key, or wrapped string) export HANDOFF_KEY='1c2e...64-hex-chars' # after export HANDOFF_KEY="$(openssl rand -base64 32)" # decodes to exactly 32 bytes
Defensive patterns
Strategy: validation
Validate before calling
fn key_decodes_to_32_bytes(k: &str) -> bool {
use base64::Engine;
let t = k.trim();
base64::engine::general_purpose::STANDARD.decode(t)
.or_else(|_| base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(t))
.map(|d| d.len() == 32)
.unwrap_or(t.len() == 32)
} Prevention
- Generate keys with `openssl rand -base64 32` so the length is correct by construction.
- Validate the key once at startup with a length check, not on first decrypt.
- Store secrets without embedded newlines; never hex-encode keys for this API.
When it happens
Trigger: The configured key was generated for AES-128 (16 bytes); a hex-encoded key (64 hex chars) was supplied since hex decoding is not attempted; the base64 string lost a character or gained a newline; a 44-char standard-base64 key is fine but a truncated 43-char unpadded copy is not.
Common situations: Ops generates the wrong key size; the key is pasted from a secret manager with embedded line breaks; a team assumes hex or base32 encoding is supported.
Related errors
- encrypted payload too short
- agent ` ` is a `worker` tier and must not list ` ` in its…
- [chat-factory] model
- [chat-factory] no model configured: role
- Config path is a directory, not a file
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/6ba54be704342363.
Report an issue: GitHub.
Appendix: source
Thrown at src/api/rest.rs:1213
// `base64::Engine` has generic methods and therefore isn't
// dyn-compatible, so we unroll the attempts instead of looping over
// a slice of trait objects.
macro_rules! try_decode {
($engine:expr) => {
if let Ok(decoded) = $engine.decode(trimmed) {
if decoded.len() == 32 {
return Ok(decoded);
}
}
};
}
try_decode!(URL_SAFE_NO_PAD);
try_decode!(URL_SAFE);
try_decode!(STANDARD);
try_decode!(STANDARD_NO_PAD);
anyhow::bail!(
"encryption key must decode to 32 raw bytes (raw, base64, or base64url accepted; got len={})",
trimmed.len()
);
}
#[cfg(test)]
#[path = "rest_tests.rs"]
mod key_bytes_from_string_tests;
View on GitHub (pinned to 7491200858)