Skip to main content

FIPS 140-3 build

Ory Enterprise License releases include a dedicated FIPS 140-3 build of Ory Hydra next to the standard build. Use it when your deployment has to run cryptography inside a FIPS 140-3 validated module.

The FIPS build is compiled against the Go Cryptographic Module, which holds a NIST CMVP certificate, and starts with GODEBUG=fips140=on by default. It also selects FIPS-approved algorithms where the standard build uses an unapproved one, and refuses to start on a configuration that cannot be FIPS compliant.

info

The FIPS build is available only with an Ory Enterprise License. Contact us to discuss your requirements.

Get the FIPS build

The FIPS build ships as a binary named hydra_oel_fips_<version>_<os>_<arch> alongside the standard OEL release artifacts. It is built for linux/amd64. There is no FIPS container image, so a deployment that runs the standard hydra-oel image today has to switch to running the binary. Ask your Ory support contact for access to the artifact.

Everything else about the binary is the same as the standard OEL build: the same commands, the same configuration file, the same database schema, and the same migrations.

Verify that you have a FIPS binary

The certified module and its version are recorded in the binary. Check them with the Go toolchain:

go version -m ./hydra_oel_fips_<version>_linux_amd64 | grep -E 'GOFIPS140|DefaultGODEBUG'

The output must list both a GOFIPS140 module version and fips140=on in the default GODEBUG:

build GOFIPS140=v1.0.0-c2097c7c
build DefaultGODEBUG=fips140=on,x509usefallbackroots=1

If either line is missing, you are looking at the standard build.

Required configuration

In FIPS mode, hydra serve refuses to start unless the configuration below is in place. The hydra migrate and hydra janitor commands are not affected, because they never touch the affected ciphers.

Dedicated secrets for every purpose

secrets.system, secrets.cookie, and secrets.pagination must all be set explicitly, and their active (first) secrets must differ from one another. When secrets.cookie or secrets.pagination is unset, Hydra silently falls back to secrets.system, which reuses one key across unrelated purposes.

secrets:
system:
- this-is-the-system-secret
cookie:
- this-is-the-cookie-secret
pagination:
- this-is-the-pagination-secret

Each secret must be at least 16 characters long. Older entries further down a list may repeat another list's secret, so key rotation keeps working while an old key is still needed for decryption.

If a secret is missing, startup fails with:

you must provide `secrets.cookie` for FIPS compliance

If two lists share the same active secret, startup fails with:

`secrets.system` and `secrets.pagination` must not share the same active (first) secret for FIPS compliance

PBKDF2 for OAuth 2.0 client secrets

Keep oauth2.hashers.algorithm at its default value, pbkdf2. Hydra hashes client secrets with PBKDF2-HMAC-SHA256, which is FIPS 140-3 approved and runs inside the validated module.

oauth2:
hashers:
algorithm: pbkdf2
pbkdf2:
iterations: 25000

The alternative, bcrypt, is not FIPS 140-3 approved and hashes outside the module, so hydra serve refuses to start with it:

`oauth2.hashers.algorithm` must not be `bcrypt` for FIPS compliance, because it hashes client secrets outside the validated
module: set it to `pbkdf2`

Client secrets that were hashed while the setting was bcrypt keep verifying, because Hydra picks the comparison algorithm from the stored hash rather than from the configuration. Verifying an existing hash is not restricted; only producing new ones is. The setting does not re-hash them either, so rotate those client secrets to move them onto PBKDF2.

What changes in FIPS mode

Hydra seals its transient payloads with XChaCha20-Poly1305, which is not a FIPS-approved construction. The FIPS build replaces it with AES-256-GCM. Everything else keeps the algorithm it already used.

