JEP470: 该特性为预览特性,用于处理PEM (Privacy-EnhanceMail)格式的加密对象,目的就是简化密钥、证书等加密材料的读写操作,并将在Java26做第二次预览。
PEM是一种文本格式,用于编码加密密钥、证书等对象:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgjDohS0RHP395oJxciVaeks9N
KNY5m9V1IkBBwYsMGyxskrW5sapgi9qlGSYOma9kkko1xlBs17qG8TTg38faxgGJ
sLT2BAmdVFwuWdRtzq6ONn2YPHYj5s5pqx6vU5baz58/STQXNIhn21QoPjXgQCnj
Pp0OxnacWeRSnAIOmQIDAQAB
-----END PUBLIC KEY-----
PEM是base64编码的二进制数据,被**-----BEGIN/END-----**标记包裹。
在Java25之前,处理PEM文件需要手动移除**-----BEGIN/END-----** 标记,进行Base64解码,使用keyFactory或CertificateFactory构造对象,或者引入第三方库。
java
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class OldPEMHandler {
/**
* 将公钥编码为PEM格式
*/
public static String encodePublicKey(PublicKey publicKey) {
String base64 = Base64.getEncoder().encodeToString(publicKey.getEncoded());
StringBuilder pem = new StringBuilder();
pem.append("-----BEGIN PUBLIC KEY-----\n");
for (int i = 0; i < base64.length(); i += 64) {
pem.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
}
pem.append("-----END PUBLIC KEY-----");
return pem.toString();
}
/**
* 将私钥编码为 PEM 格式
*/
public static String encodePrivateKey(PrivateKey privateKey) {
String base64 = Base64.getEncoder().encodeToString(privateKey.getEncoded());
StringBuilder pem = new StringBuilder();
pem.append("-----BEGIN PRIVATE KEY-----\n");
for (int i = 0; i < base64.length(); i += 64) {
pem.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
}
pem.append("-----END PRIVATE KEY-----");
return pem.toString();
}
/**
* 加密并编码私钥
*/
public static String encodeEncryptedPrivateKey(PrivateKey privateKey, char[] password) throws Exception {
byte[] salt = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec aesKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
byte[] iv = new byte[16];
random.nextBytes(iv);
javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(iv);
// 使用 AES/CBC/PKCS5Padding 加密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
byte[] encryptedKey = cipher.doFinal(privateKey.getEncoded());
EncryptedPrivateKeyInfo encryptedInfo = createEncryptedPrivateKeyInfo(
salt, iv, 65536, encryptedKey, privateKey.getEncoded().length
);
String base64 = Base64.getEncoder().encodeToString(encryptedInfo.getEncoded());
StringBuilder pem = new StringBuilder();
pem.append("-----BEGIN ENCRYPTED PRIVATE KEY-----\n");
for (int i = 0; i < base64.length(); i += 64) {
pem.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
}
pem.append("-----END ENCRYPTED PRIVATE KEY-----");
return pem.toString();
}
/**
* 创建符合 PKCS#8 标准的 EncryptedPrivateKeyInfo
* 使用 PBES2 + PBKDF2 + AES-256-CBC
*/
private static EncryptedPrivateKeyInfo createEncryptedPrivateKeyInfo(
byte[] salt, byte[] iv, int iterationCount,
byte[] encryptedData, int keyLength) throws Exception {
javax.crypto.spec.PBEParameterSpec pbeParamSpec =
new javax.crypto.spec.PBEParameterSpec(salt, iterationCount);
java.security.AlgorithmParameters algParams =
java.security.AlgorithmParameters.getInstance("PBES2");
try {
Cipher pbeCipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
} catch (Exception e) {
// 回退到标准方法
}
String pbes2OID = "1.2.840.113549.1.5.13";
byte[] pbes2Params = createPBES2Params(salt, iv, iterationCount, keyLength);
java.security.AlgorithmParameters params =
java.security.AlgorithmParameters.getInstance("PBES2");
params.init(pbes2Params);
return new EncryptedPrivateKeyInfo(params, encryptedData);
}
/**
* 手动构建PBES2参数的DER编码
*/
private static byte[] createPBES2Params(byte[] salt, byte[] iv, int iterationCount, int keyLength) {
try {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.io.DataOutputStream dos = new java.io.DataOutputStream(baos);
javax.crypto.spec.PBEParameterSpec pbeSpec =
new javax.crypto.spec.PBEParameterSpec(salt, iterationCount);
java.security.AlgorithmParameters pbeParams =
java.security.AlgorithmParameters.getInstance("PBE");
pbeParams.init(pbeSpec);
return pbeParams.getEncoded();
} catch (Exception e) {
throw new RuntimeException("Failed to create PBES2 parameters", e);
}
}
/**
* 使用标准PBE算法
*/
public static String encodeEncryptedPrivateKeySimple(PrivateKey privateKey, char[] password) throws Exception {
String algorithm = "PBEWithMD5AndDES";
byte[] salt = new byte[8];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
int iterationCount = 65536;
javax.crypto.spec.PBEParameterSpec paramSpec =
new javax.crypto.spec.PBEParameterSpec(salt, iterationCount);
javax.crypto.spec.PBEKeySpec keySpec = new javax.crypto.spec.PBEKeySpec(password);
SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
SecretKey key = factory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] encrypted = cipher.doFinal(privateKey.getEncoded());
java.security.AlgorithmParameters params =
java.security.AlgorithmParameters.getInstance(algorithm);
params.init(paramSpec);
EncryptedPrivateKeyInfo encInfo = new EncryptedPrivateKeyInfo(params, encrypted);
String base64 = Base64.getEncoder().encodeToString(encInfo.getEncoded());
StringBuilder pem = new StringBuilder();
pem.append("-----BEGIN ENCRYPTED PRIVATE KEY-----\n");
for (int i = 0; i < base64.length(); i += 64) {
pem.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
}
pem.append("-----END ENCRYPTED PRIVATE KEY-----");
return pem.toString();
}
/**
* 从 PEM 解码公钥
*/
public static PublicKey decodePublicKey(String pem, String algorithm) throws Exception {
String base64 = pem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("[\\r\\n]", "");
byte[] decoded = Base64.getDecoder().decode(base64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePublic(spec);
}
/**
* 从 PEM 解码私钥
*/
public static PrivateKey decodePrivateKey(String pem, String algorithm) throws Exception {
String base64 = pem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("[\\r\\n]", "");
byte[] decoded = Base64.getDecoder().decode(base64);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePrivate(spec);
}
/**
* 解密并解码私钥
*/
public static PrivateKey decodeEncryptedPrivateKey(String pem, char[] password, String algorithm) throws Exception {
String base64 = pem
.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "")
.replace("-----END ENCRYPTED PRIVATE KEY-----", "")
.replaceAll("[\\r\\n]", "");
byte[] decoded = Base64.getDecoder().decode(base64);
EncryptedPrivateKeyInfo encryptedInfo = new EncryptedPrivateKeyInfo(decoded);
String algName = encryptedInfo.getAlgName();
Cipher cipher = Cipher.getInstance(algName);
SecretKeyFactory factory = SecretKeyFactory.getInstance(algName);
javax.crypto.spec.PBEKeySpec spec = new javax.crypto.spec.PBEKeySpec(password);
SecretKey secretKey = factory.generateSecret(spec);
java.security.AlgorithmParameters params = encryptedInfo.getAlgParameters();
cipher.init(Cipher.DECRYPT_MODE, secretKey, params);
byte[] decrypted = cipher.doFinal(encryptedInfo.getEncryptedData());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decrypted);
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePrivate(keySpec);
}
public static void main(String[] args) {
System.out.println("========== Java PEM 处理方式 ==========");
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(256);
KeyPair keyPair = keyGen.generateKeyPair();
System.out.println("公钥处理测试");
String publicKeyPEM = encodePublicKey(keyPair.getPublic());
System.out.println("编码后的公钥PEM:");
System.out.println(publicKeyPEM);
PublicKey decodedPubKey = decodePublicKey(publicKeyPEM, "EC");
System.out.println("解码成功,算法: " + decodedPubKey.getAlgorithm());
System.out.println("密钥匹配: " + keyPair.getPublic().equals(decodedPubKey));
System.out.println("\n私钥处理测试");
String privateKeyPEM = encodePrivateKey(keyPair.getPrivate());
System.out.println("编码后的私钥PEM:");
System.out.println(privateKeyPEM.substring(0, Math.min(100, privateKeyPEM.length())) + "...");
PrivateKey decodedPrivKey = decodePrivateKey(privateKeyPEM, "EC");
System.out.println("解码成功,算法: " + decodedPrivKey.getAlgorithm());
System.out.println("密钥匹配: " + keyPair.getPrivate().equals(decodedPrivKey));
System.out.println("\n加密私钥处理测试 (简化版)");
char[] password = "mySecretPassword123".toCharArray();
try {
String encryptedPEM = encodeEncryptedPrivateKeySimple(keyPair.getPrivate(), password);
System.out.println("加密私钥PEM生成成功(长度: " + encryptedPEM.length() + ")");
System.out.println(encryptedPEM);
// 测试解密
PrivateKey decryptedKey = decodeEncryptedPrivateKey(encryptedPEM, password, "EC");
System.out.println("解密成功,算法: " + decryptedKey.getAlgorithm());
System.out.println("密钥匹配: " + keyPair.getPrivate().equals(decryptedKey));
} catch (Exception e) {
System.out.println("错误: " + e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果:

JEP470提供了可以通过withEncryption() 和withDecryption() 方法处理加密的私钥,并提供了原生的PEMEncoder 和PEMDecoder 类,这两个实例是无状态且线程安全的,可在多线程环境中共享和重用;解码时可以指定目标类型,或使用无类型版本返回DEREncodable;如果无法解码,不会抛出异常,而是返回包含原始二进制数据的通用**PEMRecord**对象。
java
import java.security.*;
public class NewPEMHandler {
public static void main(String[] args) {
System.out.println("========== Java 25的PEM处理方式(JEP470)==========");
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(256);
KeyPair keyPair = keyGen.generateKeyPair();
System.out.println("公钥编码");
// Java 25:PEMEncoder.of().encodeToString()
String publicKeyPEM = PEMEncoder.of().encodeToString(keyPair.getPublic());
System.out.println("编码后的公钥PEM:");
System.out.println(publicKeyPEM);
System.out.println();
System.out.println("私钥编码");
char[] password = "mySecretPassword123".toCharArray();
// Java 25:withEncryption()自动处理PBKDF2和AES加密
String privateKeyPEM = PEMEncoder.of().withEncryption(password).encodeToString(keyPair.getPrivate());
System.out.println("加密后的私钥PEM:");
System.out.println(privateKeyPEM);
System.out.println("公钥解码");
// Java 25:PEMDecoder.of().decode()
PublicKey decodedPubKey = PEMDecoder.of().decode(publicKeyPEM, PublicKey.class);
System.out.println("解码成功,算法: " + decodedPubKey.getAlgorithm());
System.out.println("密钥匹配: " + keyPair.getPublic().equals(decodedPubKey));
System.out.println("私钥解码");
// Java 25:withDecryption()自动处理解密
PrivateKey decodedPrivKey = PEMDecoder.of().withDecryption(password).decode(privateKeyPEM, PrivateKey.class);
System.out.println("解密成功,算法: " + decodedPrivKey.getAlgorithm());
System.out.println("密钥匹配: " + keyPair.getPrivate().equals(decodedPrivKey));
System.out.println("批量编码多个密钥");
KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA");
rsaGen.initialize(2048);
KeyPair rsaKeyPair = rsaGen.generateKeyPair();
// 编码多个密钥
String rsaPublicPEM = PEMEncoder.of().encodeToString(rsaKeyPair.getPublic());
String rsaPrivatePEM = PEMEncoder.of().withEncryption(password).encodeToString(rsaKeyPair.getPrivate());
System.out.println("RSA公钥PEM长度: " + rsaPublicPEM.length());
System.out.println("RSA加密私钥PEM长度: " + rsaPrivatePEM.length());
System.out.println("类型安全解码");
// 可以明确指定类型,也可以不指定返回DEREncodable
var decodedKey = PEMDecoder.of().decode(rsaPublicPEM, PublicKey.class);
System.out.println("解码类型: " + decodedKey.getClass().getSimpleName());
System.out.println("算法: " + decodedKey.getAlgorithm());
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果:
