Java25新特性:加密对象的PEM编码

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() 方法处理加密的私钥,并提供了原生的PEMEncoderPEMDecoder 类,这两个实例是无状态且线程安全的,可在多线程环境中共享和重用;解码时可以指定目标类型,或使用无类型版本返回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();
        }
    }
}

执行结果:

相关推荐
计算机安禾1 小时前
【c++面向对象编程】第21篇:运算符重载基础:语法、规则与不可重载的运算符
java·前端·c++
萧曵 丶1 小时前
JUC 实际业务高频面试题浅谈
java·juc·aqs·lock
初圣魔门首席弟子1 小时前
bug 2026.05.15(以前能运行的java springboot项目突然间不能运行后台数据了)
java·开发语言·bug
古怪今人1 小时前
项目和模块 一个目录下创建多个项目 IDEA Multi-Project Workspace插件
java·ide·intellij-idea
小英雄大肚腩丶1 小时前
RabbitMQ消息队列
java·数据结构·spring boot·分布式·rabbitmq·java-rabbitmq
fengxin_rou2 小时前
用户模块架构实战:DTO 与 Domain 分层、Optional 空值处理、事务只读优化详解
java·后端·架构·用户实战
redaijufeng2 小时前
C++构造函数详解:从基础原理到实际应用
java·jvm·c++
yuzhiboyouye3 小时前
VO一般java后端怎么转换成前端想要的数据
java·前端·状态模式
一 乐3 小时前
学院教学工作量统计|基于java+ vue学院教学工作量统计管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·学院教学工作量统计系统