{"record":{"id":"6ba54be704342363","repo":"tinyhumansai/openhuman","slug":"encryption-key-must-decode-to-32-raw-bytes-raw-b","errorCode":null,"errorMessage":"encryption key must decode to 32 raw bytes (raw, base64, or base64url accepted; got len={})","messagePattern":"encryption key must decode to 32 raw bytes \\(raw, base64, or base64url accepted; got len=(.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/rest.rs","lineNumber":1213,"sourceCode":"\n    // `base64::Engine` has generic methods and therefore isn't\n    // dyn-compatible, so we unroll the attempts instead of looping over\n    // a slice of trait objects.\n    macro_rules! try_decode {\n        ($engine:expr) => {\n            if let Ok(decoded) = $engine.decode(trimmed) {\n                if decoded.len() == 32 {\n                    return Ok(decoded);\n                }\n            }\n        };\n    }\n    try_decode!(URL_SAFE_NO_PAD);\n    try_decode!(URL_SAFE);\n    try_decode!(STANDARD);\n    try_decode!(STANDARD_NO_PAD);\n\n    anyhow::bail!(\n        \"encryption key must decode to 32 raw bytes (raw, base64, or base64url accepted; got len={})\",\n        trimmed.len()\n    );\n}\n\n#[cfg(test)]\n#[path = \"rest_tests.rs\"]\nmod key_bytes_from_string_tests;\n","sourceCodeStart":1195,"sourceCodeEnd":1222,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L1195-L1222","documentation":"Thrown by key_bytes_from_string (src/api/rest.rs:1186) when no accepted decoding yields exactly 32 bytes. The function tries the raw string itself, then URL_SAFE_NO_PAD, URL_SAFE, STANDARD, and STANDARD_NO_PAD in order; each successful decode must be exactly 32 bytes. The bail reports the trimmed input length to hint at which format was expected.","triggerScenarios":"Passing a human-rememberable passphrase instead of a real key; a hex-encoded 64-char key (valid base64 characters but decodes to 48 bytes, so every engine fails the 32-byte check); a base64 key for a non-256-bit cipher (16 or 24 bytes after decode); a key with a stray character or wrong length; an empty/blank string.","commonSituations":"An operator puts a password into the handoff-encryption env var rather than the generated 32-byte secret; the backend sends the key in a format this decoder predates; whitespace is trimmed automatically, so the usual culprit is genuinely wrong key material, not formatting.","solutions":["Generate a proper 32-byte key and pass it base64url-no-pad (43 chars) — the backend's current format — or as a raw 32-character string.","If the key is hex (64 chars), convert it to 32 raw bytes and re-encode as base64url before passing.","Check the decoded length in a scratch script: every engine that decodes must land on exactly 32 bytes.","Match the format the other side (backend encryptMessageFromString) expects; do not invent a passphrase."],"exampleFix":"# before (passphrase / hex key)\nOPENHUMAN_HANDOFF_KEY=\"my-secret-password\"\nOPENHUMAN_HANDOFF_KEY=\"a1b2...\"  # 64 hex chars -> decodes to 48 bytes, fails\n\n# after (32 random bytes, base64url no padding -> 43 chars)\nopenssl rand -raw 32 | basenc --base64url -w0 | tr -d '='","handlingStrategy":"validation","validationCode":"use base64::engine::general_purpose::{STANDARD, URL_SAFE, URL_SAFE_NO_PAD, STANDARD_NO_PAD};\nuse base64::engine::Engine;\n\nfn key_decodes_to_32_bytes(key: &str) -> bool {\n    let k = key.trim();\n    k.len() == 32\n        || [URL_SAFE_NO_PAD.decode(k), URL_SAFE.decode(k), STANDARD.decode(k), STANDARD_NO_PAD.decode(k)]\n            .iter()\n            .any(|r| r.as_ref().map(|b| b.len() == 32).unwrap_or(false))\n}\n\nif !key_decodes_to_32_bytes(&configured_key) {\n    anyhow::bail!(\"handoff key misconfigured: must be raw 32 chars or base64 of 32 bytes\");\n}","typeGuard":null,"tryCatchPattern":"match key_bytes_from_string(key_str) {\n    Ok(bytes) => bytes,\n    Err(e) => {\n        log::warn!(\"[handoff] rejecting configured encryption key: {e}\");\n        return Err(e.context(\"configure a 32-byte key (base64url-no-pad, 43 chars, or raw 32 chars)\"));\n    }\n}","preventionTips":["Generate keys with `openssl rand -raw 32` and encode base64url without padding (43 chars).","Validate the key format at startup, not at first handoff, so misconfiguration fails fast with a clear message.","Never substitute a passphrase; the field is raw key material, not a secret string."],"tags":["rust","crypto","key-format","base64","configuration"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}