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

  1. Ensure the key string is exactly 16 ASCII characters / 16 UTF-8 bytes
  2. Validate Buffer.from(key, 'utf8').length === 16 before calling
  3. Check for invisible whitespace or truncated copy-paste in the configured key
  4. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/bbbcbb9b6d3f778d. Report an issue: GitHub.