Java 26: PEM Encodings of Cryptographic Objects (JEP 524 — Preview)

No more manual Base64 wrapping or Bouncy Castle dependency. JEP 524 introduces a built-in API for encoding and decoding cryptographic objects in PEM format — the ubiquitous text format used by OpenSSL, SSH, TLS, and virtually every crypto tool.

Status: Second Preview — requires --enable-preview

What Changed

Before this API, Java developers had to:

  • Manually Base64-encode DER bytes and wrap them in -----BEGIN/END----- headers
  • Use Bouncy Castle just for PEM parsing
  • Write fragile string manipulation for multi-object PEM bundles

Now it’s all built in:

PEMEncoder encoder = PEMEncoder.of();
String pem = encoder.encodeToString(publicKey);
// → "-----BEGIN PUBLIC KEY-----\nMIIBI..."

PEMDecoder decoder = PEMDecoder.of();
PublicKey key = decoder.decode(pem, PublicKey.class);

Key API Classes

Class Purpose
PEMEncoder Encodes crypto objects to PEM text or bytes
PEMDecoder Decodes PEM text back to crypto objects
PEM Raw PEM record — type label + content, without full crypto interpretation
DEREncodable Marker interface implemented by PublicKey, PrivateKey, Certificate, PEM, etc.

Demo Highlights

1. Encode a Public Key to PEM

Generate an RSA key pair and encode the public key to standard PEM — compatible with OpenSSL and any other tool:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();

PEMEncoder encoder = PEMEncoder.of();
String pem = encoder.encodeToString(keyPair.getPublic());

// -----BEGIN PUBLIC KEY-----
// MIIBIjANBgkqhkiG9w0BAQEFAAOC...
// -----END PUBLIC KEY-----

2. Encode a Private Key to PEM

