{"record":{"id":"bbbcbb9b6d3f778d","repo":"decolua/9router","slug":"aes-key-must-be-16-bytes-got-keybytes-length","errorCode":null,"errorMessage":"aes key must be 16 bytes, got ${keyBytes.length}","messagePattern":"aes key must be 16 bytes, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/shared/qoder/cosy.js","lineNumber":47,"sourceCode":"// AES-128 wants a 16-byte key. Match qodercli/Veria: take the first 16 chars\n// of a fresh UUID's canonical string (hyphens included). The key is fresh\n// per request so even though the IV reuses the key bytes, each request still\n// has a unique IV.\nfunction generateAesKey() {\n  return uuidv4().slice(0, 16);\n}\n\nfunction pkcs7Pad(data, blockSize) {\n  const padding = blockSize - (data.length % blockSize);\n  const padded = Buffer.alloc(data.length + padding, padding);\n  data.copy(padded, 0);\n  return padded;\n}\n\nfunction aesEncryptCbcBase64(plaintext, keyStr) {\n  const keyBytes = Buffer.from(keyStr, \"utf8\");\n  if (keyBytes.length !== 16) {\n    throw new Error(`aes key must be 16 bytes, got ${keyBytes.length}`);\n  }\n  const iv = keyBytes.subarray(0, 16);\n  const cipher = crypto.createCipheriv(\"aes-128-cbc\", keyBytes, iv);\n  cipher.setAutoPadding(false);\n  const padded = pkcs7Pad(Buffer.from(plaintext, \"utf8\"), 16);\n  const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]);\n  return encrypted.toString(\"base64\");\n}\n\nfunction rsaEncryptBase64(data) {\n  const encrypted = crypto.publicEncrypt(\n    { key: QODER_RSA_PUBLIC_KEY, padding: crypto.constants.RSA_PKCS1_PADDING },\n    Buffer.from(data, \"utf8\"),\n  );\n  return encrypted.toString(\"base64\");\n}\n\nfunction encryptUserInfo(userInfo) {","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/shared/qoder/cosy.js#L29-L65","documentation":"cosy.js implements a custom AES-128-CBC encryption for Qoder/Cosy protocol headers. aesEncryptCbcBase64 hard-requires the key material to be exactly 16 bytes (AES-128) and also reuses the first 16 bytes of the key as the IV. This error fires when the configured key string, encoded as UTF-8, is any other length.","triggerScenarios":"Calling infoB64 (which calls aesEncryptCbcBase64) with a creds/key string that is not exactly 16 UTF-8 bytes — e.g. a 15-char or 20-char key, or a key containing multi-byte UTF-8 characters that change the byte length.","commonSituations":"Hardcoding a custom key that looks right but is the wrong length; copying a key with an extra/missing character; non-ASCII characters in the key inflating byte length beyond character count; upstream changing the protocol key length.","solutions":["Ensure the key string is exactly 16 ASCII characters / 16 UTF-8 bytes","Validate Buffer.from(key, 'utf8').length === 16 before calling","Check for invisible whitespace or truncated copy-paste in the configured key","Verify the key against the protocol constant defined in the Qoder/Cosy client rather than inventing one"],"exampleFix":"// before\nconst key = \"cosy-secret-key\"; // 15 bytes\nconst payload = infoB64(obj, key);\n// after\nconst key = \"cosy-secret-key1\"; // exactly 16 bytes\nif (Buffer.from(key, \"utf8\").length !== 16) throw new Error(\"cosy key must be 16 bytes\");\nconst payload = infoB64(obj, key);","handlingStrategy":"validation","validationCode":"function assertCosyKey(key) {\n  if (Buffer.from(String(key), \"utf8\").length !== 16) {\n    throw new Error(\"cosy AES key must be exactly 16 bytes\");\n  }\n}\nassertCosyKey(config.cosyKey);","typeGuard":"function isCosyKey(k) { return typeof k === \"string\" && Buffer.from(k, \"utf8\").length === 16; }","tryCatchPattern":"try {\n  payload = infoB64(obj, key);\n} catch (e) {\n  if (/aes key must be 16 bytes/.test(e.message)) {\n    throw new ConfigError(\"COSY_KEY misconfigured: must be exactly 16 bytes\");\n  }\n  throw e;\n}","preventionTips":["Store the key as an exact 16-byte ASCII constant","Validate key length at startup, not at request time","Avoid multi-byte characters in the key","Copy keys without trailing whitespace/newlines"],"tags":["crypto","aes","qoder","validation"],"backgroundTag":"invalid-crypto-key-length","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}