在 Java 中加载 RSA 私钥(algid 解析错误,不是序列)
Posted
技术标签:
【中文标题】在 Java 中加载 RSA 私钥(algid 解析错误,不是序列)【英文标题】:Load a RSA private key in Java (algid parse error, not a sequence) 【发布时间】:2013-02-26 22:57:25 【问题描述】:我正在尝试将使用 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.<init>(Unknown Source)
at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(Unknown Source)
at sun.security.rsa.RSAKeyFactory.generatePrivate(Unknown Source)
【问题讨论】:
也许你可以用十六进制显示base64解码的前10个字节? openssl pkcs8 -topk8 ... 是你的朋友。 openssl rsa 生成裸 rsa 密钥而不是 PKCS8 密钥。 你能把它作为答案发布吗,nullix?我刚刚验证这是一个“裸”PKCS#1 格式的私钥(只有模数、公共和私有指数以及 CRT 参数) 请考虑在此处更改接受的答案。谢谢。 【参考方案1】:如果需要,您仍然可以加载密钥,
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;
您只需要模数和指数。
【讨论】:
从哪里定义e和m参数? 这些是对应 rsa 键的指数和模数。在 PrivateKey-Class 中,您可以使用 getPrivateExponent() 和 getModulus() 以及 PublicKey 中的相同。您可以使用"KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); KeyPair kp = kpg.genKeyPair();" 生成两者。 如果已经生成了密钥怎么办?即,如果有人在字符串响应中向你发送他们的公钥?您将如何从中生成公钥? 这取决于格式。我需要更多信息来帮助您。【参考方案2】:您收到此错误是因为您正在读取 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();
【讨论】:
能否请您添加一个使用示例,例如,详细说明如何使用 Apache 的源代码从字节数组中加载 PKCS#1 密钥并将其放入 PrivateKey 实例? 您应该阅读 PrivateKeyReader,它将文件读入 byte[],然后构建 PrivateKey。【参考方案3】:根据 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 "";
【讨论】:
【参考方案4】:或者,您可以将 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
【讨论】:
【参考方案5】:这是修改后的 org.apache.jmeter.protocol.oauth.sampler.PrivateKeyReader 代码,仅使用 Java 运行时。它对我有用,它可以加载 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.
*
* <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);
【讨论】:
非常感谢,这段代码很好用!不需要任何第三方库。【参考方案6】:试试这个:
java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
【讨论】:
不只是添加提供者吗?不应该围绕KeyFactory
做点什么吗?
只是想给我两分钱,我正在一起使用 Netty 和 Bouncy Castle API,并且在这个和那个上有大量的 SSL 异常,包括 OP 遇到的问题。我很怀疑,但将上述代码添加到我的 SSL 初始化实际上解决了我所有的 SSL 问题。我惊呆了它的工作。
@jww 您好,这是因为 BouncyCastle 支持 PKCS#1 而 java 安全性不支持。添加提供程序我很确定 OP 的代码可以与 PKCS#1 证书一起使用,因此它实际上是正确的答案。以上是关于在 Java 中加载 RSA 私钥(algid 解析错误,不是序列)的主要内容,如果未能解决你的问题,请参考以下文章