非对称密钥PKCS#1和PKCS#8格式互相转换(Java)

简介: 之前在 《前后端RSA互相加解密、加签验签、密钥对生成》 中提到过PKCS#1格式和PKCS#8格式密钥的区别以及如何生成密钥。实际有些场景中有可能也会涉及到前后端密钥格式不一致,这篇文章我们会讨论关于PKCS#1和PKCS#8格式密钥的互相转换。

目录

一、序言


之前在 《前后端RSA互相加解密、加签验签、密钥对生成》 中提到过PKCS#1格式和PKCS#8格式密钥的区别以及如何生成密钥。实际有些场景中有可能也会涉及到前后端密钥格式不一致,这篇文章我们会讨论关于PKCS#1和PKCS#8格式密钥的互相转换。


这里我们会用到Bouncy Castle,它提供了各种加密算法的Java实现,其中Java相关的资料可以参考Bouncy Castle Github。

二、代码示例


1、Maven依赖

<dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcpkix-jdk18on</artifactId>
   <version>1.72</version>
</dependency>

2、工具类封装

package com.universe.crypto;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
/**
 * @author Nick Liu
 * @date 2023/2/2
 */
public class AsymmetricKeyUtils {
  private static final String ALGORITHM_NAME = "RSA";
  private static final Encoder BASE64_ENCODER = Base64.getEncoder();
  private static final Decoder BASE64_DECODER = Base64.getDecoder();
  static {
    // 必须创建Bouncy Castle提供者
    Security.addProvider(new BouncyCastleProvider());
  }
  /**
   * 格式化密钥为标准Pem格式
   * @param keyFormat 密钥格式,参考{@link KeyFormat}
   * @param asymmetricKey 非对称密钥
   * @return .pem格式密钥字符串
   * @throws IOException
   */
  public static String formatKeyAsPemString(KeyFormat keyFormat, String asymmetricKey) throws IOException {
    byte[] keyInBytes = BASE64_DECODER.decode(asymmetricKey);
    PemObject pemObject = new PemObject(keyFormat.getName(), keyInBytes);
    try (StringWriter stringWriter = new StringWriter()) {
      PemWriter pemWriter = new PemWriter(stringWriter);
      pemWriter.writeObject(pemObject);
      pemWriter.flush();
      return stringWriter.toString();
    }
  }
  /**
   * 从标准Pem格式中提取密钥
   * @param asymmetricKeyAsPem
   * @return 无---BEGIN---和---END---前后缀的密钥字符串
   * @throws IOException
   */
  public static String extractKeyFromPemString(String asymmetricKeyAsPem) throws IOException {
    try (PemReader pemReader = new PemReader(new StringReader(asymmetricKeyAsPem))) {
      PemObject pemObject = pemReader.readPemObject();
      return BASE64_ENCODER.encodeToString(pemObject.getContent());
    }
  }
  /**
   * 从文件中提取密钥
   * @param pemFilePath
   * @return 无---BEGIN---和---END---前后缀的密钥字符串
   * @throws Exception
   */
  public static String readKeyFromPath(String pemFilePath) throws Exception {
    try (PemReader pemReader = new PemReader(new InputStreamReader(Files.newInputStream(Paths.get(pemFilePath))))) {
      PemObject pemObject = pemReader.readPemObject();
      return BASE64_ENCODER.encodeToString(pemObject.getContent());
    }
  }
  /**
   * 将PKCS1公钥转换为PKCS8公钥
   * @param pubKeyInPKCS1 PKCS1形式公钥
   * @return PKCS8形式公钥
   * @throws Exception
   */
  public static String transformPubKeyFromPkcs1ToPkcs8(String pubKeyInPKCS1) throws Exception {
    RSAPublicKey rsaPublicKey = RSAPublicKey.getInstance(BASE64_DECODER.decode(pubKeyInPKCS1));
    KeySpec keySpec = new RSAPublicKeySpec(rsaPublicKey.getModulus(), rsaPublicKey.getPublicExponent());
    KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
    PublicKey publicKey = keyFactory.generatePublic(keySpec);
    return BASE64_ENCODER.encodeToString(publicKey.getEncoded());
  }
  /**
   * 将PKCS8公钥转换为PKCS1公钥
   * @param pubKeyInPKCS8 PKCS8公钥
   * @return PKCS1公钥
   */
  public static String transformPubKeyFromPkcs8ToPkcs1(String pubKeyInPKCS8) {
    ASN1Sequence publicKeyASN1Object = ASN1Sequence.getInstance(BASE64_DECODER.decode(pubKeyInPKCS8));
    DERBitString derBitString = (DERBitString) publicKeyASN1Object.getObjectAt(1);
    return BASE64_ENCODER.encodeToString(derBitString.getBytes());
  }
  /**
   * 将PKCS1私钥转换为PKCS8公钥
   * @param privateKeyInPKCS1 PKCS1公钥
   * @return PKCS8公钥
   * @throws Exception
   */
  public static String transformPrivateKeyFromPkcs1ToPkcs8(String privateKeyInPKCS1) throws Exception {
    KeySpec keySpec = new PKCS8EncodedKeySpec(BASE64_DECODER.decode(privateKeyInPKCS1));
    KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
    return BASE64_ENCODER.encodeToString(privateKey.getEncoded());
  }
  /**
   * 将PKCS1私钥转换为PKCS8私钥
   * @param privateKeyInPKCS8 PKCS8私钥
   * @return PKCS1私钥
   * @throws Exception
   */
  public static String transformPrivateKeyFromPkcs8ToPkcs1(String privateKeyInPKCS8) throws Exception {
    PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(BASE64_DECODER.decode(privateKeyInPKCS8));
    RSAPrivateKey rsaPrivateKey = RSAPrivateKey.getInstance(privateKeyInfo.parsePrivateKey());
    return BASE64_ENCODER.encodeToString(rsaPrivateKey.getEncoded());
  }
  public enum KeyFormat {
    /**
     * PKCS1格式RSA私钥
     */
    RSA_PRIVATE_KEY_PKCS1("RSA PRIVATE KEY"),
    /**
     * PKCS8格式RSA私钥
     */
    RSA_PRIVATE_KEY_PKCS8("PRIVATE KEY"),
    /**
     * PKCS1格式RSA公钥
     */
    RSA_PUBLIC_KEY_PKCS1("RSA PUBLIC KEY"),
    /**
     * PKCS8格式RSA公钥
     */
    RSA_PUBLIC_KEY_PKCS8("PUBLIC KEY");
    private String name;
    KeyFormat(String name) {
      this.name = name;
    }
    public String getName() {
      return name;
    }
  }
}

