从PEM BASE64获取RSA私钥编码的私钥文件

我有一个私钥文件(PEM BASE64编码)。 我想用它来解密一些其他的数据。使用Java我试图读取文件并解码BASE64编码的数据…这是我试过的代码片段….

import java.io.*; import java.nio.ByteBuffer; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import com.ibm.crypto.fips.provider.RSAPrivateKey; import com.ibm.misc.BASE64Decoder; public class GetPrivateKey { public static RSAPrivateKey get() throws Exception { File privateKeyFile = new File("privatekey.key"); byte[] encodedKey = new byte[(int) privateKeyFile.length()]; new FileInputStream(privateKeyFile).read(encodedKey); ByteBuffer keyBytes = new BASE64Decoder().decodeBufferToByteBuffer(encodedKey.toString()); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes.array()); KeyFactory kf = KeyFactory.getInstance("RSA", "IBMJCEFIPS"); RSAPrivateKey pk = (RSAPrivateKey) kf.generatePrivate(privateKeySpec); return pk; } public static void main(String[] args) throws Exception { PrivateKey privKey = FormatMePlease.get(); System.out.println(privKey.toString()); } } 

我收到以下错误

 Exception in thread "main" java.security.spec.InvalidKeySpecException: Inappropriate key specification: DerInputStream.getLength(): lengthTag=127, too big. at com.ibm.crypto.fips.provider.RSAKeyFactory.b(Unknown Source) at com.ibm.crypto.fips.provider.RSAKeyFactory.engineGeneratePrivate(Unknown Source) at java.security.KeyFactory.generatePrivate(Unknown Source) at GetPrivateKey.get(GetPrivateKey.java:24) at GetPrivateKey.main(GetPrivateKey.java:29) 

文件“privatekey.key”的内容

 -----BEGIN RSA PRIVATE KEY----- MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAF53wUbKmDHtvfOb8u1HPqEBFNNF csnOMjIcSEhAwIQMbgrOuQ+vH/YgXuuDJaURS85H8P4UTt6lYOJn+SFnXvS82E7LHJpVrWwQzbh2 QKh13/akPe90DlNTUGEYO7rHaPLqTlld0jkLFSytwqfwqn9yrYpM1ncUOpCciK5j8t8MzO71LJoJ g24CFxpjIS0tBrJvKzrRNcxWSRDLmu2kNmtsh7yyJouE6XoizVmBmNVltHhFaDMmqjugMQA2CZfL rxiR1ep8TH8IBvPqysqZI1RIpB/e0engP4/1KLrOt+6gGS0JEDh1kG2fJObl+N4n3sCOtgaz5Uz8 8jpwbmZ3Se8CAwEAAQKCAQAdOsSs2MbavAsIM3qo/GBehO0iqdxooMpbQvECmjZ3JTlvUqNkPPWQ vFdiW8PsHTvtackhdLsqnNUreKxXL5rr8vqi9qm0/0mXpGNi7gP3m/FeaVdYnfpIwgCe6lag5k6M yv7PG/6N8+XrWyBdwlOe96bGohvB4Jp2YFjSTM67QONQ8CdmfqokqJ8/3RyrpDvGN3iX3yzBqXGO jPkoJQv3I4lsYdR0nl4obHHnMSeWCQCYvJoZ7ZOliu/Dd0ksItlodG6s8r/ujkSa8VIhe0fnXTf0 i7lqa55CAByGN4MOR0bAkJwIB7nZzQKurBPcTAYJFFvAc5hgMnWT0XW83TehAoGBALVPGnznScUw O50OXKI5yhxGf/XDT8g28L8Oc4bctRzI+8YfIFfLJ57uDGuojO/BpqtYmXmgORru0jYR8idEkZrx gf62czOiJrCWTkBCEMtrNfFHQJQCQrjfbHofp7ODnEHbHFm7zdlbfNnEBBaKXxd2rVv4UTEhgftv wsHcimbXAoGBAIViWrHWElMeQT0datqlThE/u51mcK4VlV7iRWXVa1/gAP85ZAu44VvvDlkpYVkF zSRR+lHSOzsubDMN45OBQW6UA3RPg4TCvrTOmhQUeF5XPuSdcD0R2At6pdaLwAKnOtILg13Ha6ym Igjv8glodvem3hWLmpHIhNBiaXtf8wqpAoGADH5a8OhvKOtd8EChGXyp9LDW+HRw9vbyN/gi9dQX ltgyoUBb1jDllgoJSRHgRFUvyvbb/ImR5c03JwqtiQ8siWTC9G5WGeS+jcSNt9fVmG7W1L14MbrG Jj8fFns/7xrOlasnlPdgA+5N+CONtI/sZY2D/KZr0drhPhZBcWJlFxkCgYAn+4SOPEo/6hjKNhA6 vER7fSxDEVsDg+rDh3YgAWpvUdlaqBxqOyAqi600YugQZGHK2lv7vNYOdmrunuIx7BPuDqY+bjtR R4Mc9bVQAZbXSLXMl7j2RWwKfNhLSJbk9LX4EoVtTgLjvOUE4tAdq9fFgpqdwLwzqPTO9kECP4++ CQKBgH6tO/xcNxG/uXUideluAn3H2KeyyznZMJ7oCvzf26/XpTAMI243OoeftiKVMgxuZ7hjwqfn /VHXABc4i5gchr9RzSb1hZ/IqFzq2YGmbppg5Ok2cgwalDoDBi21bRf8aDRweL62mO+7aPnCQZ58 j5W72PB8BAr6xg0Oro25O4os -----END RSA PRIVATE KEY----- 

