Skip to main content

FIPS 140-3 for Java: Oracle Jipher, Spring Boot TLS and JWT, and What You Can Verify Without It

What FIPS 140-3 means for a Java service, what Oracle Jipher 20 is and requires, and a runnable Spring Boot 4.1 service with HTTPS and an ES256 JWT that shows which JCA provider serves every algorithm and which TLS settings Oracle’s guide asks for. I could not run Jipher itself, and the post says so.

A bank, a government contract or a healthcare customer may ask one question about your Java service: is the cryptography FIPS 140-3 validated? Stock OpenJDK cannot answer yes, because none of its providers is validated. Oracle’s answer is Jipher, a JCE provider that routes Java crypto calls into a validated OpenSSL module. This post explains what Jipher is, what Oracle’s own guide asks you to configure, and then builds a Spring Boot service with HTTPS and a JWT so you can see, with real output, which provider answers each call.
Read this first: I did not run Jipher. Jipher is a commercial component of Oracle’s Java Verified Portfolio, for Java SE Subscription customers, and runs on Oracle JDK. I had neither in my sandbox. Everything below about Jipher comes from Oracle’s documentation (cited at the end). Everything shown as output is from a stock Eclipse Temurin 25.0.4.1 JDK, which has no FIPS-validated provider. The demo shows the baseline you would be changing, and that the TLS settings in Oracle’s guide take effect. It does not show a FIPS-compliant system, and nothing here is a compliance claim.

What FIPS 140-3 asks of Java, in one paragraph

FIPS 140-3 is a US standard for cryptographic modules. A module is validated by a lab and listed by NIST’s CMVP; your application is not. So “FIPS mode” means: all cryptography in your process comes from a validated module, using only approved algorithms and parameters. Oracle’s Jipher guide states the Java problem directly: no JDK provider is CMVP-certified, so Jipher has to be the only provider supplying cryptography.

What Jipher is

From Oracle’s documentation:
  • A Java Cryptographic Service Provider (registered as JipherJCE) that packages a FIPS 140-3 validated OpenSSL cryptographic module and exposes it through the standard JCA.
  • Jipher 20 was announced on 15 September 2026. It supports Oracle JDK 17, 21, 25 and 27 and later, on Oracle Linux 8, 9 and 10 (x64 and aarch64), macOS 26 (aarch64) and Windows Server 2025 (x64). It is included in the Java Verified Portfolio and commercially supported for Java SE Subscription customers.
  • Jipher 20 adds ML-KEM and ML-DSA (see my post-quantum post for those in the JDK itself) and HKDF, and from JDK 25 calls OpenSSL through the Foreign Function and Memory API instead of bundling native code in the JAR. It also stops enabling some non-approved algorithms by default.
  • Jipher 20 supersedes Jipher 10, and Oracle advises customers to plan the move.
Two caveats from Oracle’s own text deserve attention. The announcement page I read gave no certificate number. And the Jipher 10 user guide says its default OpenSSL build, which carries security patches, is not strictly FIPS 140-3 compliant, and recommends you disclose that choice to customers and auditors. A second build from the exact validated source is embedded too. Ask Oracle which build your auditor will accept before you rely on it. Because Jipher needs Oracle JDK, the licence change in my post on Oracle JDK 21 free updates ending applies to you.

What Oracle’s guide asks you to configure

