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
- Generate/use exactly 32 random bytes: randomBytes(32), or decode a stored encoding first (Buffer.from(base64Str, 'base64') or Buffer.from(hexStr, 'hex')).
- Log/verify key.byteLength === 32 before constructing the encryption config.
- If the stored key was 16 bytes (AES-128), re-encrypt secrets with a new 32-byte key rather than padding the old one.
- 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
- Always decode encoded keys (hex/base64) to bytes before passing them; never pass the encoded string's characters.
- Generate new keys with crypto.randomBytes(32) and store them base64-encoded.
- Trim whitespace/newlines from env-provided key values before decoding.
- Include a length assertion (byteLength === 32) wherever keys are loaded from config or env.
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
- Cookie password must be at least 32 characters for SSO. Set
- Cookie password must be at least 32 characters for SSO. Set
- Telegram installation secrets are encrypted at rest, but no
- [FactorySecretEncryption] Key id is required.
- [FactorySecretEncryption] Unknown key id "${envelope.keyId}"
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/9c89514226fb6b21.
Report an issue: GitHub.