备注:必须要添加Bouncy Castle提供者,代码中Security.addProvider(new BouncyCastleProvider())展示的是动态添加,也可以静态添加,更多请参考 Bouncy Castle提供者安装

10.png


三、测试用例


1、密钥文件

准备.pem两个文件,里面分别是PKCS#1格式公钥和PKCS#1格式私钥,内容如下:

  • publicKeyInPkcs1.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCt3uiwkIsY29Vl88fiV9FkZqF65VW8emfkBFJfEVq92LZq6vw4
o/3Ldda5AQB0WZZIGILQoRwy7raiKbXqmyfZ3sJu+9CwKy5Sfrh5RXiTgy0ZAfg0
Lw2T+Ml2l70KZKkcSiAyaRTM1YPcYXIsQoRyp88tSnYvHn9ghoaljUGl2QIDAQAB
AoGBAIiEKg1gIGbvTHmVVE8qhpUfZBC7enrXXTUSE57jYG0JeAkw4cKTOFWE+4gc
+j9gi/eljyjCJwLynWFsAJLpKfrCdzbzd748XXuFxfFOqd8w6lsO28Pbhqy4YvXj
OegGJG9+RyfZWhTP9JmU/eumntLFkELr5m80SqiiYIo2uY0BAkEA07eket2UAy2d
zv3jMD3wbcEgPx97p/kTcTq4ntWUA5XuCPC3Tb0Ge9/iZmu1etZcDtPptQKvd3+H
fLmmFQyEYQJBANI8xy1PnfvqW8lRbb9TCQm5BmvmHXCONHdNAC3UW2jJ9cUD9/o8
AiPNv8ZXmTgsSbBvAW9dlUe35joWaBAvlHkCQQC5nWpdsb+fXbHaFKrG07bjcosD
7GUsKenKvpG350XiMuNDAU+jnxJ9Lha+drXf4OlKsq1V3enaGXu+dMDP+W5hAkAn
i0UPkcUuiCNhl45kCVNO392EWBE7hZP6yKH6/NGAwVQYDaoMCFOCtoWW4g0w0qu9
ovOLJfgZOE72qBZEzR5JAkBmwuH48K6Q3KJxUvQDpw6Bgh2zGayoy12BdDQM0ZW+
AblMqqb3iwqP+LWoES3SqAKRmA6clh/9DUqtOn6610gk
-----END RSA PRIVATE KEY-----
  • privateKeyInPkcs1.pem
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAK3e6LCQixjb1WXzx+JX0WRmoXrlVbx6Z+QEUl8RWr3Ytmrq/Dij/ct1
1rkBAHRZlkgYgtChHDLutqIpteqbJ9newm770LArLlJ+uHlFeJODLRkB+DQvDZP4
yXaXvQpkqRxKIDJpFMzVg9xhcixChHKnzy1Kdi8ef2CGhqWNQaXZAgMBAAE=
-----END RSA PUBLIC KEY-----

