How to Read PEM File to Get Public and Private Keys – 如何读取PEM文件以获取公钥和私钥

最后修改: 2020年 7月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In public-key cryptography, also known as asymmetric cryptography, the encryption mechanism relies upon two related keys, a public key and a private key. The public key is used to encrypt the message, while only the owner of the private key can decrypt the message. 

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

In this tutorial, we’ll learn how to read public and private keys from a PEM file.

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

First, we’ll study some important concepts around public-key cryptography. Then we’ll learn how to read PEM files using pure Java.

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

Finally, we’ll explore the BouncyCastle library as an alternate approach.

最后,我们将探索BouncyCastle库作为另一种方法。

2. Concepts

2.概念

Before we start, let’s discuss some key concepts.

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

X.509 is a standard defining the format of public-key certificates. So this format describes a public key, among other information.

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

DER is the most popular encoding format to store data, like X.509 certificates, and PKCS8 private keys in files. It’s a binary encoding, and the resulting content can’t be viewed with a text editor.

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

PKCS8 is a standard syntax for storing private key information. The private key can be optionally encrypted using a symmetric algorithm. 

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

Not only can RSA private keys be handled by this standard, but also other algorithms. The PKCS8 private keys are typically exchanged through the PEM encoding format.

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

PEM is a base-64 encoding mechanism of a DER certificate. PEM can also encode other kinds of data, such as public/private keys and certificate requests.

PEM 是DER证书的base-64编码机制。PEM也可以编码其他类型的数据,如公共/私人钥匙和证书请求。

A PEM file also contains a header and footer describing the type of encoded data:

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

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

3. Using Pure Java

3.使用纯Java

3.1. Read PEM Data From a File

3.1.从一个文件中读取PEM数据

Let’s start by reading the PEM file, and storing its content into a string:

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

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

3.2. Get Public Key From PEM String

3.2.从PEM字符串中获取公钥

Now we’ll build a utility method that gets the public key from the PEM encoded string:

现在我们将建立一个实用方法,从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-----

Let’s suppose we receive a File as a parameter:

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

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

As we can see, first we need to remove the header, the footer, and the new lines as well. Then we need to decode the Base64-encoded string into its corresponding binary format. 

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

Next, we need to load the result into a key specification class able to handle public key material. In this case, we’ll use the X509EncodedKeySpec class. 

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

Finally, we can generate a public key object from the specification using the KeyFactory class. 

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

3.3. Get Private Key From PEM String

3.3.从PEM字符串中获取私钥

Now that we know how to read a public key, the algorithm to read a private key is very similar. 

既然我们知道如何读取公钥,那么读取私钥的算法也非常类似。

We’ll use a PEM encoded private key in PKCS8 format. Let’s see what the header and footer look like:

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

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

As we learned previously, we need a class able to handle PKCS8 key material. The PKCS8EncodedKeySpec class fills that role.

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

So let’s see the algorithm:

因此,让我们看看这个算法:

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. Using BouncyCastle Library

4.使用BouncyCastle库

4.1. Read Public Key

4.1.读取公钥</b

We’ll explore the BouncyCastle library, and see how we can use it as an alternative to the pure Java implementation.

我们将探索BouncyCastle库,并看看我们如何使用它来替代纯Java实现。

Let’s get the public key:

让我们得到公钥:

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

There are a few important classes that we need to be aware of when using BouncyCastle:

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

  • PemReader – takes a Reader as a parameter and parses its content. It removes the unnecessary headers and decodes the underlying Base64 PEM data into a binary format.
  • PemObjectstores the result generated by the PemReader

Let’s see another approach that wraps Java’s classes (X509EncodedKeySpec, KeyFactory) into BouncyCastle’s own class (JcaPEMKeyConverter):

让我们看看另一种方法,将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. Read Private Key

4.2.读取私钥

Now we’ll see two examples that are very similar to the ones shown above.

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

In the first example, we just need to replace the X509EncodedKeySpec class with the PKCS8EncodedKeySpec class, and return a RSAPrivateKey object instead of a RSAPublicKey:

在第一个例子中,我们只需要用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);
    }
}

Now let’s rework the second approach from the previous section a bit in order to read a private key:

现在,让我们把上一节的第二种方法重做一下,以便读取私钥。

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

As we can see, we just replaced SubjectPublicKeyInfo with PrivateKeyInfo and RSAPublicKey with RSAPrivateKey.

我们可以看到,我们只是用PrivateKeyInfo替换了SubjectPublicKeyInfo,用RSAPrivateKey替换了RSAPublicKey

4.3. Advantages

4.3.优势

There are a couple of advantages provided by the BouncyCastle library.

BouncyCastle库提供了一些优势。

One advantage is that we don’t need to manually skip or remove the header and footer. Another is that we’re not responsible for the Base64 decoding, either. Therefore, we can write less error-prone code with BouncyCastle.

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

Moreover, the BouncyCastle library supports the PKCS1 format as well. Despite the fact that PKCS1 is also a popular format used to store cryptographic keys (only RSA keys), Java doesn’t support it on its own.

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

5. Conclusion

5.总结

In this article, we learned how to read public and private keys from PEM files.

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

First, we studied a few key concepts around public-key cryptography. Then we saw how to read public and private keys using pure Java.

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

Finally, we explored the BouncyCastle library and discovered it’s a good alternative, since it provides a few advantages compared to the pure Java implementation.

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

The full source code for both the Java and BouncyCastle approaches is available over on GitHub.

JavaBouncyCastle方法的完整源代码都可以在GitHub上找到。