如何读取PEM文件以获取公钥和私钥

2023/07/02

1. 概述

在公钥加密(也称为非对称加密)中,加密机制依赖于两个相关的密钥,一个公钥和一个私钥。公钥用于加密消息,而只有私钥的所有者才能解密消息。

在本教程中,我们将学习如何从PEM文件中读取公钥和私钥。

首先,我们将研究一些关于公钥加密的重要概念。然后我们将学习如何使用纯Java读取PEM文件。

最后,我们将探索BouncyCastle库作为替代方法。

2. 概念

在开始之前,让我们讨论一些关键概念。

X.509是定义公钥证书格式的标准,因此这种格式描述了一个公钥,以及其他信息。

DER是最流行的用于存储数据的编码格式,例如文件中的X.509证书和PKCS8私钥。它是一种二进制编码,无法使用文本编辑器查看生成的内容。

PKCS8是一种用于存储私钥信息的标准语法,可以选择使用对称算法对私钥进行加密。

这个标准不仅可以处理RSA私钥,还可以处理其他算法。PKCS8私钥通常通过PEM编码格式进行交换。

PEM是DER证书的base-64编码机制。PEM还可以对其他类型的数据进行编码,例如公钥/私钥和证书请求。

PEM文件还包含描述编码数据类型的页眉和页脚:

-----BEGIN PUBLIC KEY-----
...Base64 encoding of the DER encoded certificate...
-----END PUBLIC KEY-----

3. 使用纯Java

3.1 从文件中读取PEM数据

让我们从读取PEM文件开始,并将其内容存储到一个字符串中:

String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

3.2 从PEM字符串获取公钥

现在我们将构建一个实用方法,从PEM编码字符串中获取公钥:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjtGIk8SxD+OEiBpP2/T
JUAF0upwuKGMk6wH8Rwov88VvzJrVm2NCticTk5FUg+UG5r8JArrV4tJPRHQyvqK
wF4NiksuvOjv3HyIf4oaOhZjT8hDne1Bfv+cFqZJ61Gk0MjANh/T5q9vxER/7TdU
NHKpoRV+NVlKN5bEU/NQ5FQjVXicfswxh6Y6fl2PIFqT2CfjD+FkBPU1iT9qyJYH
A38IRvwNtcitFgCeZwdGPoxiPPh1WHY8VxpUVBv/2JsUtrB/rAIbGqZoxAIWvijJ
Pe9o1TY3VlOzk9ASZ1AeatvOir+iDVJ5OpKmLnzc46QgGPUsjIyo6Sje9dxpGtoG
QQIDAQAB
-----END PUBLIC KEY-----

假设我们收到一个文件作为参数:

public static RSAPublicKey readPublicKey(File file) throws Exception {
    String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

    String publicKeyPEM = key
        .replace("-----BEGIN PUBLIC KEY-----", "")
        .replaceAll(System.lineSeparator(), "")
        .replace("-----END PUBLIC KEY-----", "");

    byte[] encoded = Base64.decodeBase64(publicKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
    return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}

如我们所见,首先我们需要删除页眉、页脚和新行。然后我们需要将Base64编码的字符串解码成对应的二进制格式。

接下来,我们需要将结果加载到能够处理公钥材料的密钥规范类中。在这种情况下,我们将使用X509EncodedKeySpec类。

最后,我们可以使用KeyFactory类从规范中生成一个公钥对象。

3.3 从PEM字符串中获取私钥

现在我们知道了如何读取公钥,读取私钥的算法也非常相似。

我们将使用PKCS8格式的PEM编码私钥,让我们看看页眉和页脚是什么样的:

-----BEGIN PRIVATE KEY-----
...Base64 encoded key...
-----END PRIVATE KEY-----

正如我们之前了解到的,我们需要一个能够处理PKCS8密钥材料的类。PKCS8EncodedKeySpec类填补了这个角色。

那么让我们看看算法:

public RSAPrivateKey readPrivateKey(File file) throws Exception {
    String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

    String privateKeyPEM = key
        .replace("-----BEGIN PRIVATE KEY-----", "")
        .replaceAll(System.lineSeparator(), "")
        .replace("-----END PRIVATE KEY-----", "");

    byte[] encoded = Base64.decodeBase64(privateKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
    return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}

4. 使用BouncyCastle库

4.1 读取公钥

我们将探索BouncyCastle库,并了解如何将其用作纯Java实现的替代方案。

让我们获取公钥:

public RSAPublicKey readPublicKey(File file) throws Exception {
    KeyFactory factory = KeyFactory.getInstance("RSA");

    try (FileReader keyReader = new FileReader(file); 
        PemReader pemReader = new PemReader(keyReader)) {
        
        PemObject pemObject = pemReader.readPemObject();
        byte[] content = pemObject.getContent();
        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
        return (RSAPublicKey) factory.generatePublic(pubKeySpec);
    }
}

在使用BouncyCastle时,我们需要注意几个重要的类:

  • PemReader:将Reader作为参数并解析其内容,它删除了不必要的标头并将底层Base64 PEM数据解码为二进制格式
  • PemObject:存储PemReader生成的结果

让我们看看另一种将Java的类(X509EncodedKeySpec、KeyFactory)包装到BouncyCastle自己的类(JcaPEMKeyConverter)中的方法:

public RSAPublicKey readPublicKeySecondApproach(File file) throws IOException {
    try (FileReader keyReader = new FileReader(file)) {
        PEMParser pemParser = new PEMParser(keyReader);
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemParser.readObject());
        return (RSAPublicKey) converter.getPublicKey(publicKeyInfo);
    }
}

4.2 读取私钥

现在我们将看到两个与上面显示的非常相似的示例。

在第一个示例中,我们只需要用PKCS8EncodedKeySpec类替换X509EncodedKeySpec类,并返回一个RSAPrivateKey对象而不是RSAPublicKey:

public RSAPrivateKey readPrivateKey(File file) throws Exception {
    KeyFactory factory = KeyFactory.getInstance("RSA");

    try (FileReader keyReader = new FileReader(file);
        PemReader pemReader = new PemReader(keyReader)) {

        PemObject pemObject = pemReader.readPemObject();
        byte[] content = pemObject.getContent();
        PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
        return (RSAPrivateKey) factory.generatePrivate(privKeySpec);
    }
}

现在让我们稍微修改一下上一节中的第二种方法,以便读取私钥:

public RSAPrivateKey readPrivateKeySecondApproach(File file) throws IOException {
    try (FileReader keyReader = new FileReader(file)) {

        PEMParser pemParser = new PEMParser(keyReader);
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(pemParser.readObject());

        return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo);
    }
}

如我们所见,我们只是将SubjectPublicKeyInfo替换为PrivateKeyInfo并将RSAPublicKey替换为RSAPrivateKey。

4.3 优点

BouncyCastle库提供了几个优点。

一个优点是我们不需要手动跳过或删除页眉和页脚,另一个是我们也不负责Base64解码。因此,我们可以使用BouncyCastle编写不易出错的代码。

此外,BouncyCastle库也支持PKCS1格式。尽管PKCS1也是一种用于存储加密密钥(仅RSA密钥)的流行格式,但Java本身并不支持它。

5. 总结

在本文中,我们学习了如何从PEM文件中读取公钥和私钥。

首先,我们研究了一些关于公钥加密的关键概念。然后我们看到了如何使用纯Java读取公钥和私钥。

最后,我们探索了BouncyCastle库并发现它是一个很好的选择,因为与纯Java实现相比它提供了一些优势。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章