在Java中加载RSA私钥(algid解析错误,而不是序列)

我正在尝试将使用ssl生成的私有RSA密钥加载到java中,我的代码是:

生成密钥

openssl genrsa -out mykey.pem 1024 

结果:

 -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCUibP4fY2PA/sGMKMbU6usuIGcOAqgQjD6c2ylVo05Oz7pgjnE +O0l2MFRUYUGT5KKk/W+0cAXkxaQHE3n8A8X1mHT8eMDmWnzz0PeYjDE8LQmAw8R Y2FnVKFAB36BIjdb5FsZmCk5QYKU5+nWLMqH/j/IR5AyX5wR2SMoslUg2QIDAQAB AoGAeJ1s7638IhLIZtldyRXjRKi6ToFPV50IKodJxOSIXt3WE0V05ZaA84eUSxUY IOzCgRbuqVmnUz1USAdD18AecC8qc7tXTRALLy7q8fxklPwmGPUOvTFmI7gRMUnv cWrq1gySk3SKpj0YmWnuY9Xmd2+xoWLzUeFD1CROY5OTjIECQQDDlp1+Al7+duR0 XyMlkWLIk0nIbkQ5zlTAEipzmaeTSOJi6YG3EMMz3AGuZb7tw6HFxWqeg1hyKJ+T cTM3WTdJAkEAwmrCDKE29n3wFOBKsZZFQbDgVOUXCBs2ubEI+ALe1DJU5BlfnrhJ OINRCNgnwSFNbwxDTkDpR6J6Av2ElAvNEQJAV0dVvk5Wj50Ecz2lFHWdLD41taAn B9igDxnMIcvWcK4cf+ENhmCPiwvJIEa8/aLIBNYErvmTtVWVaBkirrc8KQJABr+z +sJB6S6X/fGHRkDkKJKeRvQo54QiUzHdENbwq0cQAVcMJbNZ/1c3oen2/1JLoNY5 I+dG8dCnEaGBT65VMQJBAIDqH1Kqs5tb51cpt6h9ot31SUVud5pSML/babwp3pRs 1s6poreym4PkAyRug0Dgcj1zVLt25TlOHvrL9r3Swq8= -----END RSA PRIVATE KEY----- 

加载:

 String privKeyPEM=readFile("mykey.pem"); privKeyPEM= privKeyPEM.replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("\n", ""); // Remove the first and last lines 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); 

并抛出IOException : algid parse error, not a sequence 。 错误在哪里?

 Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(Unknown Source) at java.security.KeyFactory.generatePrivate(Unknown Source) at base54.encrypt.RSAToy.main(RSAToy.java:36) Caused by: java.security.InvalidKeyException: IOException : algid parse error, not a sequence at sun.security.pkcs.PKCS8Key.decode(Unknown Source) at sun.security.pkcs.PKCS8Key.decode(Unknown Source) at sun.security.rsa.RSAPrivateCrtKeyImpl.(Unknown Source) at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(Unknown Source) at sun.security.rsa.RSAKeyFactory.generatePrivate(Unknown Source) 

如有必要,您仍然可以加载密钥,

 public static PublicKey bigIntegerToPublicKey(BigInteger e, BigInteger m) { RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e); KeyFactory fact = KeyFactory.getInstance("RSA"); PublicKey pubKey = fact.generatePublic(keySpec); return pubKey; } public static PrivateKey bigIntegerToPrivateKey(BigInteger e, BigInteger m) { RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(m, e); KeyFactory fact = KeyFactory.getInstance("RSA"); PrivateKey privKey = fact.generatePrivate(keySpec); return privKey; } 

你需要的只是模数和指数。

继Julien Kronegg的回答之后,如果由于文件具有PKCS#1格式而收到此错误,则可以使用以下步骤将其转换为PKCS#8文件。

首先,将PKCS#1密钥文件保存到名为priv1.pem的文件中:

 -----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY----- 

然后,执行以下命令:

 openssl pkcs8 -topk8 -inform PEM -outform PEM -in priv1.pem -out priv8.pem -nocrypt 

这会生成一个名为priv8.pem的文件,它是PKCS#8格式的密钥文件:

 -----BEGIN PRIVATE KEY----- [...] -----END PRIVATE KEY----- 

我在Java中使用它如下:

 String PRIVATE_RSA_KEY_PKCS8 = "-----BEGIN PRIVATE KEY-----\n" + "MDSTofml23d....\n" + [...] + "-----END PRIVATE KEY-----\n"; String key = PRIVATE_RSA_KEY_PKCS8 .replace("-----BEGIN PRIVATE KEY-----\n", "") .replace("\n-----END PRIVATE KEY-----\n", ""); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(DatatypeConverter.parseBase64Binary(key)); try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] bytes = parseBase64Binary(encryptedNodeIdentifier); byte[] decryptedData = cipher.doFinal(bytes); return new String(decryptedData); } catch (GeneralSecurityException e) { return ""; } 

