{"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/749120085864ce16e0f273c7b86fac7740b39c5b/src/api/rest.rs#L1195-L1222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before (16-byte key, hex key, or wrapped string)\nexport HANDOFF_KEY='1c2e...64-hex-chars'\n\n# after\nexport HANDOFF_KEY=\"$(openssl rand -base64 32)\"   # decodes to exactly 32 bytes","handlingStrategy":"validation","validationCode":"fn key_decodes_to_32_bytes(k: &str) -> bool {\n    use base64::Engine;\n    let t = k.trim();\n    base64::engine::general_purpose::STANDARD.decode(t)\n        .or_else(|_| base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(t))\n        .map(|d| d.len() == 32)\n        .unwrap_or(t.len() == 32)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["crypto","aes-256","key-management","config","base64"],"backgroundTag":"invalid-encryption-key","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}