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); // ✅ true4. 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 bytes5. 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(); // → 3Real-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 -nooutPublic 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); // ✅ trueMulti-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 PEMBest 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.PemEncodingDemoCheck out the full source on GitHub.