ory/hydra · error
key must be exactly 32 long bytes, got %d bytes
Error message
key must be exactly 32 long bytes, got %d bytes
What it means
AESGCM.decrypt requires the encryption key to be exactly 32 bytes (AES-256) before it derives the AEAD subkey and runs GCM decryption. The library throws this when a caller supplies a key of any other length, because Go's crypto/aes only accepts 16/24/32-byte keys and this implementation standardizes on 256-bit keys.
Source
Thrown at aead/aesgcm.go:67
}
keys, err := allKeys(ctx, c.c)
if err != nil {
return nil, errors.WithStack(err)
}
for _, key := range keys {
if plaintext, err = c.decrypt(msg, key, aad); err == nil {
return plaintext, nil
}
}
return nil, err
}
func (*AESGCM) decrypt(ciphertext, key, additionalData []byte) ([]byte, error) {
if len(key) != 32 {
return nil, errors.Errorf("key must be exactly 32 long bytes, got %d bytes", len(key))
}
plaintext, err := aesGCMDecrypt(ciphertext, aeadKey(key), additionalData)
if err != nil {
return nil, errors.WithStack(err)
}
return plaintext, nil
}
// aesGCMEncrypt encrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Output takes the
// form nonce|ciphertext|tag where '|' indicates concatenation.
func aesGCMEncrypt(plaintext []byte, key *[32]byte, additionalData []byte) (ciphertext []byte, err error) {
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}View on GitHub (pinned to 4174065ffb)
Solutions
- Check len(key) at the call site and regenerate/derive a 32-byte key (crypto/rand.Read(make([]byte, 32)) or sha256 of a passphrase).
- If the key comes from base64/hex config, decode it (base64.StdEncoding.DecodeString / hex.DecodeString) instead of using the literal string bytes.
- If you have a shorter key, derive a 32-byte one deterministically (e.g. HKDF/SHA-256) — but note this changes ciphertexts only if the original encryption also derived keys the same way.
Example fix
// before
key := []byte(os.Getenv("COOKIE_SECRET")) // may be < 32 bytes
plaintext, err := cipher.Decrypt(ctx, ciphertext, key, ad)
// after
raw, err := base64.StdEncoding.DecodeString(os.Getenv("COOKIE_SECRET"))
if err != nil || len(raw) != 32 {
return fmt.Errorf("COOKIE_SECRET must decode to exactly 32 bytes")
}
plaintext, err := cipher.Decrypt(ctx, ciphertext, raw, ad) Defensive patterns
Strategy: validation
Validate before calling
if len(key) != 32 {
return fmt.Errorf("encryption key must be 32 bytes, got %d", len(key))
} Prevention
- Generate keys with crypto/rand.Read(make([]byte, 32)) and store them base64-encoded.
- Always decode base64/hex key material from config before passing byte slices.
- Add a startup assertion on key length so misconfiguration fails fast.
- Never pass the raw config string bytes as a key.
When it happens
Trigger: Calling Decrypt (or decrypt directly) with a key that is not 32 bytes long — e.g. a raw 16-byte or 24-byte AES key, a hex-decoded key that was truncated, or a base64 string passed in without decoding.
Common situations: Storing the key in config as a string and passing its byte length instead of the decoded value; generating a key with a different cipher setting (AES-128); reading a key from an env var that was padded, truncated, or not base64/hex decoded; migrating from an older cipher that used 16-byte keys.
Related errors
- cannot create XChaCha20-Poly1305 AEAD
- cannot create AEAD
- cannot open AEAD
- cookiex: at least one secret is required
- ErrUnknownHashAlgorithm
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/29baf77a0ab3f8e9.
Report an issue: GitHub.