ory/hydra · critical
at least one encryption key must be defined but none were
Error message
at least one encryption key must be defined but none were
What it means
allKeys collects the global (current) AEAD key plus rotated keys and refuses to proceed when none are configured. Decrypt and encryptionKey depend on this list, so decryption/encryption cannot run without at least one key.
Source
Thrown at aead/helpers.go:38
}
return key, nil
}
func allKeys(ctx context.Context, d Dependencies) ([][]byte, error) {
global, err := d.GetGlobalSecret(ctx)
if err != nil {
return nil, err
}
rotated, err := d.GetRotatedGlobalSecrets(ctx)
if err != nil {
return nil, err
}
keys := append([][]byte{global}, rotated...)
if len(keys) == 0 {
return nil, fmt.Errorf("at least one encryption key must be defined but none were")
}
return keys, nil
}
View on GitHub (pinned to 4174065ffb)
Solutions
- Set secrets.system / SECRETS_SYSTEM to a non-empty base64 32-byte key (openssl rand -base64 32).
- When moving deployments, keep the original key in rotated_keys so existing ciphertexts remain decryptable.
- Verify the environment variable actually reaches the process (docker-compose env, k8s secret mounted, config file loaded with -c).
- If data is unrecoverable, accept invalidation: reset the key and re-create affected secrets.
Example fix
// before (docker-compose) environment: [] // after environment: - SECRETS_SYSTEM=Q7N3...base64-32-bytes...
Defensive patterns
Strategy: validation
Validate before calling
// Before starting the service/janitor:
if os.Getenv("SECRETS_SYSTEM") == "" {
panic("SECRETS_SYSTEM must be set (base64 32-byte key): openssl rand -base64 32")
} Try / catch
// Wrap decryption so a missing-key config surfaces clearly:
val, err := x.Decrypt(ctx, ct)
if err != nil {
return nil, fmt.Errorf("decrypt failed — is secrets.system configured and matching the key that encrypted this data? %w", err)
} Prevention
- Set SECRETS_SYSTEM in every environment before first boot and persist it.
- Back up the key; losing it makes encrypted data undecryptable.
- In orchestrators, mount the key via a secret and fail fast on empty values.
- Use rotated_keys when migrating deployments.
When it happens
Trigger: Calling Decrypt or Encrypt when the AEAD dependency has an empty global key and no rotated keys (e.g. secrets.system unset or empty in configuration).
Common situations: Forgetting to set SECRETS_SYSTEM when deploying Hydra; empty string after config parsing; a fresh instance without the key used to encrypt previously stored data; environment variables not loaded in container orchestration.
Related errors
- key must be exactly %d bytes long, got %d bytes
- plaintext too large
- malformed ciphertext: too short
- cookiex: purpose must be non-empty and must not contain a pi
- cookiex: at least one secret is required
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/b1b1e230e0f357b4.
Report an issue: GitHub.