Private keys use -----BEGIN PRIVATE KEY----- headers (PKCS#8 format):

KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair keyPair = kpg.generateKeyPair();

PEMEncoder encoder = PEMEncoder.of();
String pem = encoder.encodeToString(keyPair.getPrivate());
// → "-----BEGIN PRIVATE KEY-----\n..."

3. Round-Trip: Encode → Decode → Verify

Encode keys to PEM, decode them back, and verify they’re identical — proving the API is lossless:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair original = kpg.generateKeyPair();

// Encode
PEMEncoder encoder = PEMEncoder.of();
String publicPem  = encoder.encodeToString(original.getPublic());
String privatePem = encoder.encodeToString(original.getPrivate());

// Decode
PEMDecoder decoder = PEMDecoder.of();
PublicKey  decodedPublic  = decoder.decode(publicPem,  PublicKey.class);
PrivateKey decodedPrivate = decoder.decode(privatePem, PrivateKey.class);

// Cross-verify: sign with original private key, verify with decoded public key
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(original.getPrivate());
sig.update("test message".getBytes());
byte[] signature = sig.sign();

sig.initVerify(decodedPublic);
sig.update("test message".getBytes());
boolean verified = sig.verify(signature);  // ✅ true

4. PEM Record — Low-Level PEM Inspection

PEM gives raw access to the type label and content without committing to a specific crypto object type. Useful for inspecting PEM files generically:

PEMDecoder decoder = PEMDecoder.of();
PEM pemRecord = decoder.decode(pem, PEM.class);

System.out.println("Type:    " + pemRecord.type());     // "PUBLIC KEY"
System.out.println("Content: " + pemRecord.content().length() + " chars");
byte[] derBytes = pemRecord.decode();                    // Raw DER bytes

5. Encode Multiple Objects (PEM Bundle)

PEM files often contain multiple objects concatenated (e.g., a certificate chain). Encoding several objects into one bundle is straightforward:

PEMEncoder encoder = PEMEncoder.of();
StringBuilder bundle = new StringBuilder();

// RSA public key
bundle.append(encoder.encodeToString(rsaKeys.getPublic())).append("\n");
// EC public key
bundle.append(encoder.encodeToString(ecKeys.getPublic())).append("\n");
// Ed25519 public key
bundle.append(encoder.encodeToString(edKeys.getPublic()));

// Count PEM blocks
long pemBlocks = bundle.toString().lines()
    .filter(l -> l.startsWith("-----BEGIN")).count();  // → 3

Real-World Use Cases

The companion PemEncodingRealWorldExamples class demonstrates six production scenarios:

Cross-Platform Key Export

Export a Java-generated EC key to PEM so it can be used by OpenSSL, Python, Go, or Node.js — no conversion needed:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair keyPair = kpg.generateKeyPair();

PEMEncoder encoder = PEMEncoder.of();
String publicPem  = encoder.encodeToString(keyPair.getPublic());
String privatePem = encoder.encodeToString(keyPair.getPrivate());

// publicPem → share with other services
// privatePem → store in HashiCorp Vault or AWS KMS
// Verify with: echo '<pem>' | openssl ec -pubin -text -noout

Public Key Pinning (Trust-On-First-Use)

Record a server’s public key in PEM format on first connection and compare on subsequent ones:

// First connection: pin the server key
String pinnedPem = encoder.encodeToString(serverKey.getPublic());

// Second connection: verify the key matches the pin
String currentPem = encoder.encodeToString(currentKey.getPublic());
boolean pinMatch = pinnedPem.equals(currentPem);
// pinMatch == false → potential MITM attack!

Key Pair Backup and Restore (HashiCorp Vault / DR)

Serialize a key pair to PEM for secure storage and restore it later:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair original = kpg.generateKeyPair();

// Backup
PEMEncoder encoder = PEMEncoder.of();
String publicPem  = encoder.encodeToString(original.getPublic());
String privatePem = encoder.encodeToString(original.getPrivate());
// → Store in encrypted S3, Vault, or HSM staging

// Restore (disaster recovery)
PEMDecoder decoder = PEMDecoder.of();
PublicKey  restoredPub  = decoder.decode(publicPem,  PublicKey.class);
PrivateKey restoredPriv = decoder.decode(privatePem, PrivateKey.class);

PEM Type Routing (PKI Gateway)

A PKI gateway receives mixed PEM objects and routes them to appropriate handlers using pattern matching:

DEREncodable decoded = decoder.decode(pem);

String route = switch (decoded) {
    case PublicKey pk   -> "Public Key Store  [" + pk.getAlgorithm() + "]";
    case PrivateKey sk  -> "Secure Key Vault  [" + sk.getAlgorithm() + "]";
    case PEM rec        -> "Raw PEM Archive   [type=" + rec.type() + "]";
    default             -> "Unknown Handler";
};

Code Signing with PEM Keys (CI/CD Pipeline)

A CI/CD pipeline reads signing keys from PEM-formatted secrets, signs build artifacts, and verifies them:

// Step 1: Read signing key from CI secret (PEM format)
PEMDecoder decoder = PEMDecoder.of();
PrivateKey signingKey = decoder.decode(signingKeyPem, PrivateKey.class);

// Step 2: Sign the artifact
byte[] artifact = "com.example:my-app:1.0.0:jar".getBytes();
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(signingKey);
sig.update(artifact);
byte[] signature = sig.sign();

// Verification: anyone with the public PEM can verify
PublicKey verifyKey = decoder.decode(verifyKeyPem, PublicKey.class);
sig.initVerify(verifyKey);
sig.update(artifact);
boolean valid = sig.verify(signature);  // ✅ true

Multi-Algorithm Keyring (Crypto Agility)

Manage keys of different algorithms — RSA, EC, Ed25519, ML-DSA — all stored in standard PEM format for a PQC-ready keyring:

record KeyEntry(String name, String algorithm, String pemPublic, String pemPrivate) {}

List<KeyEntry> keyring = List.of(
    new KeyEntry("signing-rsa",    "RSA",      rsaPem.pub,   rsaPem.priv),
    new KeyEntry("signing-ec",     "EC",        ecPem.pub,    ecPem.priv),
    new KeyEntry("signing-ed",     "Ed25519",   edPem.pub,    edPem.priv),
    new KeyEntry("signing-pqc",    "ML-DSA-65", mlPem.pub,    mlPem.priv)
);
// All keys portable, inspectable, and tool-compatible via PEM

Best For

  • Multi-language microservices sharing crypto keys
  • CI/CD code signing pipelines (GitHub Actions, Sigstore, Maven Central)
  • Certificate management platforms (Venafi, DigiCert, ACME clients)
  • Key management systems and backup/restore workflows
  • Applications preparing for post-quantum cryptography migration

Running the Demo

mvn compile exec:exec \
  -Dexec.mainClass=org.example.preview.PemEncodingDemo

Check out the full source on GitHub.