Java 加载 PEM 格式的 RSA 证书和私钥

上一篇文章 中,我们介绍了如何使用 Java 生成 RSA 密钥对,以及如何使用 RSA 进行加密、解密和签名验签。

在实际情况中,RSA 加密、解密所使用的密钥对往往是已经生成好的,通常以 PEM(Privacy Enhanced Mail)格式存储。

PEM 是一种常见的文件格式,用于存储和传输加密的 证书私钥 和其他安全相关的数据。

  1. 证书(Certificate):用于存储公钥证书,通常以 .pem.crt 为扩展名。
  2. 私钥(Private Key):用于存储私钥,通常以 .pem.key 为扩展名。

PEM 格式的文件通常以.pem为扩展名,它使用标头和尾部标记来界定不同类型的数据,并使用 Base64 编码将二进制数据转换为 ASCII 文本。

本文将带你了解如何在 Java 中使用 Spring 的 PemContent 工具类加载 PEM 格式的 RSA 证书和私钥为 PublicKeyPrivateKey 对象。

使用 OpenSSL 生成 RSA 私钥和证书

我们使用 OpenSSL 来生成示例所用的 RSA 私钥和证书。首先需要确保你在本机安装了 OpenSSL,并且正确地配置到了 PATH 环境变量。

OpenSSL 是一个开源的密码学工具库,被广泛应用于网络安全领域,用于创建和管理 SSL/TLS 连接、生成自签名证书、签发数字证书、进行加密通信等。

生成 2048 位的 RSA 私钥:

$ openssl genrsa -out springdoc.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
..............................+++++
..+++++
e is 65537 (0x010001)

根据私钥,生成自签名的 X.509 证书:

$ openssl req -new -x509 -days 365 -key springdoc.key -out springdoc.crt -subj "/C=CN/ST=/L=/O=/OU=/CN=springdoc.cn"
No value provided for Subject Attribute ST, skipped
No value provided for Subject Attribute L, skipped
No value provided for Subject Attribute O, skipped
No value provided for Subject Attribute OU, skipped

最终在命令执行目录下得到私钥 springdoc.key 和证书 springdoc.crt 文件,内容如下:

  • springdoc.key

    -----BEGIN RSA PRIVATE KEY-----
    MIIEpQIBAAKCAQEA1Q8RBCnVTfI2H..
    ... 省略剩余内容
    -----END RSA PRIVATE KEY-----
    
  • springdoc.crt

    -----BEGIN CERTIFICATE-----
    MIIDGzCCAgOgAwIBAgIJAKB7...
    ....省略剩余内容
    -----END CERTIFICATE-----
    

RSA 加密和解密方法

首先,把 上一篇文章 中的 “RSA 公钥加密” 和 “RSA 私钥解密” 方法复制过来,进行一点小修改。

/**
 * 加密
 * @param key		公钥
 * @param data		原文
 * @return			密文
 * @throws Exception
 */
public static byte[] encode(PublicKey key, byte[] data) throws Exception {
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
    cipher.init(Cipher.ENCRYPT_MODE, key);
    return cipher.doFinal(data);
}

/**
 * 解密
 * @param key		私钥
 * @param data		密文
 * @return			原文
 * @throws Exception
 */
public static byte[] decode(PrivateKey key, byte[] data) throws Exception {
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
    cipher.init(Cipher.DECRYPT_MODE, key);
    return cipher.doFinal(data);
}

使用 PemContent 加载 RSA 证书和私钥

得益于 Spring Boot 3 中引入的 Ssl Bundle 功能,我们可以使用其 PemContent 工具类来加载 PEM 格式的 RSA 证书和密钥。

public static void main(String[] args) throws Exception {
    
    // 加载 Pem 格式的证书
    PemContent certificatePem = PemContent.load(Paths.get("C:\\Users\\KevinBlandy\\Desktop\\tmp\\springdoc.crt"));
    // 或者证书链中的第一个证书
    List<X509Certificate>  certificates = certificatePem.getCertificates();
    // 获取证书中的公钥
    PublicKey publicKey = certificates.get(0).getPublicKey();
    
    // 加载 Pem 格式的私钥
    PemContent privateKeyPem = PemContent.load(Paths.get("C:\\Users\\KevinBlandy\\Desktop\\tmp\\springdoc.key"));
    // 获取私钥
    PrivateKey privateKey = privateKeyPem.getPrivateKey();
    
    // 原文
    String content = "Hello springdoc.cn";
    
    System.out.println("原文:" + content);
    
    // 公钥加密
    byte[] ret = encode(publicKey, content.getBytes());
    
    System.out.println("加密后的密文:" + Base64.getEncoder().encodeToString(ret));
    
    // 私钥解密
    byte[] raw =  decode(privateKey, ret);
    
    System.out.println("解密后的原文:" + new String(raw));
}

首先,使用 PemContent 工具类的 load 方法来加载 PEM 证书文件,该文件中可能包含多个证书定义,这里只获取第一个。然后从证书中获取到 RSA 公钥,解析为 PublicKey 对象。

接着还是使用 PemContent 来加载 PEM 密钥文件,直接从该文件中获取 RSA 私钥,解析为 PrivateKey 对象。

最后进行测试。使用 RSA 公钥对一段字符串进行加密,得到密文。然后使用 RSA 私钥对密文进行解密,得到原文。

执行 main 方法,控制台输出如下:

原文:Hello springdoc.cn
加密后的密文:kJWHtaxYr++xhIH6wiCAPdlunhg8N68I0yYdaImcnPlPL0yxcwLoD+QVLlzWHNL56bB9k/xCTlr1aKyQW0zBZFUC8lycZSoi4cGxrunIlNHd2jsBPwtsuVqiDPBHsLLLadlUhQWNr7FBzYyuXKRIQlBSPDbwrgK3AGjSk1EXKyk3C988ZwQNCYA9DNcbqIgJkPF2ac3TLGslu+yhZrPi4vXqCSPT9pBWia1f0oQWPl2olwfdMSQn5BdV6LQXN1sYCwmnMzMqycwcHxj+eFcYfnE11i4VjWh6ntaHvp2efMlSTFNFJCW1avyyE1EmevV7QNSKyutaMM05bDFKTwZlaw==
解密后的原文:Hello springdoc.cn

如你所见,成功地加载了 PEM 格式的 RSA 证书和私钥。并且完成了加密和解密操作。

使用 CertificateFactory 解析 RSA 公钥

最后,你也可以通过 Java 的 java.security.cert.CertificateFactory 工厂类来加载 X.509 格式的证书,并从证书中解析出 RSA 公钥。

/**
 * 从证书解析公钥
 * @param pem
 * @return
 * @throws CertificateException
 */
public static PublicKey parsePublicKeyFromCertificate(InputStream pem) throws Exception {
    
    CertificateFactory factory = CertificateFactory.getInstance("X.509");
    
    // 证书链
    Collection<? extends Certificate> certificates = factory.generateCertificates(pem);
    
    // 获取第一个证书中的公钥
    Certificate certificate = certificates.iterator().next();
    
    return certificate.getPublicKey();
}

public static void main(String[] args) throws Exception {
    
    // RSA 公钥
    PublicKey publicKey = parsePublicKeyFromCertificate(Files.newInputStream(Paths.get("C:\\Users\\KevinBlandy\\Desktop\\tmp\\springdoc.crt")));
    System.out.println(publicKey);
}