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());
-----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-----

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

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. 读取私钥

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

在第一个例子中,我们只需要更换X509EncodedKeySpec与类的PKCS8EncodedKeySpec类,并返回一个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进行RSAPublicKeyRSAPrivateKey

4.3. 好处

BouncyCastle 库提供了几个优点。

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

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

5. 结论

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

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

最后,我们探索了 BouncyCastle 库并了解到它是一个很好的替代方案,因为与纯 Java 实现相比,它提供了一些优势。

JavaBouncyCastle方法的完整源代码可在 GitHub 上获得。

 

 

 

 

 

 

 

 

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