2、公私钥PKCS1和PKCS8格式互相转换

public class Test {
  /**
   * 密钥保存在用户目录下keys文件夹
   */
  private static final String BASE_PATH = Paths.get(System.getProperty("user.home"), "keys").toString();
  private static final String PRIVATE_KEY_PKCS1 = Paths.get(BASE_PATH, "privateKeyInPkcs1.pem").toString();
  private static final String PUBLIC_KEY_PKCS1 = Paths.get(BASE_PATH, "publicKeyInPkcs1.pem").toString();
  public static void main(String[] args) throws Exception {
    String pubicKeyInPkcs1 = AsymmetricKeyUtils.readKeyFromPath(PUBLIC_KEY_PKCS1);
    System.out.println("读取到的PKCS1格式公钥为:\n" + pubicKeyInPkcs1);
    String publicKeyInPkcs8 = AsymmetricKeyUtils.transformPubKeyFromPkcs1ToPkcs8(pubicKeyInPkcs1);
    System.out.println("转换后的PKCS8格式公钥为:\n" + publicKeyInPkcs8);
    pubicKeyInPkcs1 = AsymmetricKeyUtils.transformPubKeyFromPkcs8ToPkcs1(publicKeyInPkcs8);
    System.out.println("转换后的PKCS1格式公钥为:\n" + pubicKeyInPkcs1);
    System.out.println();
    String privateKeyInPkcs1 = AsymmetricKeyUtils.readKeyFromPath(PRIVATE_KEY_PKCS1);
    System.out.println("读取到的PKCS1格式私钥为:\n" + privateKeyInPkcs1);
    String privateKeyInPkcs8 = AsymmetricKeyUtils.transformPrivateKeyFromPkcs1ToPkcs8(privateKeyInPkcs1);
    System.out.println("转换后的PKCS8格式私钥为:\n" + privateKeyInPkcs8);
    privateKeyInPkcs1 = AsymmetricKeyUtils.transformPrivateKeyFromPkcs8ToPkcs1(privateKeyInPkcs8);
    System.out.println("转换后的PKCS1格式私钥为:\n" + privateKeyInPkcs1);
  }
}

控制台输出如下:

