decolua/9router · error · Error
aes key must be 16 bytes, got ${keyBytes.length}
Error message
aes key must be 16 bytes, got ${keyBytes.length} What it means
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.
Source
Thrown at open-sse/shared/qoder/cosy.js:47
// AES-128 wants a 16-byte key. Match qodercli/Veria: take the first 16 chars
// of a fresh UUID's canonical string (hyphens included). The key is fresh
// per request so even though the IV reuses the key bytes, each request still
// has a unique IV.
function generateAesKey() {
return uuidv4().slice(0, 16);
}
function pkcs7Pad(data, blockSize) {
const padding = blockSize - (data.length % blockSize);
const padded = Buffer.alloc(data.length + padding, padding);
data.copy(padded, 0);
return padded;
}
function aesEncryptCbcBase64(plaintext, keyStr) {
const keyBytes = Buffer.from(keyStr, "utf8");
if (keyBytes.length !== 16) {
throw new Error(`aes key must be 16 bytes, got ${keyBytes.length}`);
}
const iv = keyBytes.subarray(0, 16);
const cipher = crypto.createCipheriv("aes-128-cbc", keyBytes, iv);
cipher.setAutoPadding(false);
const padded = pkcs7Pad(Buffer.from(plaintext, "utf8"), 16);
const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]);
return encrypted.toString("base64");
}
function rsaEncryptBase64(data) {
const encrypted = crypto.publicEncrypt(
{ key: QODER_RSA_PUBLIC_KEY, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(data, "utf8"),
);
return encrypted.toString("base64");
}
function encryptUserInfo(userInfo) {View on GitHub (pinned to 90b52e06ff)
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
Example fix
// before
const key = "cosy-secret-key"; // 15 bytes
const payload = infoB64(obj, key);
// after
const key = "cosy-secret-key1"; // exactly 16 bytes
if (Buffer.from(key, "utf8").length !== 16) throw new Error("cosy key must be 16 bytes");
const payload = infoB64(obj, key); Defensive patterns
Strategy: validation
Validate before calling
function assertCosyKey(key) {
if (Buffer.from(String(key), "utf8").length !== 16) {
throw new Error("cosy AES key must be exactly 16 bytes");
}
}
assertCosyKey(config.cosyKey); Type guard
function isCosyKey(k) { return typeof k === "string" && Buffer.from(k, "utf8").length === 16; } Try / catch
try {
payload = infoB64(obj, key);
} catch (e) {
if (/aes key must be 16 bytes/.test(e.message)) {
throw new ConfigError("COSY_KEY misconfigured: must be exactly 16 bytes");
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cosy: user id is empty
- qoder: model_config for "${qoderKey}" not yet known (run a m
- Xquik queryType must be Latest or Top
- Bing TTS returned empty audio
- ElevenLabs TTS returned empty audio
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/bbbcbb9b6d3f778d.
Report an issue: GitHub.