mastra-ai/mastra · error

[FactorySecretEncryption] Key "${id}" must be exactly 32 byt

Error message

[FactorySecretEncryption] Key "${id}" must be exactly 32 bytes.

What it means

Encryption is AES-256-GCM, which requires a 256-bit (32-byte) key. validateKey() converts the provided Uint8Array key to a Buffer and throws unless byteLength is exactly 32, for the primary key and every entry in `previous`. A wrong length would make createCipheriv/createDecipheriv fail or silently weaken the setup, so the library fails fast.

Source

Thrown at mastracode/factory/src/secret-encryption.ts:39

  encrypt<T>(value: T): Promise<string>;
  decrypt<T>(value: unknown): Promise<DecryptedFactorySecret<T>>;
}

export interface FactorySecretEncryptionKey {
  id: string;
  key: Uint8Array;
}

export interface FactorySecretEncryptionConfig {
  primary: FactorySecretEncryptionKey;
  previous?: FactorySecretEncryptionKey[];
}

function validateKey({ id, key }: FactorySecretEncryptionKey): Buffer {
  if (!id) throw new Error('[FactorySecretEncryption] Key id is required.');
  const buffer = Buffer.from(key);
  if (buffer.byteLength !== 32) {
    throw new Error(`[FactorySecretEncryption] Key "${id}" must be exactly 32 bytes.`);
  }
  return buffer;
}

function parseEnvelope(value: string): SecretEnvelopeV1 {
  let parsed: unknown;
  try {
    parsed = JSON.parse(Buffer.from(value.slice(ENVELOPE_PREFIX.length), 'base64url').toString('utf8'));
  } catch {
    throw new Error('[FactorySecretEncryption] Invalid encrypted value.');
  }

  if (
    !parsed ||
    typeof parsed !== 'object' ||
    typeof (parsed as SecretEnvelopeV1).keyId !== 'string' ||
    typeof (parsed as SecretEnvelopeV1).iv !== 'string' ||
    typeof (parsed as SecretEnvelopeV1).ciphertext !== 'string' ||

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Generate/use exactly 32 random bytes: randomBytes(32), or decode a stored encoding first (Buffer.from(base64Str, 'base64') or Buffer.from(hexStr, 'hex')).
  2. Log/verify key.byteLength === 32 before constructing the encryption config.
  3. If the stored key was 16 bytes (AES-128), re-encrypt secrets with a new 32-byte key rather than padding the old one.
  4. Trim whitespace/newlines from env-provided keys before decoding.

Example fix

// before
const key = Buffer.from(process.env.SECRET_KEY ?? '', 'utf8'); // arbitrary length
createFactorySecretEncryption({ primary: { id: 'k1', key } });

// after
const key = Buffer.from(process.env.SECRET_KEY_B64 ?? '', 'base64');
if (key.byteLength !== 32) throw new Error(`Key must be 32 bytes, got ${key.byteLength}`);
createFactorySecretEncryption({ primary: { id: 'k1', key } });
Defensive patterns

Strategy: validation

Validate before calling

import { randomBytes } from 'node:crypto';
function decodeKey32(raw: string): Uint8Array {
  const buf = /^[0-9a-fA-F]{64}$/.test(raw.trim()) ? Buffer.from(raw.trim(), 'hex')
    : Buffer.from(raw.trim(), 'base64');
  if (buf.byteLength !== 32) throw new Error(`Key must be 32 bytes, got ${buf.byteLength}`);
  return buf;
}
// or generate: randomBytes(32)

Type guard

function isAes256Key(key: unknown): key is Uint8Array {
  return key instanceof Uint8Array && key.byteLength === 32;
}

Try / catch

try {
  const enc = createFactorySecretEncryption({ primary: { id, key } });
} catch (err) {
  if (err instanceof Error && err.message.includes('must be exactly 32 bytes')) {
    console.error(`Key '${id}' has wrong length — decode hex/base64 first or generate randomBytes(32)`);
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a FactorySecretEncryptionKey whose key Uint8Array is not 32 bytes (e.g. 16-byte AES-128 key, 44-char base64 string decoded to 32+ chars, hex-encoded 64-char string interpreted as 64 raw bytes, or truncated/padded env var) to createFactorySecretEncryption or primaryKey.

Common situations: Supplying a hex string's characters instead of its decoded bytes; base64 keys with whitespace/newlines changing length; reusing an older AES-128 key after upgrade; env var gotcha where the raw key contains characters and was never decoded; copying a key and dropping bytes.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/9c89514226fb6b21. Report an issue: GitHub.