读取到的PKCS1格式公钥为:
MIGJAoGBAK3e6LCQixjb1WXzx+JX0WRmoXrlVbx6Z+QEUl8RWr3Ytmrq/Dij/ct11rkBAHRZlkgYgtChHDLutqIpteqbJ9newm770LArLlJ+uHlFeJODLRkB+DQvDZP4yXaXvQpkqRxKIDJpFMzVg9xhcixChHKnzy1Kdi8ef2CGhqWNQaXZAgMBAAE=
转换后的PKCS8格式公钥为:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCt3uiwkIsY29Vl88fiV9FkZqF65VW8emfkBFJfEVq92LZq6vw4o/3Ldda5AQB0WZZIGILQoRwy7raiKbXqmyfZ3sJu+9CwKy5Sfrh5RXiTgy0ZAfg0Lw2T+Ml2l70KZKkcSiAyaRTM1YPcYXIsQoRyp88tSnYvHn9ghoaljUGl2QIDAQAB
转换后的PKCS1格式公钥为:
MIGJAoGBAK3e6LCQixjb1WXzx+JX0WRmoXrlVbx6Z+QEUl8RWr3Ytmrq/Dij/ct11rkBAHRZlkgYgtChHDLutqIpteqbJ9newm770LArLlJ+uHlFeJODLRkB+DQvDZP4yXaXvQpkqRxKIDJpFMzVg9xhcixChHKnzy1Kdi8ef2CGhqWNQaXZAgMBAAE=
读取到的PKCS1格式私钥为:
MIICXQIBAAKBgQCt3uiwkIsY29Vl88fiV9FkZqF65VW8emfkBFJfEVq92LZq6vw4o/3Ldda5AQB0WZZIGILQoRwy7raiKbXqmyfZ3sJu+9CwKy5Sfrh5RXiTgy0ZAfg0Lw2T+Ml2l70KZKkcSiAyaRTM1YPcYXIsQoRyp88tSnYvHn9ghoaljUGl2QIDAQABAoGBAIiEKg1gIGbvTHmVVE8qhpUfZBC7enrXXTUSE57jYG0JeAkw4cKTOFWE+4gc+j9gi/eljyjCJwLynWFsAJLpKfrCdzbzd748XXuFxfFOqd8w6lsO28Pbhqy4YvXjOegGJG9+RyfZWhTP9JmU/eumntLFkELr5m80SqiiYIo2uY0BAkEA07eket2UAy2dzv3jMD3wbcEgPx97p/kTcTq4ntWUA5XuCPC3Tb0Ge9/iZmu1etZcDtPptQKvd3+HfLmmFQyEYQJBANI8xy1PnfvqW8lRbb9TCQm5BmvmHXCONHdNAC3UW2jJ9cUD9/o8AiPNv8ZXmTgsSbBvAW9dlUe35joWaBAvlHkCQQC5nWpdsb+fXbHaFKrG07bjcosD7GUsKenKvpG350XiMuNDAU+jnxJ9Lha+drXf4OlKsq1V3enaGXu+dMDP+W5hAkAni0UPkcUuiCNhl45kCVNO392EWBE7hZP6yKH6/NGAwVQYDaoMCFOCtoWW4g0w0qu9ovOLJfgZOE72qBZEzR5JAkBmwuH48K6Q3KJxUvQDpw6Bgh2zGayoy12BdDQM0ZW+AblMqqb3iwqP+LWoES3SqAKRmA6clh/9DUqtOn6610gk
转换后的PKCS8格式私钥为:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK3e6LCQixjb1WXzx+JX0WRmoXrlVbx6Z+QEUl8RWr3Ytmrq/Dij/ct11rkBAHRZlkgYgtChHDLutqIpteqbJ9newm770LArLlJ+uHlFeJODLRkB+DQvDZP4yXaXvQpkqRxKIDJpFMzVg9xhcixChHKnzy1Kdi8ef2CGhqWNQaXZAgMBAAECgYEAiIQqDWAgZu9MeZVUTyqGlR9kELt6etddNRITnuNgbQl4CTDhwpM4VYT7iBz6P2CL96WPKMInAvKdYWwAkukp+sJ3NvN3vjxde4XF8U6p3zDqWw7bw9uGrLhi9eM56AYkb35HJ9laFM/0mZT966ae0sWQQuvmbzRKqKJgija5jQECQQDTt6R63ZQDLZ3O/eMwPfBtwSA/H3un+RNxOrie1ZQDle4I8LdNvQZ73+Jma7V61lwO0+m1Aq93f4d8uaYVDIRhAkEA0jzHLU+d++pbyVFtv1MJCbkGa+YdcI40d00ALdRbaMn1xQP3+jwCI82/xleZOCxJsG8Bb12VR7fmOhZoEC+UeQJBALmdal2xv59dsdoUqsbTtuNyiwPsZSwp6cq+kbfnReIy40MBT6OfEn0uFr52td/g6UqyrVXd6doZe750wM/5bmECQCeLRQ+RxS6II2GXjmQJU07f3YRYETuFk/rIofr80YDBVBgNqgwIU4K2hZbiDTDSq72i84sl+Bk4TvaoFkTNHkkCQGbC4fjwrpDconFS9AOnDoGCHbMZrKjLXYF0NAzRlb4BuUyqpveLCo/4tagRLdKoApGYDpyWH/0NSq06frrXSCQ=
转换后的PKCS1格式私钥为:
MIICXQIBAAKBgQCt3uiwkIsY29Vl88fiV9FkZqF65VW8emfkBFJfEVq92LZq6vw4o/3Ldda5AQB0WZZIGILQoRwy7raiKbXqmyfZ3sJu+9CwKy5Sfrh5RXiTgy0ZAfg0Lw2T+Ml2l70KZKkcSiAyaRTM1YPcYXIsQoRyp88tSnYvHn9ghoaljUGl2QIDAQABAoGBAIiEKg1gIGbvTHmVVE8qhpUfZBC7enrXXTUSE57jYG0JeAkw4cKTOFWE+4gc+j9gi/eljyjCJwLynWFsAJLpKfrCdzbzd748XXuFxfFOqd8w6lsO28Pbhqy4YvXjOegGJG9+RyfZWhTP9JmU/eumntLFkELr5m80SqiiYIo2uY0BAkEA07eket2UAy2dzv3jMD3wbcEgPx97p/kTcTq4ntWUA5XuCPC3Tb0Ge9/iZmu1etZcDtPptQKvd3+HfLmmFQyEYQJBANI8xy1PnfvqW8lRbb9TCQm5BmvmHXCONHdNAC3UW2jJ9cUD9/o8AiPNv8ZXmTgsSbBvAW9dlUe35joWaBAvlHkCQQC5nWpdsb+fXbHaFKrG07bjcosD7GUsKenKvpG350XiMuNDAU+jnxJ9Lha+drXf4OlKsq1V3enaGXu+dMDP+W5hAkAni0UPkcUuiCNhl45kCVNO392EWBE7hZP6yKH6/NGAwVQYDaoMCFOCtoWW4g0w0qu9ovOLJfgZOE72qBZEzR5JAkBmwuH48K6Q3KJxUvQDpw6Bgh2zGayoy12BdDQM0ZW+AblMqqb3iwqP+LWoES3SqAKRmA6clh/9DUqtOn6610gk

