Security Considerations
This page consolidates the security properties of the JWE starter and the operational recommendations that follow from them — the cryptographic invariants (IV uniqueness, CEK lifetime, key strength), key-rotation guidance, and the attack surface to be aware of when operating the starter. The per-topic pages keep their local security notes; this page is the single place to audit them together.
Algorithms and key strength
- Key encryption:
RSA-OAEP-256with RSA-4096 key pairs. Key size is validated on every load — keys smaller than 4096 bits are rejected and the application refuses to start (see Key management). - Content encryption:
A256GCM(AES-256 in Galois/Counter Mode, authenticated encryption). - No custom cryptography. All JOSE operations use established libraries: Nimbus JOSE+JWT on
the server,
josein the jeap-jwe-client frontend library. The Amazon Corretto Crypto Provider accelerates the RSA-OAEP and AES-GCM primitives with a transparent fallback to the JDK provider (see Architecture).
IV uniqueness
AES-GCM fails catastrophically when an IV (nonce) is reused under the same key — both confidentiality and authenticity are lost. The protocol guarantees IV uniqueness on two independent levels:
- A fresh random 96-bit IV is generated for every JWE by the JOSE library (Nimbus on the
server,
joseon the client). IVs are never supplied by configuration or application code, and never reused across messages. - Every content-encryption key is single-use (see CEK lifetime below): a CEK encrypts exactly one message. Even a hypothetical IV collision between two messages would therefore involve two different keys — the dangerous (key, IV) pair reuse cannot occur. With one encryption per key, usage stays far below the AES-GCM limits of NIST SP 800-38D.
A retried request is a new JWE with a fresh IV (and normally a fresh CEK) — the protocol has no mechanism to replay an IV.
CEK lifetime
Content-encryption keys are strictly per message and live only for the duration of a single HTTP exchange:
- The request CEK is generated by the client for one request, RSA-wrapped inside the request JWE, unwrapped by the filter, used once to decrypt that request body, and discarded.
- The response CEK is a separate key, generated by the client per request and
delivered RSA-wrapped in the
JWE-Response-Keyheader. The filter uses it once to encrypt that response (alg: dir). The request CEK is never reused for the response. - CEKs are held only in JVM heap memory for the duration of the request, are never persisted, never logged, and never shared across requests. There are no session keys and no server-side key state per client — the protocol is stateless.
This one-CEK-per-message design is what makes the IV-uniqueness argument above robust and limits the blast radius of any single compromised message to exactly that message.
RSA key material handling
- Private key material lives exclusively in JVM heap memory — never on disk, in logs, environment variables, or external caches.
- Log messages reference keys only by their public
kid; PEM/key content is never logged, andJweProperties.Test.toString()redacts configured test keys. - The JWKS endpoint emits public key parameters only (see JWKS endpoint).
Key-rotation recommendations
Rotation is cheap and non-disruptive — rotate regularly, per your organization's key management policy, rather than only on demand:
- Rotate with
vault write -f transit/<system>/keys/<key-name>/rotate. The new version becomes the current encryption key within one refresh interval (jeap.jwe.refresh.interval, default 5 minutes) and appears first in the JWKS. - Rely on the grace period. All active key versions remain valid for decryption, so clients holding a slightly older JWKS keep working through a rotation — no coordinated switch-over is needed.
- Decommission old versions deliberately by raising
jeap.jwe.vault.min-key-versionin the service configuration. Do this only after clients have had time to pick up the new key (at least one JWKS cache lifetime plus one refresh interval). Clients still using an evicted version receive400 JWE_UNKNOWN_KEY_ID, which instructs them to re-fetch the JWKS and retry — the jeap-jwe-client library does this automatically. - On suspected key compromise, rotate immediately and raise
jeap.jwe.vault.min-key-versionto the new version so the compromised versions are evicted from the key store and the JWKS right away, accepting the forced client refresh. - Keep the refresh interval short enough for your rotation-to-availability and compromise-response expectations; the default of 5 minutes is a sensible balance.
The rotation flow, including the eviction step, is illustrated in Vault integration.
Unauthenticated decrypt amplification
Every encrypted request (and every JWE-Response-Key envelope) costs one RSA-4096
private-key operation on the server. Mitigations, in the order they take effect:
- Authenticate first. The default filter ordering (Spring Security at
-100, JWE filter at0) rejects unauthenticated requests before any RSA work — keep this ordering unless you have a strong reason not to (see Using with jeap-security). - Bound the input.
jeap.jwe.filter.max-payload-bytes(default 5 MiB) caps both the encrypted request body and the response-key header, preventing memory exhaustion from unauthenticated input. - Rate-limit upstream. For endpoints that must accept encrypted traffic without authentication, apply rate limiting at the WAF / API gateway in front of the service.
JWE complements TLS — it does not replace it
Always run the service behind TLS. The JWE layer protects the payload end to end — through TLS-terminating intermediaries such as WAFs, load balancers, and reverse proxies that would otherwise see plaintext. It does not hide what TLS hides: URLs, request paths, headers, and traffic patterns remain visible to those intermediaries.
Errors and information exposure
- Protocol errors are returned as plain
application/problem+json(never encrypted) with a stable machine-readablecode— they contain no key material and no payload data. - The distinct
JWE_UNKNOWN_KEY_IDerror reveals onlykidvalidity, which is already public through the JWKS endpoint. - Structured error logs record the code, status, method and path — never plaintext payloads or key material.
Verifying encryption is active
The jeap.jwe.encryption.active gauge is 1 only when JWE is enabled, both enforcement
directions are on (require-encrypted-request and require-encrypted-response), and at least
one key is loaded — the signal a Governance service should monitor to detect a service whose
end-to-end encryption has been switched off or degraded. See
Observability (metrics).