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,DelegatingPasswordEncoderfor migration) or Bouncy Castle'sArgon2BytesGenerator. The JDK only offersPBKDF2WithHmacSHA256, chronically misused (low iterations, reused salts) — reach for the libraries. SecureRandom: the only acceptable randomness for keys, IVs, tokens.java.util.Random/ThreadLocalRandomare predictable. Default constructor is fine on modern JDKs/Linux; don't force/dev/randomblocking variants without cause.- JSSE/TLS:
SSLContext+ key managers (your identity) and trust managers (who you accept). Servers configure keystores; mutual TLS adds client certs andsetNeedClientAuth(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 (useMessageDigest.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 withMessageDigest.isEqualto stay constant-time.Cipher—init(mode, key, spec)thendoFinal; for GCM passnew GCMParameterSpec(128, iv). Associated data viaupdateAAD.KeyStore—KeyStore.getInstance("PKCS12"),load(stream, password); holds private keys + cert chains and trusted certs.SecretKeyFactory— derives keys from passwords (PBKDF2) viaPBEKeySpec.
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
- Security in Java applications — the map — parent overview of the Java security landscape.
- Cryptography for engineers — the primitives theory behind these APIs.
- TLS/HTTPS & certificates — certificate lifecycle around the JSSE config.
- Secrets management — the Vault strategy the framework integrations implement.
- keytool — the JDK tool managing the keystores used here.