类似的问题已经发布在这里,但这些对我来说没有任何作用。 他们几乎所有人都build议使用Bouncycastle提供程序,它不愿意使用那些应该使用符合FIPS的提供程序,并且不确定BC提供程序是否符合FIPS。

一个帮助在这里gettin我会高度赞赏…在此先感谢。

由于缺乏ASN1支持,parsingPKCS1(只有PKCS8格式在Android上开箱即可),结果是一个乏味的任务,但如果你包含Spongy城堡 jar来阅读DER Integers,可以解决。

 String privKeyPEM = key.replace( "-----BEGIN RSA PRIVATE KEY-----\n", "") .replace("-----END RSA PRIVATE KEY-----", ""); // Base64 decode the data byte[] encodedPrivateKey = Base64.decode(privKeyPEM, Base64.DEFAULT); try { ASN1Sequence primitive = (ASN1Sequence) ASN1Sequence .fromByteArray(encodedPrivateKey); Enumeration<?> e = primitive.getObjects(); BigInteger v = ((DERInteger) e.nextElement()).getValue(); int version = v.intValue(); if (version != 0 && version != 1) { throw new IllegalArgumentException("wrong version for RSA private key"); } /** * In fact only modulus and private exponent are in use. */ BigInteger modulus = ((DERInteger) e.nextElement()).getValue(); BigInteger publicExponent = ((DERInteger) e.nextElement()).getValue(); BigInteger privateExponent = ((DERInteger) e.nextElement()).getValue(); BigInteger prime1 = ((DERInteger) e.nextElement()).getValue(); BigInteger prime2 = ((DERInteger) e.nextElement()).getValue(); BigInteger exponent1 = ((DERInteger) e.nextElement()).getValue(); BigInteger exponent2 = ((DERInteger) e.nextElement()).getValue(); BigInteger coefficient = ((DERInteger) e.nextElement()).getValue(); RSAPrivateKeySpec spec = new RSAPrivateKeySpec(modulus, privateExponent); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey pk = kf.generatePrivate(spec); } catch (IOException e2) { throw new IllegalStateException(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } catch (InvalidKeySpecException e) { throw new IllegalStateException(e); } 

你刚刚发布了这个私钥,所以现在全世界都知道它是什么。 希望这只是为了testing。

编辑: 其他人已经注意到,公开密钥的openssl文本标题—– BEGIN RSA PRIVATE KEY —–表示它是PKCS#1。 但是,有关密钥的实际Base64内容是PKCS#8。 很明显,OP复制并将PKCS#1密钥的头部和尾部粘贴到PKCS#8密钥上,原因不明。 我在下面提供的示例代码使用PKCS#8私钥。

