Skip to main content

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-256 with 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, jose in 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:

  1. A fresh random 96-bit IV is generated for every JWE by the JOSE library (Nimbus on the server, jose on the client). IVs are never supplied by configuration or application code, and never reused across messages.
  2. 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-Key header. 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, and JweProperties.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-version in 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 receive 400 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-version to 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 at 0) 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-readable code — they contain no key material and no payload data.
  • The distinct JWE_UNKNOWN_KEY_ID error reveals only kid validity, 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).