rgoussu@goussu: ~/library/java/java-security
~/library/java/java-security cat cryptography-and-jca.md

Cryptography in Java — JCA/JSSE in practice

# The JCA provider architecture, the core crypto APIs, TLS via JSSE, keystores, Vault integrations and the classic pitfalls.

Conceptsaved 2026-08-09 #java#security#cryptography#tls#jca#secrets

Overview

The JDK ships a complete crypto stack: the Java Cryptography Architecture (JCA) defines provider-based engine classes for digests, MACs, ciphers, signatures and keys, and JSSE builds TLS on top of it. The design principle is algorithm independence — you ask for "AES/GCM/NoPadding" by name and a pluggable provider supplies the implementation — which is also its trap: the API happily hands you broken choices (ECB, static IVs, unsalted fast hashes). This note covers the architecture, the APIs worth knowing, the modern-default parameter choices, and where frameworks and Vault take over. Crypto theory proper lives in Cryptography for engineers.

Key points

  • Provider architecture: Cipher.getInstance("transformation") resolves against an ordered provider list (SUN, SunJCE, SunEC, SunJSSE...). Bouncy Castle registers as one more provider when you need algorithms the JDK lacks (or its lightweight API directly, bypassing JCA). The old JCE "unlimited strength policy files" ceased to exist years ago — full key lengths are the default.
  • The engine classes: MessageDigest (SHA-256), Mac (HmacSHA256), Cipher (encryption), Signature, KeyStore, KeyPairGenerator/KeyGenerator, SecretKeyFactory (password-based keys), SecureRandom.
  • Symmetric default: AES/GCM/NoPadding, 256-bit key, fresh random 12-byte IV per encryption, IV stored alongside the ciphertext, never reused with the same key.
  • Password hashing: Argon2id or BCrypt — via Spring Security Crypto's PasswordEncoder (Argon2PasswordEncoder, BCryptPasswordEncoder, DelegatingPasswordEncoder for migration) or Bouncy Castle's Argon2BytesGenerator. The JDK only offers PBKDF2WithHmacSHA256, chronically misused (low iterations, reused salts) — reach for the libraries.
  • SecureRandom: the only acceptable randomness for keys, IVs, tokens. java.util.Random/ThreadLocalRandom are predictable. Default constructor is fine on modern JDKs/Linux; don't force /dev/random blocking variants without cause.
  • JSSE/TLS: SSLContext + key managers (your identity) and trust managers (who you accept). Servers configure keystores; mutual TLS adds client certs and setNeedClientAuth(true) — all three frameworks expose this as config, not code.
  • Keystores: prefer PKCS#12 (.p12) over legacy JKS; managed with keytool.
  • Secrets don't belong in application.properties: Vault integrations — Spring Cloud Vault, quarkus-vault, micronaut-vault — inject them as config properties at runtime.
  • Pitfalls: ECB mode, static/reused IVs, getInstance("AES") (defaults to ECB!), MD5/SHA-1 for security purposes, string comparison of MACs (use MessageDigest.isEqual), and any DIY protocol design.

Details

Provider architecture

Every engine class is a facade: getInstance(algorithm) walks the registered providers in preference order and returns the first implementation (an SPI instance). Providers are listed in java.security or registered at runtime (Security.addProvider(new BouncyCastleProvider())), and a specific one can be pinned with the two-arg getInstance(alg, "BC"). Bouncy Castle earns its place for: Argon2, modern primitives ahead of the JDK, PGP/CMS/PKCS#10 certificate tooling, and a FIPS-certified variant. The JDK's own coverage is broad these days — including ChaCha20-Poly1305, EdDSA (JEP 339), and post-quantum ML-KEM/ML-DSA landing via recent JEPs — so add BC when you hit a gap, not by reflex.

The APIs in practice

  • MessageDigest.getInstance("SHA-256") — integrity/fingerprints, not passwords.
  • Mac.getInstance("HmacSHA256") — authentication of messages; compare results with MessageDigest.isEqual to stay constant-time.
  • Cipherinit(mode, key, spec) then doFinal; for GCM pass new GCMParameterSpec(128, iv). Associated data via updateAAD.
  • KeyStoreKeyStore.getInstance("PKCS12"), load(stream, password); holds private keys + cert chains and trusted certs.
  • SecretKeyFactory — derives keys from passwords (PBKDF2) via PBEKeySpec.

TLS and mutual TLS

JSSE assembles an SSLContext from a KeyManagerFactory (keystore = what you present) and a TrustManagerFactory (truststore = what you accept); TLS 1.3 and 1.2 are the only protocols worth enabling. In practice you configure rather than code it:

Server TLS / mTLS Client trust
Spring Boot SSL bundles (spring.ssl.bundle.jks.*) referenced by server and clients; server.ssl.client-auth=need for mTLS same bundle abstraction on RestClient/WebClient
Quarkus TLS registry quarkus.tls.<name>.key-store.* / .trust-store.*; quarkus.http.ssl.client-auth=required named TLS configs on REST/gRPC clients
Micronaut micronaut.server.ssl.* (client-authentication: need) micronaut.http.client.ssl.*

Never ship a trust-all TrustManager "to fix the handshake in dev" — fix the truststore. Certificate theory and rotation practice: TLS/HTTPS & certificates.

Secrets in configuration

Config files and env vars leak (repos, images, ps, crash dumps). The pattern all three frameworks share: a Vault client integrated into the configuration source, so db.password resolves from Vault's KV engine at startup with the app authenticating via AppRole/Kubernetes auth — Spring Cloud Vault (property source order ahead of files), quarkus-vault (config source + dynamic DB credentials), micronaut-vault (distributed configuration). Cloud-native equivalents (AWS Secrets Manager, GCP Secret Manager) plug in the same way. Broader rotation/lease strategy: Secrets management.

Examples

// AES-GCM encrypt — fresh IV per message, IV prepended to ciphertext
byte[] iv = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] ct = cipher.doFinal(plaintext);
byte[] out = ByteBuffer.allocate(iv.length + ct.length).put(iv).put(ct).array();
// Password hashing — Spring Security Crypto (usable without Spring Security itself)
PasswordEncoder encoder = new Argon2PasswordEncoder(16, 32, 1, 1 << 14, 2);
String stored = encoder.encode(rawPassword);
boolean ok = encoder.matches(attempt, stored);
# PKCS#12 keystore with a fresh keypair, then export the cert for a truststore
keytool -genkeypair -alias server -keyalg EC -storetype PKCS12 \
        -keystore server.p12 -validity 365 -dname "CN=api.example.com"
keytool -exportcert -alias server -keystore server.p12 -rfc -file server.crt

Related