这是仅使用Java运行时的修改后的org.apache.jmeter.protocol.oauth.sampler.PrivateKeyReader代码。 它适用于我,它可以加载PKCS#1或PKCS#8私钥文件并返回一个PrivateKey类。 (如果我在复制/粘贴时弄乱了任何东西,请告诉我)。

像这样使用它:PrivateKey pk =(new PrivateKeyReader(“/ path / to / myfile.der”))。getPrivateKey();

 /** * */ package default; /**************************************************************************** * Copyright (c) 1998-2010 AOL Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Modified 23-4-2015 misterti * ****************************************************************************/ import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.xml.bind.DatatypeConverter; /** * Class for reading RSA private key from PEM file. It uses * the JMeter FileServer to find the file. So the file should * be located in the same directory as the test plan if the * path is relative. * * 

There is a cache so each file is only read once. If file * is changed, it will not take effect until the program * restarts. * *

It can read PEM files with PKCS#8 or PKCS#1 encodings. * It doesn't support encrypted PEM files. * */ public class PrivateKeyReader { // Private key file using PKCS #1 encoding public static final String P1_BEGIN_MARKER = "-----BEGIN RSA PRIVATE KEY"; //$NON-NLS-1$ public static final String P1_END_MARKER = "-----END RSA PRIVATE KEY"; //$NON-NLS-1$ // Private key file using PKCS #8 encoding public static final String P8_BEGIN_MARKER = "-----BEGIN PRIVATE KEY"; //$NON-NLS-1$ public static final String P8_END_MARKER = "-----END PRIVATE KEY"; //$NON-NLS-1$ private static Map keyCache = Collections.synchronizedMap(new HashMap()); protected final String fileName; /** * Create a PEM private key file reader. * * @param fileName The name of the PEM file */ public PrivateKeyReader(String fileName) { this.fileName = fileName; } /** * Get a Private Key for the file. * * @return Private key * @throws IOException */ public PrivateKey getPrivateKey() throws IOException, GeneralSecurityException { PrivateKey key = null; FileInputStream fis = null; boolean isRSAKey = false; try { File f = new File(fileName); fis = new FileInputStream(f); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); StringBuilder builder = new StringBuilder(); boolean inKey = false; for (String line = br.readLine(); line != null; line = br.readLine()) { if (!inKey) { if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) { inKey = true; isRSAKey = line.contains("RSA"); } continue; } else { if (line.startsWith("-----END ") && line.endsWith(" PRIVATE KEY-----")) { inKey = false; isRSAKey = line.contains("RSA"); break; } builder.append(line); } } KeySpec keySpec = null; byte[] encoded = DatatypeConverter.parseBase64Binary(builder.toString()); if (isRSAKey) { keySpec = getRSAKeySpec(encoded); } else { keySpec = new PKCS8EncodedKeySpec(encoded); } KeyFactory kf = KeyFactory.getInstance("RSA"); key = kf.generatePrivate(keySpec); } finally { if (fis != null) try { fis.close(); } catch (Exception ign) {} } return key; } /** * Convert PKCS#1 encoded private key into RSAPrivateCrtKeySpec. * *

The ASN.1 syntax for the private key with CRT is * *
 * -- * -- Representation of RSA private key with information for the CRT algorithm. * -- * RSAPrivateKey ::= SEQUENCE { * version Version, * modulus INTEGER, -- n * publicExponent INTEGER, -- e * privateExponent INTEGER, -- d * prime1 INTEGER, -- p * prime2 INTEGER, -- q * exponent1 INTEGER, -- d mod (p-1) * exponent2 INTEGER, -- d mod (q-1) * coefficient INTEGER, -- (inverse of q) mod p * otherPrimeInfos OtherPrimeInfos OPTIONAL * } * 

* * @param keyBytes PKCS#1 encoded key * @return KeySpec * @throws IOException */ private RSAPrivateCrtKeySpec getRSAKeySpec(byte[] keyBytes) throws IOException { DerParser parser = new DerParser(keyBytes); Asn1Object sequence = parser.read(); if (sequence.getType() != DerParser.SEQUENCE) throw new IOException("Invalid DER: not a sequence"); //$NON-NLS-1$ // Parse inside the sequence parser = sequence.getParser(); parser.read(); // Skip version BigInteger modulus = parser.read().getInteger(); BigInteger publicExp = parser.read().getInteger(); BigInteger privateExp = parser.read().getInteger(); BigInteger prime1 = parser.read().getInteger(); BigInteger prime2 = parser.read().getInteger(); BigInteger exp1 = parser.read().getInteger(); BigInteger exp2 = parser.read().getInteger(); BigInteger crtCoef = parser.read().getInteger(); RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec( modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef); return keySpec; } } /** * A bare-minimum ASN.1 DER decoder, just having enough functions to * decode PKCS#1 private keys. Especially, it doesn't handle explicitly * tagged types with an outer tag. * *

This parser can only handle one layer. To parse nested constructs, * get a new parser for each layer using Asn1Object.getParser(). * *

There are many DER decoders in JRE but using them will tie this * program to a specific JCE/JVM. * * @author zhang * */ class DerParser { // Classes public final static int UNIVERSAL = 0x00; public final static int APPLICATION = 0x40; public final static int CONTEXT = 0x80; public final static int PRIVATE = 0xC0; // Constructed Flag public final static int CONSTRUCTED = 0x20; // Tag and data types public final static int ANY = 0x00; public final static int BOOLEAN = 0x01; public final static int INTEGER = 0x02; public final static int BIT_STRING = 0x03; public final static int OCTET_STRING = 0x04; public final static int NULL = 0x05; public final static int OBJECT_IDENTIFIER = 0x06; public final static int REAL = 0x09; public final static int ENUMERATED = 0x0a; public final static int RELATIVE_OID = 0x0d; public final static int SEQUENCE = 0x10; public final static int SET = 0x11; public final static int NUMERIC_STRING = 0x12; public final static int PRINTABLE_STRING = 0x13; public final static int T61_STRING = 0x14; public final static int VIDEOTEX_STRING = 0x15; public final static int IA5_STRING = 0x16; public final static int GRAPHIC_STRING = 0x19; public final static int ISO646_STRING = 0x1A; public final static int GENERAL_STRING = 0x1B; public final static int UTF8_STRING = 0x0C; public final static int UNIVERSAL_STRING = 0x1C; public final static int BMP_STRING = 0x1E; public final static int UTC_TIME = 0x17; public final static int GENERALIZED_TIME = 0x18; protected InputStream in; /** * Create a new DER decoder from an input stream. * * @param in * The DER encoded stream */ public DerParser(InputStream in) throws IOException { this.in = in; } /** * Create a new DER decoder from a byte array. * * @param The * encoded bytes * @throws IOException */ public DerParser(byte[] bytes) throws IOException { this(new ByteArrayInputStream(bytes)); } /** * Read next object. If it's constructed, the value holds * encoded content and it should be parsed by a new * parser from Asn1Object.getParser. * * @return A object * @throws IOException */ public Asn1Object read() throws IOException { int tag = in.read(); if (tag == -1) throw new IOException("Invalid DER: stream too short, missing tag"); //$NON-NLS-1$ int length = getLength(); byte[] value = new byte[length]; int n = in.read(value); if (n < length) throw new IOException("Invalid DER: stream too short, missing value"); //$NON-NLS-1$ Asn1Object o = new Asn1Object(tag, length, value); return o; } /** * Decode the length of the field. Can only support length * encoding up to 4 octets. * *

In BER/DER encoding, length can be encoded in 2 forms, *

    *

  • Short form. One octet. Bit 8 has value "0" and bits 7-1 * give the length. *
  • Long form. Two to 127 octets (only 4 is supported here). * Bit 8 of first octet has value "1" and bits 7-1 give the * number of additional length octets. Second and following * octets give the length, base 256, most significant digit first. *

* @return The length as integer * @throws IOException */ private int getLength() throws IOException { int i = in.read(); if (i == -1) throw new IOException("Invalid DER: length missing"); //$NON-NLS-1$ // A single byte short length if ((i & ~0x7F) == 0) return i; int num = i & 0x7F; // We can't handle length longer than 4 bytes if ( i >= 0xFF || num > 4) throw new IOException("Invalid DER: length field too big (" //$NON-NLS-1$ + i + ")"); //$NON-NLS-1$ byte[] bytes = new byte[num]; int n = in.read(bytes); if (n < num) throw new IOException("Invalid DER: length too short"); //$NON-NLS-1$ return new BigInteger(1, bytes).intValue(); } } /** * An ASN.1 TLV. The object is not parsed. It can * only handle integers and strings. * * @author zhang * */ class Asn1Object { protected final int type; protected final int length; protected final byte[] value; protected final int tag; /** * Construct a ASN.1 TLV. The TLV could be either a * constructed or primitive entity. * *

The first byte in DER encoding is made of following fields, *

 *------------------------------------------------- *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1| *------------------------------------------------- *| Class | CF | + Type | *------------------------------------------------- * 

*

    *

  • Class: Universal, Application, Context or Private *
  • CF: Constructed flag. If 1, the field is constructed. *
  • Type: This is actually called tag in ASN.1. It * indicates data type (Integer, String) or a construct * (sequence, choice, set). *

* * @param tag Tag or Identifier * @param length Length of the field * @param value Encoded octet string for the field. */ public Asn1Object(int tag, int length, byte[] value) { this.tag = tag; this.type = tag & 0x1F; this.length = length; this.value = value; } public int getType() { return type; } public int getLength() { return length; } public byte[] getValue() { return value; } public boolean isConstructed() { return (tag & DerParser.CONSTRUCTED) == DerParser.CONSTRUCTED; } /** * For constructed field, return a parser for its content. * * @return A parser for the construct. * @throws IOException */ public DerParser getParser() throws IOException { if (!isConstructed()) throw new IOException("Invalid DER: can't parse primitive entity"); //$NON-NLS-1$ return new DerParser(value); } /** * Get the value as integer * * @return BigInteger * @throws IOException */ public BigInteger getInteger() throws IOException { if (type != DerParser.INTEGER) throw new IOException("Invalid DER: object is not integer"); //$NON-NLS-1$ return new BigInteger(value); } /** * Get value as string. Most strings are treated * as Latin-1. * * @return Java string * @throws IOException */ public String getString() throws IOException { String encoding; switch (type) { // Not all are Latin-1 but it's the closest thing case DerParser.NUMERIC_STRING: case DerParser.PRINTABLE_STRING: case DerParser.VIDEOTEX_STRING: case DerParser.IA5_STRING: case DerParser.GRAPHIC_STRING: case DerParser.ISO646_STRING: case DerParser.GENERAL_STRING: encoding = "ISO-8859-1"; //$NON-NLS-1$ break; case DerParser.BMP_STRING: encoding = "UTF-16BE"; //$NON-NLS-1$ break; case DerParser.UTF8_STRING: encoding = "UTF-8"; //$NON-NLS-1$ break; case DerParser.UNIVERSAL_STRING: throw new IOException("Invalid DER: can't handle UCS-4 string"); //$NON-NLS-1$ default: throw new IOException("Invalid DER: object is not a string"); //$NON-NLS-1$ } return new String(value, encoding); } }

您收到此错误是因为您正在读取PKCS#8密钥文件,但您的文件具有PKCS#1格式(PKCS#1具有BEGIN RSA PRIVATE KEY标头,而PKCS#8具有BEGIN PRIVATE KEY标头)。

为了读取PKCS#1和PKCS#8 PEM文件,我使用了Apache JMeter的org.apache.jmeter.protocol.oauth.sampler.PrivateKeyReader的源代码:

PrivateKey pk =(new PrivateKeyReader(“/ path / to / myfile.der”))。getPrivateKey();

尝试这个 :

 java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 

或者,您可以将mykey.pem转换为DER格式并使用现有代码。 以下是执行此操作的openssl命令

 openssl pkcs8 -topk8 -inform PEM -outform DER -in mykey.pem -out mykey.der -nocrypt 

新的格式化文件看起来像

 3082 0276 0201 0030 0d06 092a 8648 86f7 0d01 0101 0500 0482 0260 3082 025c 0201 0002 8181 00a9 0689 7676 2a63 5779 e4e8 4bb5 8e53 bc44 a975 8254 cb64 5cf2 1499 ad1c 6c35 6027 e357 ac09 0421 2b4b ddb4 1be3 a688 a848 5618 bce8 25d0 2cf2 972b 3801 a023 d6ee d824 4cfb 078f dad8 4317 fc9d 9468 27cc 2f08 f755 3293 76cb 3c60 4302 1c59 a8e8 5ac9 0576 8bb0 6bbb 8f1a 092d 7884 a405 c775 a8ca 7a2c 8037 435c 557a c9f0 a702 0301 0001 0281 8100 8462 6c53 ee25 30fd 98a9 230f f939 6a78 30c7 1114 6d59 8858 0bfa fa8a 4d92 ab13 8eea 4f06 9d61 30a1 7aa0 40aa ff58 b5fc 27fb d710 4e3b 1f9b b4bd 95ca 1deb d165 063a da5c 383d e428 cbf6 1f62 4549 5b96 ee2d b811 4cff c849 9637 67e4 a5d5 1c75 f13b 8ddd b212 ba2f 7118 885e bc98 cab6 5434 d1e8 2a33 c408 1186 be05 6074 7431 0241 00d8 a1e2 d382 d52a 372e 0a62 807f 8283 e615 c7a7 180b e21c b6ce cd55 940f 542b 903f 86a0 e488 74b2 d69d d5d1 27d9 4079 72a2 80f8 c105 3e19 309b dd78 4dbe 440d 0d02 4100 c7bd e5fd ccee 60b2 cbc0 7520 53c1 6442 9f11 e0d0 969f 6315 41cd 7b3c c279 cebe 4025 a4f4 97c9 6d1b c48f 6f01 cd3b e849 33e6 5f13 7fb6 2780 22d1 f2da