目录
一、序言
之前在 《前后端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提供者安装。
三、测试用例
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的。