Learn Java Security Cryptography Integrity Part 011 Asymmetric Cryptography Rsa Ec Eddsa Kem
title: Learn Java Security, Cryptography and Integrity - Part 011 description: Asymmetric cryptography in Java: RSA, elliptic curves, EdDSA context, key agreement, KEM, key serialization, parameter discipline, crypto agility, and production failure modes. series: learn-java-security-cryptography-integrity seriesTitle: Learn Java Security, Cryptography and Integrity order: 11 partTitle: Asymmetric Cryptography: RSA, EC, EdDSA, KEM tags:
- java
- security
- cryptography
- asymmetric-cryptography
- rsa
- elliptic-curve
- kem
- jca date: 2026-06-30
Part 011 — Asymmetric Cryptography: RSA, EC, EdDSA, KEM
Target: setelah part ini, kamu tidak hanya tahu cara memanggil
KeyPairGenerator,Cipher,Signature,KeyAgreement, atauKEM, tetapi mampu menilai apakah desain asymmetric crypto masuk akal, bisa dimigrasikan, dan tidak diam-diam membangun protokol rapuh.
Asymmetric cryptography adalah salah satu area security engineering yang sering membuat engineer overconfident. API-nya terlihat sederhana: generate key pair, encrypt with public key, decrypt with private key, sign, verify, exchange key. Realitanya, mayoritas kegagalan bukan karena RSA/ECC-nya “rusak”, tetapi karena primitive digunakan untuk tujuan yang salah, padding salah, parameter tidak eksplisit, canonicalization tidak stabil, key lifecycle tidak jelas, atau sistem tidak punya cara aman untuk rotasi.
Di Java modern, asymmetric crypto berada di bawah Java Cryptography Architecture (JCA) dan Java Cryptography Extension (JCE). Engine class yang paling relevan adalah:
KeyPairGeneratorKeyFactorySignatureCipherKeyAgreementKEMAlgorithmParameterSpecX509EncodedKeySpecPKCS8EncodedKeySpecEncodedKeySpec
Oracle Java SE 25 JCA reference guide mencantumkan Signature, Cipher, Mac, dan KEM sebagai high-level cryptographic classes, sementara Java Security Standard Algorithm Names mendefinisikan nama algoritma standar seperti RSA, RSASSA-PSS, EC, EdDSA, Ed25519, Ed448, X25519, dan ML-KEM.
Referensi utama:
- Java SE 25 JCA Reference Guide: https://docs.oracle.com/en/java/javase/25/security/java-cryptography-architecture-jca-reference-guide.html
- Java SE 25 Security Standard Algorithm Names: https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
- Java SE 25
KeyPairGenerator: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/security/KeyPairGenerator.html - Java SE 25
Signature: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/security/Signature.html - Java SE 25
KeyAgreement: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/javax/crypto/KeyAgreement.html - Java SE 25
KEM: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/javax/crypto/KEM.html - RFC 8017, PKCS #1 v2.2: https://www.rfc-editor.org/rfc/rfc8017.html
- RFC 8032, EdDSA: https://www.rfc-editor.org/rfc/rfc8032.html
- NIST FIPS 186-5, Digital Signature Standard: https://csrc.nist.gov/pubs/fips/186-5/final
- NIST SP 800-56A Rev. 3, Pair-Wise Key Establishment: https://csrc.nist.gov/pubs/sp/800/56/a/r3/final
1. Kaufman Deconstruction: Apa Skill yang Sebenarnya Harus Dikuasai?
Kaufman menyarankan skill kompleks dipecah menjadi sub-skill kecil yang bisa dipraktikkan dan dikoreksi. Untuk asymmetric crypto, breakdown efektifnya bukan “hafal algoritma”, tetapi menguasai tujuh capability berikut.
| Capability | Pertanyaan korektif | Output engineering |
|---|---|---|
| Primitive selection | Apakah kita butuh encryption, signature, key agreement, atau key encapsulation? | Primitive dipilih berdasarkan tujuan, bukan kebiasaan. |
| Parameter discipline | Padding, curve, key size, hash, provider, dan encoding sudah eksplisit? | Tidak bergantung pada default provider yang bisa berubah. |
| Key lifecycle | Siapa membuat key, menyimpan, memakai, merotasi, dan mencabut? | Key inventory dan operational runbook. |
| Protocol composition | Apakah primitive disusun menjadi protokol yang aman? | Tidak membuat “custom TLS/JWT/payment signature” tanpa model. |
| Identity binding | Public key itu milik siapa dan diverifikasi bagaimana? | Trust anchor, certificate, JWKS, registry, atau pinning strategy. |
| Agility | Bagaimana migrasi dari RSA ke EC/PQC atau dari parameter lama ke baru? | Metadata envelope dan dual-read/dual-write plan. |
| Failure analysis | Bagaimana sistem gagal jika key hilang, bocor, expired, salah provider, atau replay? | Incident path, blast-radius control, audit evidence. |
Mental model utama:
Top 1% engineer tidak bertanya “algoritma apa yang kuat?”, tetapi:
- Apa security property yang dibutuhkan?
- Primitive mana yang memberikan property itu?
- Apa parameter dan encoding yang harus dibekukan?
- Bagaimana public key diikat ke identity?
- Bagaimana rotasi dan migration dilakukan tanpa downtime?
- Bagaimana failure bisa terdeteksi sebelum menjadi breach?
2. Asymmetric Crypto Bukan Pengganti Symmetric Crypto
Asymmetric crypto mahal, terbatas, dan jarang dipakai untuk data besar. Dalam production, asymmetric crypto biasanya dipakai untuk:
- Key establishment: menyepakati atau mengirim symmetric key.
- Digital signature: membuktikan bahwa pemegang private key menandatangani data tertentu.
- Identity binding: public key dikaitkan dengan subject melalui certificate, JWKS, truststore, registry, atau konfigurasi.
- Envelope encryption: data dienkripsi dengan data encryption key (DEK), lalu DEK dilindungi dengan key encryption key (KEK), public key, KMS, atau KEM.
Anti-pattern paling umum:
public-key encrypt seluruh JSON besar
Masalahnya:
- RSA punya limit ukuran plaintext berdasarkan key size dan padding.
- Operasi asymmetric lebih mahal daripada AES-GCM/ChaCha20-Poly1305.
- Tanpa AEAD, integrity sering tidak lengkap.
- Developer sering salah memakai padding
RSA/ECB/PKCS1Paddingkarena contoh lama.
Pattern yang benar untuk confidentiality data besar:
Envelope minimal:
{
"version": 1,
"keyId": "payment-receiver-2026-q2",
"keyWrapAlg": "RSA-OAEP-256",
"contentAlg": "AES-256-GCM",
"aad": "base64url(...)",
"encryptedKey": "base64url(...)",
"nonce": "base64url(...)",
"ciphertext": "base64url(...)",
"tag": "base64url(...)"
}
Invariant:
Asymmetric encryption protects keys or tiny secrets; symmetric AEAD protects payloads.
3. Primitive Decision Matrix
| Use case | Prefer | Avoid | Notes |
|---|---|---|---|
| Encrypt large payload | AEAD + envelope key protection | Raw RSA over payload | Use AES-GCM/ChaCha20-Poly1305 for data. |
| Encrypt a symmetric key for a receiver | KEM or RSA-OAEP | RSA PKCS#1 v1.5 encryption | RSA-OAEP requires explicit hash/MGF parameters. |
| Sign data | Ed25519/Ed448, ECDSA, or RSA-PSS | MD5withRSA, SHA1withRSA, ad-hoc hash signing | Signature signs canonical bytes, not vague business object. |
| Establish shared secret | X25519/ECDH + KDF | Static DH without authentication | Key agreement does not authenticate by itself. |
| Modern encapsulation API | javax.crypto.KEM where provider supports it | Custom KEM composition | KEM separates encapsulation/decapsulation semantics. |
| Compatibility with legacy enterprise | RSA-OAEP / RSA-PSS | RSA defaults | Parameter pinning is non-negotiable. |
| Long-term migration to PQC | Algorithm-agile envelope | Hard-coded RSA/ECDSA fields | Keep crypto metadata versioned. |
Decision rule:
If you cannot explain the security property in one sentence, do not choose a primitive yet.
Examples:
- “Only payroll service can read this payload.” → confidentiality → envelope encryption.
- “The regulator can verify this decision package came from our platform.” → signature → canonical signing + key identity.
- “Two services need a fresh session key.” → key agreement/KEM + authentication + KDF.
- “We need to prove the database row was not tampered with.” → MAC/signature/hash-chain depending on verifier and trust model.
4. RSA in Java: Useful, Legacy-Compatible, Easy to Misuse
RSA remains common because of ecosystem compatibility: certificates, payment networks, banking APIs, enterprise integrations, HSM support, older clients, and compliance environments. But RSA has several sharp edges.
4.1 RSA Use Cases
| Goal | RSA construction | Java engine |
|---|---|---|
| Encrypt small key material | RSA-OAEP | Cipher |
| Sign data | RSASSA-PSS | Signature |
| Legacy signature verification | PKCS#1 v1.5 signature | Signature |
| Key generation | RSA / RSASSA-PSS | KeyPairGenerator |
Do not collapse these into “RSA encryption/signing”. RSA is a mathematical primitive, but security comes from the scheme: OAEP for encryption, PSS for signature, and exact parameterization.
4.2 RSA Key Generation
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.RSAKeyGenParameterSpec;
public final class RsaKeys {
public static KeyPair generateRsa3072() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(
new RSAKeyGenParameterSpec(3072, RSAKeyGenParameterSpec.F4),
SecureRandom.getInstanceStrong()
);
return generator.generateKeyPair();
}
}
Notes:
F4is the common public exponent 65537.- 2048-bit RSA may still appear in legacy systems, but new designs should evaluate 3072-bit or higher based on policy and lifetime.
- Key size is not the only decision. Padding, hash, provider, storage, and rotation matter more often in application failures.
4.3 RSA-OAEP for Key Wrapping
Unsafe old pattern:
Cipher cipher = Cipher.getInstance("RSA");
This is bad because transformation defaults are provider-dependent and may imply legacy padding. Never rely on that.
Better pattern with explicit transformation and parameter spec:
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.PublicKey;
import java.security.spec.MGF1ParameterSpec;
public final class RsaOaepWrap {
public static byte[] wrapKey(byte[] dek, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaep = new OAEPParameterSpec(
"SHA-256",
"MGF1",
MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT
);
cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaep);
return cipher.doFinal(dek);
}
}
Important: ECB in this transformation string is a historical naming artifact for RSA transformations in JCA. It does not mean block-cipher ECB mode over RSA payload. Still, the string is confusing, which is another reason crypto configuration must be centralized and reviewed.
4.4 RSA-OAEP Decryption
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.PrivateKey;
import java.security.spec.MGF1ParameterSpec;
public final class RsaOaepUnwrap {
public static byte[] unwrapKey(byte[] encryptedKey, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaep = new OAEPParameterSpec(
"SHA-256",
"MGF1",
MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT
);
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaep);
return cipher.doFinal(encryptedKey);
}
}
Security review questions:
- Are OAEP hash and MGF1 hash both explicit?
- Is the transformation string centrally defined?
- Does envelope metadata record
RSA-OAEP-256or equivalent? - Are decryption failures generic, not used as oracle feedback?
- Is plaintext DEK zeroized where practical after use?
- Is private key held in HSM/KMS where policy requires it?
4.5 RSA Failure Modes
| Failure | Root cause | Consequence |
|---|---|---|
Cipher.getInstance("RSA") | Provider default ambiguity | Legacy padding or inconsistent behavior. |
| Encrypting payload directly | Misunderstood RSA limit | Size failure or insecure chunking. |
| RSA PKCS#1 v1.5 encryption | Legacy compatibility | Padding oracle risk in protocol contexts. |
| Different OAEP MGF hash | Interop mismatch | Production decrypt failures. |
| Detailed decrypt errors | Oracle surface | Attackers learn validity differences. |
| No key ID | Ambiguous recipient key | Rotations break or wrong key used. |
| Reusing key for encryption and signing | Poor key separation | Cross-protocol risk and audit confusion. |
Invariant:
RSA is not one algorithmic decision. It is key size + purpose + scheme + padding + hash + provider + encoding + lifecycle.
5. Elliptic Curve Cryptography: Smaller Keys, Different Failure Modes
Elliptic curve cryptography is widely used for signatures and key agreement because it offers strong security with smaller keys and often better performance than comparable RSA security levels. But it also introduces curve selection, point validation, parameter encoding, and protocol composition issues.
5.1 EC Use Cases in Java
| Use case | API | Typical algorithms |
|---|---|---|
| Key agreement | KeyAgreement | ECDH, X25519, X448 depending on provider/platform. |
| Signature | Signature | SHA256withECDSA, Ed25519, Ed448, EdDSA. |
| Key generation | KeyPairGenerator | EC, Ed25519, X25519. |
| Key decoding | KeyFactory | EC, EdDSA, XDH. |
Java SE 25 requires support for standard KeyAgreement algorithms including DiffieHellman, ECDH with specific curves, and X25519, according to the KeyAgreement API documentation.
5.2 ECDH Is Not Authentication
ECDH derives a shared secret. It does not prove who you are talking to unless public keys are authenticated.
Without authentication, a man-in-the-middle can establish two different shared secrets. TLS solves this through certificates and transcript verification. If you compose ECDH manually, you must solve authentication, key confirmation, transcript binding, replay prevention, and KDF context separation yourself.
5.3 X25519 Key Agreement Example
import javax.crypto.KeyAgreement;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
public final class X25519Agreement {
public static KeyPair generate() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("X25519");
return generator.generateKeyPair();
}
public static byte[] sharedSecret(KeyPair ownKeyPair, PublicKey peerPublicKey) throws Exception {
KeyAgreement agreement = KeyAgreement.getInstance("X25519");
agreement.init(ownKeyPair.getPrivate());
agreement.doPhase(peerPublicKey, true);
return agreement.generateSecret();
}
}
Do not use raw shared secret directly as an AES key. Feed it into a KDF with transcript/context binding.
Pseudo-pattern:
byte[] rawSharedSecret = sharedSecret(ownEphemeralKeyPair, peerEphemeralPublicKey);
byte[] sessionKey = hkdfExtractAndExpand(
rawSharedSecret,
salt,
"service-a|service-b|protocol-v1|handshake-hash".getBytes(UTF_8),
32
);
The actual HKDF implementation may come from a vetted library/provider. The important design rule is that key derivation must bind the shared secret to a protocol context. Without context binding, the same low-level secret can be reused across protocols or purposes.
5.4 ECDH/ECDSA Curve Selection
Common enterprise curve families:
| Family | Examples | Notes |
|---|---|---|
| NIST prime curves | P-256/secp256r1, P-384/secp384r1 | Common in TLS, certificates, FIPS environments. |
| Montgomery curves | X25519, X448 | Used for key agreement; simple and widely deployed. |
| Edwards curves | Ed25519, Ed448 | Used for signatures; deterministic signing design. |
Selection constraints:
- Regulatory/FIPS contexts may constrain provider and curve choices.
- Interoperability with external partners may force RSA or P-256/P-384.
- TLS/JWT/payment ecosystems may dictate algorithms.
- If a hardware security module does not support a curve, the operational design may need RSA/P-256 even if Ed25519 is attractive.
A mature design does not ask “which curve is best?” in isolation. It asks:
Which algorithm is supported by our clients, providers, HSM/KMS, audit requirements,
certificate ecosystem, and migration plan?
6. EdDSA Context: Signature Primitive, Not Key Agreement
EdDSA, including Ed25519 and Ed448, is a digital signature scheme standardized by RFC 8032. Java standard algorithm names include EdDSA, Ed25519, and Ed448. Oracle provider documentation notes that EdDSA key generation can be initialized with Ed25519 or Ed448 parameters, with Ed25519 as default when unparameterized in the referenced provider behavior.
Important distinctions:
| Algorithm | Purpose |
|---|---|
| Ed25519 / Ed448 | Signature |
| X25519 / X448 | Key agreement |
| ECDSA with P-256/P-384 | Signature |
| ECDH with P-256/P-384 | Key agreement |
Do not treat these as interchangeable because the numbers look similar. Ed25519 is not X25519.
Example key generation for Ed25519:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
public final class Ed25519Keys {
public static KeyPair generate() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("Ed25519");
return generator.generateKeyPair();
}
}
Signing itself is covered deeply in Part 012. In this part, the key idea is classification:
EdDSA = signature primitive.
XDH/X25519 = key agreement primitive.
KEM = key encapsulation primitive.
7. Key Encapsulation Mechanism (KEM)
A KEM allows a sender to generate a secret key and an encapsulation message using the receiver's public key. The receiver uses its private key to decapsulate the message and recover the same secret key. Java SE provides javax.crypto.KEM as the engine class for KEM functionality.
KEM mental model:
A KEM is not a bulk encryption algorithm. It is a clean way to derive/protect symmetric keys. This maps naturally to envelope encryption and to post-quantum migration, because modern PQC standards include KEM algorithms such as ML-KEM.
Conceptual Java shape:
import javax.crypto.KEM;
import javax.crypto.SecretKey;
import java.security.PrivateKey;
import java.security.PublicKey;
public final class KemEnvelopeConcept {
public record EncapsulatedKey(byte[] encapsulation, SecretKey secretKey) {}
public static EncapsulatedKey encapsulate(PublicKey receiverPublicKey) throws Exception {
KEM kem = KEM.getInstance("DHKEM"); // Example only: actual availability is provider-specific.
KEM.Encapsulator encapsulator = kem.newEncapsulator(receiverPublicKey);
KEM.Encapsulated encapsulated = encapsulator.encapsulate();
return new EncapsulatedKey(encapsulated.encapsulation(), encapsulated.key());
}
public static SecretKey decapsulate(
PrivateKey receiverPrivateKey,
byte[] encapsulation,
String algorithm
) throws Exception {
KEM kem = KEM.getInstance(algorithm);
KEM.Decapsulator decapsulator = kem.newDecapsulator(receiverPrivateKey);
return decapsulator.decapsulate(encapsulation);
}
}
Review warning:
- The exact KEM algorithm name and provider availability must be verified in your target JDK/provider.
- Do not fake a KEM with hand-rolled RSA/ECDH glue unless a protocol specification defines it.
- Record KEM algorithm, parameter set, key ID, provider policy, and payload algorithm in the envelope.
KEM envelope metadata example:
{
"version": 2,
"kemAlg": "ML-KEM-768",
"recipientKeyId": "regulator-pqc-test-2026-01",
"contentAlg": "AES-256-GCM",
"encapsulation": "base64url(...)"
}
This does not mean every application should deploy PQC today. It means designs created today should avoid hard-coding “RSA forever”.
8. Key Serialization: Public, Private, and Dangerous Encodings
In Java, keys are objects. To persist or transmit them, you usually use encoded specifications.
| Data | Common encoding | Java spec |
|---|---|---|
| Public key | X.509 SubjectPublicKeyInfo DER | X509EncodedKeySpec |
| Private key | PKCS#8 DER | PKCS8EncodedKeySpec |
| Certificate | X.509 certificate | java.security.cert.X509Certificate |
| Secret key | Raw bytes or provider-specific | SecretKeySpec, keystore/HSM preferred |
8.1 Decode Public Key
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public final class PublicKeyLoading {
public static PublicKey loadRsaPublicKeyFromBase64(String base64Der) throws Exception {
byte[] der = Base64.getDecoder().decode(base64Der);
X509EncodedKeySpec spec = new X509EncodedKeySpec(der);
return KeyFactory.getInstance("RSA").generatePublic(spec);
}
}
8.2 Decode Private Key
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public final class PrivateKeyLoading {
public static PrivateKey loadRsaPrivateKeyFromBase64(String base64Der) throws Exception {
byte[] der = Base64.getDecoder().decode(base64Der);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der);
return KeyFactory.getInstance("RSA").generatePrivate(spec);
}
}
Security warning:
PKCS8EncodedKeySpecmay contain private key material in process memory.- Do not load long-lived private keys from application config unless your architecture explicitly accepts that risk.
- Prefer hardware-backed or managed key storage when key compromise blast radius is high.
- If private keys are file-based, permissions, encryption-at-rest, rotation, backup, and access audit matter.
8.3 PEM Is a Container Format, Not an Algorithm
PEM is base64 with header/footer. It is not a crypto algorithm.
Examples:
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
Do not infer too much from PEM header alone. The inner DER structure and algorithm identifier determine how it should be parsed.
9. Identity Binding: The Most Ignored Part
Asymmetric crypto only gives you math over keys. It does not tell you whose key it is.
A public key must be bound to identity through a trust mechanism:
| Mechanism | Typical use | Risk |
|---|---|---|
| X.509 certificate chain | TLS, mTLS, enterprise PKI | Misconfigured truststore, weak hostname verification. |
| JWKS endpoint | OIDC/JWT ecosystems | Key confusion, issuer mismatch, cache poisoning. |
| Static pinned public key | Small internal systems | Rotation pain, emergency recovery pain. |
| Database key registry | Internal signing/verifying | Access-control and audit integrity required. |
| KMS/HSM key reference | Cloud/regulated workloads | Vendor semantics, IAM policy, region/data boundary. |
Verification question:
When this code verifies a signature or encrypts for a recipient,
how does it know the public key belongs to the intended identity?
If the answer is “because the client sent it”, the design is usually broken.
Example broken pattern:
// Attacker supplies both payload and public key.
boolean valid = verify(payload, signature, request.publicKey());
Why broken?
Anyone can generate a key pair, sign malicious payload, and submit the matching public key. A signature proves only possession of the private key corresponding to the submitted public key. It does not prove authorization unless the public key is trusted and bound to an identity/policy.
Better shape:
// Public key is resolved from trusted registry based on authenticated issuer/key ID.
PublicKey trustedKey = trustedKeyRegistry.resolve(issuer, keyId, purpose, now);
boolean valid = verify(canonicalPayload, signature, trustedKey);
10. Algorithm Agility for Asymmetric Crypto
Crypto agility means the system can change algorithm/parameters without corrupting data, breaking interoperability, or forcing unsafe emergency migrations.
Bad envelope:
{
"encryptedKey": "...",
"ciphertext": "..."
}
Good envelope:
{
"version": 3,
"recipient": "case-management-platform",
"recipientKeyId": "case-platform-rsa-2026-01",
"keyProtection": {
"type": "RSA-OAEP",
"hash": "SHA-256",
"mgf": "MGF1",
"mgfHash": "SHA-256"
},
"contentEncryption": {
"alg": "AES-GCM",
"keyLength": 256,
"nonceLength": 12,
"tagLength": 128
},
"createdAt": "2026-06-30T00:00:00Z",
"encryptedKey": "base64url(...)"
}
For signatures:
{
"version": 1,
"signatureAlg": "Ed25519",
"keyId": "decision-signer-2026-q2",
"canonicalization": "case-decision-c14n-v1",
"signedAt": "2026-06-30T00:00:00Z",
"signature": "base64url(...)"
}
Migration pattern:
Important distinction:
- Read path often needs to support old algorithms for historical data.
- Write path should move to current approved algorithms earlier.
- Verification policy must account for the time of signing, key validity at signing time, and evidence retention requirements.
11. Java API Design Pattern: Centralize Crypto Profiles
Do not scatter algorithm strings across application code.
Bad:
Signature.getInstance("SHA256withRSA");
Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
KeyPairGenerator.getInstance("RSA");
Repeated everywhere, this creates drift.
Better:
public enum AsymmetricCryptoProfile {
RSA_OAEP_256_AES_GCM(
"RSA",
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
"SHA-256",
"MGF1",
"SHA-256"
),
ED25519_SIGNATURE(
"Ed25519",
null,
null,
null,
null
);
private final String keyAlgorithm;
private final String cipherTransformation;
private final String digest;
private final String maskGenerationFunction;
private final String maskGenerationDigest;
AsymmetricCryptoProfile(
String keyAlgorithm,
String cipherTransformation,
String digest,
String maskGenerationFunction,
String maskGenerationDigest
) {
this.keyAlgorithm = keyAlgorithm;
this.cipherTransformation = cipherTransformation;
this.digest = digest;
this.maskGenerationFunction = maskGenerationFunction;
this.maskGenerationDigest = maskGenerationDigest;
}
public String keyAlgorithm() {
return keyAlgorithm;
}
public String cipherTransformation() {
return cipherTransformation;
}
}
Production-grade version should include:
- allowed purposes,
- minimum key sizes,
- provider requirements,
- deprecation date,
- envelope metadata mapping,
- FIPS/compliance notes,
- test vectors,
- migration policy.
12. Side Channels and Error Oracles
Application developers rarely implement RSA/ECC arithmetic directly, but they still create side channels at protocol/application level.
12.1 Timing and Error Messages
Bad API behavior:
{ "error": "invalid OAEP padding" }
Better:
{ "error": "invalid encrypted message" }
Even better:
- same HTTP status for decryption/verification failure,
- generic client-facing error,
- detailed internal security event with correlation ID,
- rate limiting,
- no difference between “unknown key id” and “bad ciphertext” where oracle risk exists.
12.2 Logging Sensitive Material
Never log:
- private key,
- raw shared secret,
- DEK,
- unwrapped key,
- plaintext sensitive payload,
- full signed payload if it contains secrets/PII and logs are not authorized for that data,
- signature verification internals that leak key lookup semantics.
Safe-ish to log with care:
- key ID,
- algorithm profile,
- envelope version,
- verification result,
- failure category after normalization,
- correlation ID,
- issuer/subject if not sensitive and allowed by policy.
13. Testing Strategy for Asymmetric Crypto
A useful test suite does not merely test “happy path encrypt/decrypt”. It tests boundaries and misuse.
13.1 Test Categories
| Test | Purpose |
|---|---|
| Known-answer tests | Ensure algorithm/provider behavior matches expected vectors where possible. |
| Round-trip tests | Ensure generated envelope can decrypt/verify. |
| Tamper tests | Changing ciphertext/signature/AAD/key ID must fail. |
| Wrong-key tests | Using another key must fail. |
| Migration tests | Old envelopes verify/decrypt after new profile introduced. |
| Provider tests | Production provider supports required algorithms. |
| Metadata tests | Missing algorithm/key/version rejected safely. |
| Error normalization tests | External error does not leak oracle details. |
13.2 Example Tamper Test Shape
import static org.junit.jupiter.api.Assertions.assertThrows;
class EnvelopeEncryptionTest {
@org.junit.jupiter.api.Test
void changingRecipientKeyIdMustNotSilentlyDecrypt() {
EncryptedEnvelope envelope = encryptFor("receiver-key-2026-q2", payload());
EncryptedEnvelope tampered = envelope.withRecipientKeyId("receiver-key-2025-q4");
assertThrows(SecurityException.class, () -> decrypt(tampered));
}
@org.junit.jupiter.api.Test
void changingCiphertextMustFailAuthentication() {
EncryptedEnvelope envelope = encryptFor("receiver-key-2026-q2", payload());
EncryptedEnvelope tampered = envelope.withCiphertext(flipOneBit(envelope.ciphertext()));
assertThrows(SecurityException.class, () -> decrypt(tampered));
}
}
Testing invariant:
Every bit of metadata that changes security meaning must be authenticated or verified.
14. Production Review Checklist
Use this before approving any asymmetric crypto design.
Primitive Choice
- Is the security goal explicit: confidentiality, authenticity, integrity, key establishment, evidence?
- Is asymmetric crypto used only where appropriate?
- Is payload encryption done via symmetric AEAD, not raw public-key encryption?
- Is signature used for origin/integrity, not as a substitute for authorization?
Algorithms and Parameters
- Are algorithm names explicit and centralized?
- Are padding/hash/MGF parameters explicit for RSA-OAEP/RSA-PSS?
- Are legacy algorithms rejected on new writes?
- Is provider behavior verified in CI/runtime startup?
- Is cryptographic metadata versioned in envelope/signature records?
Key Management
- Are keys separated by purpose?
- Does every key have
kid, owner, purpose, status, creation time, expiry/rotation policy? - Are private keys protected by KMS/HSM/keystore appropriate to risk?
- Is key compromise response documented?
- Is public key identity binding trustworthy?
Protocol and Data Binding
- Is AAD used to bind envelope metadata to ciphertext?
- Is key agreement output passed through a KDF with context?
- Are signatures over canonical bytes with domain separation?
- Are replay and downgrade attacks considered?
- Are errors normalized to avoid oracles?
Migration
- Can the system dual-read old and new algorithms?
- Can it dual-sign or dual-encrypt if needed?
- Is there a deprecation and removal policy?
- Are historical records verifiable after key rotation?
15. Hands-On Lab
Build a small Java module called crypto-envelope-lab.
Lab 1 — RSA-OAEP Envelope
Implement:
EncryptedEnvelope encryptFor(PublicKey receiverKey, byte[] plaintext, byte[] aad)
byte[] decryptWith(PrivateKey receiverKey, EncryptedEnvelope envelope)
Requirements:
- Generate random 256-bit DEK.
- Encrypt payload with AES-GCM.
- Wrap DEK with RSA-OAEP SHA-256/MGF1 SHA-256.
- Include version, key ID, key wrap algorithm, content algorithm, nonce, AAD hash, encrypted key, ciphertext, tag.
- Reject missing/unknown algorithm.
- Reject tampered metadata.
Lab 2 — X25519 Key Agreement
Implement:
SessionKeys deriveSessionKeys(ownEphemeralPrivate, peerEphemeralPublic, transcriptHash)
Requirements:
- Use
KeyAgreement.getInstance("X25519"). - Derive output using HKDF or a vetted KDF.
- Bind transcript hash and protocol label.
- Derive separate client-to-server and server-to-client keys.
- Write tests proving swapped context produces different keys.
Lab 3 — Crypto Profile Registry
Create:
CryptoProfileRegistry
KeyRegistry
EnvelopeValidator
Requirements:
- No raw algorithm string outside registry.
- Every profile has version, purpose, provider expectation, and deprecation status.
- Startup fails if required algorithms are unavailable.
- Test legacy read / modern write migration.
16. Common Interview/Review Questions
Q1. Why not encrypt everything with RSA public key?
Because RSA is not designed for bulk data encryption. Use hybrid encryption: symmetric AEAD for payload, asymmetric key protection for the DEK.
Q2. What does ECDH prove?
It derives a shared secret. By itself, it does not authenticate the peer. You need certificates, signatures, pre-trusted keys, or another authentication layer.
Q3. What is the difference between Ed25519 and X25519?
Ed25519 is for digital signatures. X25519 is for key agreement. They are not interchangeable.
Q4. Why is Cipher.getInstance("RSA") dangerous?
Because it relies on provider defaults. Secure designs pin transformation and parameters explicitly.
Q5. What is KEM good for?
KEM encapsulates a symmetric secret to a receiver's public key and maps naturally to envelope encryption and PQC migration.
Q6. What is public-key identity binding?
It is the mechanism that proves a public key belongs to a specific identity/purpose: certificate chain, JWKS issuer, trusted registry, or pinned configuration. Without identity binding, signature verification can be meaningless.
17. Mental Compression
Remember this:
RSA-OAEP encrypts keys.
RSA-PSS signs data.
ECDH/X25519 derives shared secrets.
Ed25519 signs data.
KEM encapsulates keys.
AEAD encrypts payloads.
Certificates/registries bind keys to identity.
Metadata makes migration possible.
The primitive is only one layer. The secure system is:
primitive + parameters + provider + key identity + lifecycle + protocol binding + error behavior + migration plan
If any of those are missing, the design may still compile, pass unit tests, and be insecure.
18. What Comes Next
Part 012 focuses on digital signatures specifically:
- what signatures actually prove,
- what they do not prove,
- canonical data signing,
- detached signatures,
- non-repudiation boundaries,
- Java
SignatureAPI, - RSA-PSS vs Ed25519,
- verification pipeline design,
- audit-grade integrity patterns.
You just completed lesson 11 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.