Series MapLesson 11 / 35
Build CoreOrdered learning track

Learn Java Security Cryptography Integrity Part 011 Asymmetric Cryptography Rsa Ec Eddsa Kem

19 min read3613 words
PrevNext
Lesson 1135 lesson track0719 Build Core

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, atau KEM, 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:

  • KeyPairGenerator
  • KeyFactory
  • Signature
  • Cipher
  • KeyAgreement
  • KEM
  • AlgorithmParameterSpec
  • X509EncodedKeySpec
  • PKCS8EncodedKeySpec
  • EncodedKeySpec

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:


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.

CapabilityPertanyaan korektifOutput engineering
Primitive selectionApakah kita butuh encryption, signature, key agreement, atau key encapsulation?Primitive dipilih berdasarkan tujuan, bukan kebiasaan.
Parameter disciplinePadding, curve, key size, hash, provider, dan encoding sudah eksplisit?Tidak bergantung pada default provider yang bisa berubah.
Key lifecycleSiapa membuat key, menyimpan, memakai, merotasi, dan mencabut?Key inventory dan operational runbook.
Protocol compositionApakah primitive disusun menjadi protokol yang aman?Tidak membuat “custom TLS/JWT/payment signature” tanpa model.
Identity bindingPublic key itu milik siapa dan diverifikasi bagaimana?Trust anchor, certificate, JWKS, registry, atau pinning strategy.
AgilityBagaimana migrasi dari RSA ke EC/PQC atau dari parameter lama ke baru?Metadata envelope dan dual-read/dual-write plan.
Failure analysisBagaimana 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:

  1. Apa security property yang dibutuhkan?
  2. Primitive mana yang memberikan property itu?
  3. Apa parameter dan encoding yang harus dibekukan?
  4. Bagaimana public key diikat ke identity?
  5. Bagaimana rotasi dan migration dilakukan tanpa downtime?
  6. 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:

  1. Key establishment: menyepakati atau mengirim symmetric key.
  2. Digital signature: membuktikan bahwa pemegang private key menandatangani data tertentu.
  3. Identity binding: public key dikaitkan dengan subject melalui certificate, JWKS, truststore, registry, atau konfigurasi.
  4. 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/PKCS1Padding karena 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 casePreferAvoidNotes
Encrypt large payloadAEAD + envelope key protectionRaw RSA over payloadUse AES-GCM/ChaCha20-Poly1305 for data.
Encrypt a symmetric key for a receiverKEM or RSA-OAEPRSA PKCS#1 v1.5 encryptionRSA-OAEP requires explicit hash/MGF parameters.
Sign dataEd25519/Ed448, ECDSA, or RSA-PSSMD5withRSA, SHA1withRSA, ad-hoc hash signingSignature signs canonical bytes, not vague business object.
Establish shared secretX25519/ECDH + KDFStatic DH without authenticationKey agreement does not authenticate by itself.
Modern encapsulation APIjavax.crypto.KEM where provider supports itCustom KEM compositionKEM separates encapsulation/decapsulation semantics.
Compatibility with legacy enterpriseRSA-OAEP / RSA-PSSRSA defaultsParameter pinning is non-negotiable.
Long-term migration to PQCAlgorithm-agile envelopeHard-coded RSA/ECDSA fieldsKeep 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

GoalRSA constructionJava engine
Encrypt small key materialRSA-OAEPCipher
Sign dataRSASSA-PSSSignature
Legacy signature verificationPKCS#1 v1.5 signatureSignature
Key generationRSA / RSASSA-PSSKeyPairGenerator

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:

  • F4 is 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-256 or 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

FailureRoot causeConsequence
Cipher.getInstance("RSA")Provider default ambiguityLegacy padding or inconsistent behavior.
Encrypting payload directlyMisunderstood RSA limitSize failure or insecure chunking.
RSA PKCS#1 v1.5 encryptionLegacy compatibilityPadding oracle risk in protocol contexts.
Different OAEP MGF hashInterop mismatchProduction decrypt failures.
Detailed decrypt errorsOracle surfaceAttackers learn validity differences.
No key IDAmbiguous recipient keyRotations break or wrong key used.
Reusing key for encryption and signingPoor key separationCross-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 caseAPITypical algorithms
Key agreementKeyAgreementECDH, X25519, X448 depending on provider/platform.
SignatureSignatureSHA256withECDSA, Ed25519, Ed448, EdDSA.
Key generationKeyPairGeneratorEC, Ed25519, X25519.
Key decodingKeyFactoryEC, 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:

FamilyExamplesNotes
NIST prime curvesP-256/secp256r1, P-384/secp384r1Common in TLS, certificates, FIPS environments.
Montgomery curvesX25519, X448Used for key agreement; simple and widely deployed.
Edwards curvesEd25519, Ed448Used 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:

AlgorithmPurpose
Ed25519 / Ed448Signature
X25519 / X448Key agreement
ECDSA with P-256/P-384Signature
ECDH with P-256/P-384Key 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.

DataCommon encodingJava spec
Public keyX.509 SubjectPublicKeyInfo DERX509EncodedKeySpec
Private keyPKCS#8 DERPKCS8EncodedKeySpec
CertificateX.509 certificatejava.security.cert.X509Certificate
Secret keyRaw bytes or provider-specificSecretKeySpec, 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:

  • PKCS8EncodedKeySpec may 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:

MechanismTypical useRisk
X.509 certificate chainTLS, mTLS, enterprise PKIMisconfigured truststore, weak hostname verification.
JWKS endpointOIDC/JWT ecosystemsKey confusion, issuer mismatch, cache poisoning.
Static pinned public keySmall internal systemsRotation pain, emergency recovery pain.
Database key registryInternal signing/verifyingAccess-control and audit integrity required.
KMS/HSM key referenceCloud/regulated workloadsVendor 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

TestPurpose
Known-answer testsEnsure algorithm/provider behavior matches expected vectors where possible.
Round-trip testsEnsure generated envelope can decrypt/verify.
Tamper testsChanging ciphertext/signature/AAD/key ID must fail.
Wrong-key testsUsing another key must fail.
Migration testsOld envelopes verify/decrypt after new profile introduced.
Provider testsProduction provider supports required algorithms.
Metadata testsMissing algorithm/key/version rejected safely.
Error normalization testsExternal 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 Signature API,
  • RSA-PSS vs Ed25519,
  • verification pipeline design,
  • audit-grade integrity patterns.
Lesson Recap

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.