Load a RSA private key in Java (algid parse error,

2020-02-02 12:10发布

问题:

I'm trying to load a private RSA key generated with ssl into java, my code is:

Generate the key:

openssl genrsa -out mykey.pem 1024

Result:

-----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-----

Load:

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

and it throws an IOException : algid parse error, not a sequence. Where is the error?

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.<init>(Unknown Source)
at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(Unknown Source)
at sun.security.rsa.RSAKeyFactory.generatePrivate(Unknown Source)

回答1:

You could still load the keys if necessary,

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

all you need is the modulus and the exponent.



回答2:

Further to Julien Kronegg's answer, if you are getting this error because your file has a PKCS#1 format, you can use the following steps to convert it to a PKCS#8 file.

First, save your PKCS#1 key file to a file called priv1.pem:

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

Then, execute the following command:

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

This produces a file called priv8.pem, which is your key file in PKCS#8 format:

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

I use this from Java as follows:

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


回答3:

Here's a modified org.apache.jmeter.protocol.oauth.sampler.PrivateKeyReader code using Java runtime only. It works for me, it can load PKCS#1 or PKCS#8 private key file and returns a PrivateKey class. (Please let me know if i messed up anything while copy/pasting).

Use it like this: 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.
 * 
 * <p/>There is a cache so each file is only read once. If file
 * is changed, it will not take effect until the program 
 * restarts.
 * 
 * <p/>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<String, PrivateKey> keyCache =
    Collections.synchronizedMap(new HashMap<String, PrivateKey>());

  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.
     * 
     * <p/>The ASN.1 syntax for the private key with CRT is
     * 
     * <pre>
     * -- 
     * -- 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 
     * }
     * </pre>
     * 
     * @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.
 * 
 * <p/>This parser can only handle one layer. To parse nested constructs,
 * get a new parser for each layer using <code>Asn1Object.getParser()</code>.
 * 
 * <p/>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 <code>Asn1Object.getParser</code>.
   * 
   * @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.
   * 
   * <p/>In BER/DER encoding, length can be encoded in 2 forms,
   * <ul>
   * <li>Short form. One octet. Bit 8 has value "0" and bits 7-1
   * give the length.
     * <li>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.
   * </ul>
   * @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.
   * 
   * <p/>The first byte in DER encoding is made of following fields,
   * <pre>
   *-------------------------------------------------
     *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
     *-------------------------------------------------
     *|  Class    | CF  |     +      Type             |
     *-------------------------------------------------
   * </pre>
   * <ul>
   * <li>Class: Universal, Application, Context or Private
   * <li>CF: Constructed flag. If 1, the field is constructed.
   * <li>Type: This is actually called tag in ASN.1. It
   * indicates data type (Integer, String) or a construct
   * (sequence, choice, set).
   * </ul>
   * 
   * @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);
  }
}


回答4:

You get this error because you are reading a PKCS#8 key file, but your file has a PKCS#1 format (PKCS#1 has the BEGIN RSA PRIVATE KEY header while PKCS#8 has the BEGIN PRIVATE KEY header).

In order to read both PKCS#1 and PKCS#8 PEM files, I use the source code of Apache JMeter's org.apache.jmeter.protocol.oauth.sampler.PrivateKeyReader:

PrivateKey pk = (new PrivateKeyReader("/path/to/myfile.der")).getPrivateKey();



回答5:

try this :

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


回答6:

Alternately you can conver your mykey.pem to DER format and use your existing code. Following is the openssl command to do that

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

The new formatted file will look some thing like

 

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