备注:可以将转换后的公私钥在RSA加/解密平台测试一下,博主亲测是okay的。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
1月前
|
Java
Java将OffsetDateTime格式化为 yyyy-MM-dd HH:mm:ss 如何写代码?
Java将OffsetDateTime格式化为 yyyy-MM-dd HH:mm:ss 如何写代码?
33 0
|
2月前
|
JSON 前端开发 Java
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
文章介绍了Java后端如何使用Spring Boot框架响应不同格式的数据给前端,包括返回静态页面、数据、HTML代码片段、JSON对象、设置状态码和响应的Header。
170 1
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
|
5月前
|
Java 编译器 开发者
Java演进问题之Truffle处理不同编程语言的源代码或中间格式如何解决
Java演进问题之Truffle处理不同编程语言的源代码或中间格式如何解决
|
4月前
|
缓存 Java 数据处理
|
4月前
|
Java
Java模拟文件发送给服务器,服务器将文件转发给其他用户,并保存到服务器本地,其他用户可以接收,并保存到本地磁盘,支持各种文件格式,并解决通信中服务器怎么区分客户端发来的文件类型
Java模拟文件发送给服务器,服务器将文件转发给其他用户,并保存到服务器本地,其他用户可以接收,并保存到本地磁盘,支持各种文件格式,并解决通信中服务器怎么区分客户端发来的文件类型
|
6月前
|
Java 区块链
用Java将ico格式转 PNG/JPG等格式
用Java将ico格式转 PNG/JPG等格式
71 1
|
6月前
|
Java
使用java文件过滤器输出制定格式文件路径
使用java文件过滤器输出制定格式文件路径
|
4天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
6天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
6天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。