以下是一些将从该数据创build私钥的代码。 您必须用IBM Base64解码器replaceBase64解码。

 public class RSAToy { private static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEuwIBADAN ...skipped the rest\n" // + ... // + ... skipped the rest // + ... + "-----END RSA PRIVATE KEY-----"; public static void main(String[] args) throws Exception { // Remove the first and last lines String privKeyPEM = BEGIN_RSA_PRIVATE_KEY.replace("-----BEGIN RSA PRIVATE KEY-----\n", ""); privKeyPEM = privKeyPEM.replace("-----END RSA PRIVATE KEY-----", ""); System.out.println(privKeyPEM); // Base64 decode the data byte [] encoded = Base64.decode(privKeyPEM); // PKCS8 decode the encoded RSA private key PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privKey = kf.generatePrivate(keySpec); // Display the results System.out.println(privKey); } } 

这是私钥的PKCS#1格式。 试试这个代码。 它不使用Bouncy Castle或其他第三方encryption提供商。 只是java.security和sun.security为DER sequeceparsing。 此外,它支持以PKCS#8格式(具有标题“—– BEGIN PRIVATE KEY —–”的PEM文件)parsing私钥。

 import sun.security.util.DerInputStream; import sun.security.util.DerValue; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.Base64; public static PrivateKey pemFileLoadPrivateKeyPkcs1OrPkcs8Encoded(File pemFileName) throws GeneralSecurityException, IOException { // PKCS#8 format final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----"; final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----"; // PKCS#1 format final String PEM_RSA_PRIVATE_START = "-----BEGIN RSA PRIVATE KEY-----"; final String PEM_RSA_PRIVATE_END = "-----END RSA PRIVATE KEY-----"; Path path = Paths.get(pemFileName.getAbsolutePath()); String privateKeyPem = new String(Files.readAllBytes(path)); if (privateKeyPem.indexOf(PEM_PRIVATE_START) != -1) { // PKCS#8 format privateKeyPem = privateKeyPem.replace(PEM_PRIVATE_START, "").replace(PEM_PRIVATE_END, ""); privateKeyPem = privateKeyPem.replaceAll("\\s", ""); byte[] pkcs8EncodedKey = Base64.getDecoder().decode(privateKeyPem); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey)); } else if (privateKeyPem.indexOf(PEM_RSA_PRIVATE_START) != -1) { // PKCS#1 format privateKeyPem = privateKeyPem.replace(PEM_RSA_PRIVATE_START, "").replace(PEM_RSA_PRIVATE_END, ""); privateKeyPem = privateKeyPem.replaceAll("\\s", ""); DerInputStream derReader = new DerInputStream(Base64.getDecoder().decode(privateKeyPem)); DerValue[] seq = derReader.getSequence(0); if (seq.length < 9) { throw new GeneralSecurityException("Could not parse a PKCS1 private key."); } // skip version seq[0]; BigInteger modulus = seq[1].getBigInteger(); BigInteger publicExp = seq[2].getBigInteger(); BigInteger privateExp = seq[3].getBigInteger(); BigInteger prime1 = seq[4].getBigInteger(); BigInteger prime2 = seq[5].getBigInteger(); BigInteger exp1 = seq[6].getBigInteger(); BigInteger exp2 = seq[7].getBigInteger(); BigInteger crtCoef = seq[8].getBigInteger(); RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(keySpec); } throw new GeneralSecurityException("Not supported format of a private key"); } 

您将面对的问题是有两种types的PEM格式化密钥:PKCS8和SSLeay。 根据命令,OpenSSL似乎并没有帮助:

通常的openssl genrsa命令将生成SSLeay格式的PEM。 使用openssl pkcs12 -in file.p12从PKCS12文件openssl pkcs12 -in file.p12将创build一个PKCS8文件。

后者的PKCS8格式可以使用PKCS8EncodedKeySpec在Java中本地打开。 另一方面,SSLeay格式化的密钥不能以本地方式打开。

要打开SSLeay私钥,您可以像以前一样使用BouncyCastle提供程序,也可以使用Not-Yet-Commons-SSL从BouncyCastle借用最less量的必需代码来支持以PEM和DER格式parsingPKCS8和SSLeay密钥: http: //juliusdavies.ca/commons-ssl/pkcs8.html 。 (我不确定Not-Yet-Commons-SSL是否符合FIPS)

密钥格式标识

通过OpenSSL手册页的推断,两种格式的密钥标题如下所示:

PKCS8格式

非encryption: -----BEGIN PRIVATE KEY-----
encryption: -----BEGIN ENCRYPTED PRIVATE KEY-----encryption-----BEGIN ENCRYPTED PRIVATE KEY-----

SSLeay格式

-----BEGIN RSA PRIVATE KEY-----

(这些似乎与其他答案是矛盾的,但我已经使用PKCS8EncodedKeySpectesting了OpenSSL的输出。只有PKCS8键,显示----BEGIN PRIVATE KEY-----工作)

正如其他人所做的回应一样,您正在尝试parsing的密钥没有正确的PKCS#8标头,这是Oracle的PKCS8EncodedKeySpec需要理解的。 如果您不想使用openssl pkcs8转换密钥,或者使用JDK内部APIparsing密钥,则可以将PKCS#8头添加为如下forms:

 static final Base64.Decoder DECODER = Base64.getMimeDecoder(); private static byte[] buildPKCS8Key(File privateKey) throws IOException { final String s = new String(Files.readAllBytes(privateKey.toPath())); if (s.contains("--BEGIN PRIVATE KEY--")) { return DECODER.decode(s.replaceAll("-----\\w+ PRIVATE KEY-----", "")); } if (!s.contains("--BEGIN RSA PRIVATE KEY--")) { throw new RuntimeException("Invalid cert format: "+ s); } final byte[] innerKey = DECODER.decode(s.replaceAll("-----\\w+ RSA PRIVATE KEY-----", "")); final byte[] result = new byte[innerKey.length + 26]; System.arraycopy(DECODER.decode("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKY="), 0, result, 0, 26); System.arraycopy(BigInteger.valueOf(result.length - 4).toByteArray(), 0, result, 2, 2); System.arraycopy(BigInteger.valueOf(innerKey.length).toByteArray(), 0, result, 24, 2); System.arraycopy(innerKey, 0, result, 26, innerKey.length); return result; } 

一旦该方法就位,您可以将其输出到PKCS8EncodedKeySpec构造函数中,如下所示: new PKCS8EncodedKeySpec(buildPKCS8Key(privateKey));

确保你的id_rsa文件没有像.txt或.rtf这样的扩展名。 富文本格式将附加字符添加到您的文件,并将其添加到字节数组。 最终导致无效的私钥错误。 长话短说,复制文件,不是内容。