DataStandard buildFIPS build
Login, consent, device, and logout challenges and verifiersXChaCha20-Poly1305AES-256-GCM
Verifiable credential noncesXChaCha20-Poly1305AES-256-GCM
Authorization codes, when oauth2.authorize_code.format is aeadXChaCha20-Poly1305AES-256-GCM
Admin API pagination tokensXChaCha20-Poly1305AES-256-GCM
JSON Web Keys and OAuth 2.0 session payloads at restAES-256-GCMAES-256-GCM
Access token, refresh token, and authorization code signaturesHMAC-SHA-512/256HMAC-SHA-512/256
OAuth 2.0 client secret hashesPBKDF2-HMAC-SHA256PBKDF2-HMAC-SHA256

Data at rest is not re-encrypted, and the storage format does not change. Existing JSON Web Keys and session payloads stay readable, and no migration is needed.

ID tokens and, with the JWT access token strategy, access tokens are signed with the algorithm you pick when you create the JSON Web Key set. Every algorithm Hydra can generate a key for is FIPS 140-3 approved, including the RS256 default, and the FIPS build signs with the validated module.

Rotate the system secret regularly

warning

Regular rotation of all secrets is mandatory in FIPS mode. The AES-256-GCM construction that FIPS 140-3 requires here carries a real chance of reusing a nonce, and that chance grows the longer Hydra runs on one system secret. Rotation is the only thing that resets it.

AES-256-GCM draws a fresh 96-bit nonce at random for every payload it seals, and every payload sealed under one key draws from the same nonce space. Two payloads can therefore draw the same nonce, and by the birthday bound that probability grows with the square of the number of payloads sealed — doubling the volume quadruples the risk. It is never zero, and it only ever increases while the key stays in place. XChaCha20-Poly1305, which the standard build uses, has a 192-bit nonce and does not have this limit.

A repeated nonce is not a graceful degradation. It reveals the XOR of the two plaintexts, and it leaks the GHASH authentication subkey, which lets an attacker forge valid tags for arbitrary payloads under that key. Confidentiality and integrity both fail.

NIST SP 800-38D section 8.3 caps a random-nonce AES-GCM key at 2^32 sealings for this reason. Treat that as the outer bound, not a target:

Payloads sealed under one keyChance that two share a nonce
2^32 (4.3 billion)1 in 8.6 billion — NIST's cap
2^40 (1.1 trillion)1 in 130,000
2^44 (18 trillion)1 in 500
2^48 (280 trillion)1 in 2.5

Hydra spends this budget on every login, consent, device, and logout challenge and verifier, every verifiable credential nonce, every aead-format authorization code, and every admin API pagination token, so a single user flow seals several payloads. At a sustained 1,000 sealings per second, one key reaches NIST's cap in about 50 days; at 10,000 per second, in about 5.

Rotating secrets.system promotes a new active key and restarts the count at zero. Older entries in the list keep decrypting payloads sealed under previous keys, so rotation does not invalidate in-flight flows. For how to roll a secret without downtime, see Secrets and key rotation.

Switch an existing deployment

The two builds cannot read each other's transient payloads. Switching in either direction invalidates in-flight login, consent, device, and logout flows, outstanding verifiable credential nonces, unredeemed authorization codes, and admin API pagination tokens. Users in the middle of a flow start over, and a list request that carries a pagination token across the switch starts at the first page. Issued access, refresh, and ID tokens are not affected.

Prefer a stop-then-start cutover: scale the old build to zero, then start the new one. A rolling update also converges, but flows that cross instances fail while both builds are running.

Startup log signals

When the FIPS build starts, it logs an info message confirming that FIPS 140-3 mode is active. Hydra evaluates the configuration once at startup and does not re-check it on configuration reloads, so a change that would fail the checks above only takes effect on the next restart.

Running the standard build in FIPS mode

The standard OEL build honors GODEBUG=fips140=on and switches the same ciphers when you set it, so treat that setting like a build switch and follow Switch an existing deployment. What it does not give you is the certified module: the standard build is compiled against the regular Go cryptography packages. Only the FIPS build runs the validated module, so only the FIPS build supports a FIPS 140-3 compliance claim.