The configuration below is from the Jipher 10 user guide (version 10.37). I did not read the Jipher 20 guide, so check it for differences. Registering the provider first, in java.security:
security.provider.1=JipherJCE
security.provider.2=SUN
security.provider.3=SunRsaSign
security.provider.4=SunJSSE
The guide’s other requirements that matter for a web service:
  • TLS 1.2 and 1.3 only; SSLv3, TLSv1 and TLSv1.1 in jdk.tls.disabledAlgorithms.
  • Named groups limited to secp256r1, secp384r1, secp521r1 and ffdhe2048/3072/4096; jdk.tls.ephemeralDHKeySize=2048.
  • Cipher suites built from ECDHE or DHE key exchange, ECDSA or RSA authentication, AES and GCM (CBC is “supported but not recommended”). RSA key-exchange suites are not supported.
  • Keys: RSA of at least 2048 bits, or EC on secp224r1, secp256r1, secp384r1 or secp521r1.
  • Use SSLContext.getInstance("TLS"). The guide warns that getInstance("TLS1.3") sets a ceiling that still allows TLS 1.0 and 1.1.
  • The JVM option --add-exports=java.base/sun.security.internal.spec=ALL-UNNAMED on the class path.
  • Native libraries are extracted at runtime into a temp directory, which must not be mounted noexec; point jipher.user.dir elsewhere if it is.
  • Jipher is skipped for algorithms it does not allow or implement, and those fall through to the next provider. So registering it first is not enough: you must also remove non-approved algorithms (the guide names MD5, MD2, SHA-1 for signatures and RSA PKCS#1 padding for key transport) from your application.
That last point is the one teams miss, and it is exactly what the next section lets you check.

The demo service

FipsDemoApplication.java is one Spring Boot 4.1.1 class with three endpoints: POST /token signs an ES256 JWT with the JCA, GET /whoami verifies it, and GET /providers reports which provider serves each algorithm the service uses. HTTPS and the TLS restrictions come from configuration. The JWT is hand-written to keep the dependency list to spring-boot-starter-webmvc and to make every JCA call visible; for a real service, use a maintained JWT library (I did not test any with Jipher).
Download the code. FipsDemoApplication.java. Put it under src/main/java/demo/ of a Spring Boot 4.1.1 project with the pom.xml, application.properties and fips-posture.security below (all in the accordions). Build with mvn package on JDK 25.
Project files: pom.xml, application.properties, fips-posture.security
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
    <relativePath/>
  </parent>
  <groupId>com.ankurm</groupId>
  <artifactId>fips-demo</artifactId>
  <version>1.0.0</version>
  <properties><java.version>25</java.version></properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
src/main/resources/application.properties:
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=${TLS_KEYSTORE:tls.p12}
server.ssl.key-store-type=PKCS12
server.ssl.key-store-password=changeit
server.ssl.key-alias=srv
# TLS 1.2 and 1.3 only, as Oracle's Jipher guide requires
server.ssl.enabled-protocols=TLSv1.3,TLSv1.2
# AES-GCM suites with ECDHE only (the Jipher guide's supported components)
server.ssl.ciphers=TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
fips-posture.security (values from Oracle’s Jipher guide):
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, MD5withRSA, DH keySize < 2048, RSA keySize < 2048
jdk.certpath.disabledAlgorithms=MD2, MD5
The keystore is a throwaway EC P-256 certificate made with the same keytool command as in my post-quantum post, alias srv.
Start it with the posture settings applied:
$ java -Djava.security.properties=fips-posture.security \
       -Djdk.tls.namedGroups=secp256r1,secp384r1,secp521r1,ffdhe2048,ffdhe3072,ffdhe4096 \
       -jar target/fips-demo-1.0.0.jar

Which provider answers? The baseline on a stock JDK

$ curl -s --cacert tls.pem https://localhost:8443/providers | python3 -m json.tool --indent 2
{
  "javaVendor": "Eclipse Adoptium",
  "javaVersion": "25.0.4.1",
  "providerOrder": [
    "SUN",
    "SunRsaSign",
    "SunEC",
    "SunJSSE",
    "SunJCE",
    "SunJGSS",
    "SunSASL",
    "XMLDSig",
    "SunPCSC",
    "JdkLDAP",
    "JdkSASL",
    "SunPKCS11"
  ],
  "serving": {
    "MessageDigest SHA-256": "SUN",
    "Signature SHA256withECDSA": "SunEC",
    "Signature SHA256withRSA": "SunRsaSign",
    "Mac HmacSHA256": "SunJCE",
    "Cipher AES/GCM/NoPadding": "SunJCE",
    "SecretKeyFactory PBKDF2WithHmacSHA256": "SunJCE",
    "SecureRandom (default)": "SUN NativePRNG",
    "MessageDigest MD5 (not FIPS-allowed)": "SUN",
    "MessageDigest SHA-1 (not for signatures)": "SUN"
  }
}
Every algorithm is served by a JDK provider, which is the point: with this configuration, none of it comes from a validated module. Also note the last two lines. MD5 and SHA-1 are happily available. With Jipher registered first, Oracle’s guide says Jipher is skipped for algorithms that are not FIPS-allowed, so those two calls would fall through to SUN and succeed silently unless you remove them from your code. This endpoint is the check I would run in a test, after registering Jipher, to confirm the “serving” column no longer names a JDK provider for the algorithms you care about. I could not run that second half.

The JWT round trip

$ curl -s -X POST --cacert tls.pem "https://localhost:8443/token?user=alice"
{"signatureProvider":"SunEC","token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsImV4cCI6MTc5MTgyNzcyMH0.oNSujDUz4dhtjgr8oR98gC8PgVjxYjQEBO3iCQp6-JbMhN_f9a9ufaMpnlvqDywlXK0KQv_brKsBlOhZ2Iu1KQ"}

$ curl -s --cacert tls.pem -H "Authorization: Bearer $TOKEN" https://localhost:8443/whoami
{"claims":"{\"sub\":\"alice\",\"exp\":1791827720}","verifyProvider":"SunEC","verified":true}
$ same, last character of the signature changed
{"error":"bad signature"} [HTTP 401]
$ same, no Authorization header
{"error":"missing bearer token"} [HTTP 401]
ES256 signing and verification are done by SunEC. Under Jipher you would expect that line to say JipherJCE, and that is the thing to assert in a test. One implementation detail worth knowing: JWS wants the raw R||S signature, so the code asks the JCA for SHA256withECDSAinP1363Format instead of converting the default DER output by hand.

Does the TLS posture take effect?

The server runs with TLS 1.2 and 1.3, GCM suites, and the named-group list from Oracle’s guide. I probed it with OpenSSL’s client (OpenSSL 3.0.13):
$ openssl s_client probes against the Spring Boot server (JDK 25, posture settings)
TLS 1.1 only                                              FAIL (no session established)
TLS 1.2, CBC suite only (ECDHE-ECDSA-AES256-SHA384)       FAIL (no session established)
TLS 1.2, AES-GCM ECDHE-ECDSA suite                        OK   TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
TLS 1.3 default                                           OK   TLSv1.3 TLS_AES_256_GCM_SHA384
TLS 1.3, client offers only X25519                        FAIL (no session established)
TLS 1.3, client offers only P-256                         OK   TLSv1.3 TLS_AES_256_GCM_SHA384
(The first line is the heading my probe script printed; the OK and FAIL lines are my script’s summary of OpenSSL’s New, ... Cipher is line, not raw OpenSSL output.) Old protocol: refused. CBC-only client: refused. A client that offers only X25519: refused, because X25519 is not in the permitted group list, while P-256 works. This also matters for the JDK 27 hybrid group: X25519MLKEM768 is not on Oracle’s Jipher 10 list either, so a FIPS posture and default JDK 27 hybrid TLS pull in different directions. I did not check what Jipher 20 allows.

A checklist before you tell an auditor anything

  1. Confirm your JDK is Oracle JDK on a platform Jipher supports, and that you hold the subscription.
  2. Register Jipher first and remove other providers you do not need.
  3. Run a test that asserts, for every algorithm you use, that the serving provider is JipherJCE.
  4. Remove non-approved algorithms from your code and dependencies (search for MD5, SHA-1 signatures, RSA PKCS#1 key transport).
  5. Apply the TLS settings above and probe them as shown.
  6. Ask Oracle which Jipher build and which certificate your audit will reference, and record it.

Further reading

No Comments yet!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.