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

  1. 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).
  2. If the key comes from base64/hex config, decode it (base64.StdEncoding.DecodeString / hex.DecodeString) instead of using the literal string bytes.
  3. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/29baf77a0ab3f8e9. Report an issue: GitHub.