我有一个私钥文件(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-----
这里发布了类似的问题,但这些问题对我来说无济于事。 几乎所有人都建议使用不愿意使用的Bouncycastle提供商,因为我应该使用符合FIPS标准的提供商,并且不确定BC提供商是否符合FIPS标准。
帮助我解决这个问题将受到高度赞赏...... 提前谢谢。
答案 0 :(得分:36)
你刚刚发布了私钥,所以现在全世界都知道它是什么。希望这只是为了测试。
编辑:Others已注意到已发布密钥的openssl文本标题----- BEGIN RSA PRIVATE KEY -----表示它是PKCS#1。但是,有关密钥的实际Base64内容是PKCS#8。很明显OP复制并将PKCS#1密钥的标题和预告片粘贴到PKCS#8密钥上,原因不明。我在下面提供的示例代码适用于PKCS#8私钥。
以下是一些将根据该数据创建私钥的代码。您必须使用IBM Base64解码器替换Base64解码。
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);
}
}
答案 1 :(得分:28)
这是私钥的PKCS#1格式。试试这个代码。它不使用Bouncy Castle或其他第三方加密提供商。只需java.security和sun.security进行DER sequece解析。它还支持解析PKCS#8格式的私钥(PEM文件有一个标题" ----- BEGIN PRIVATE KEY -----")。
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");
}
答案 2 :(得分:22)
您将遇到的问题是有两种类型的PEM格式化密钥:PKCS8和SSLeay。 OpenSSL似乎根据命令使用它们没有帮助:
通常的openssl genrsa
命令将生成SSLeay格式的PEM。从带有openssl pkcs12 -in file.p12
的PKCS12文件导出将创建PKCS8文件。
后一种PKCS8格式可以使用PKCS8EncodedKeySpec
在Java中本机打开。另一方面,SSLeay格式化的密钥无法本机打开。
要打开SSLeay私钥,您可以像以前一样使用BouncyCastle提供程序,或者Not-Yet-Commons-SSL从BouncyCastle借用了少量必要代码,以支持在PEM和DER格式中解析PKCS8和SSLeay密钥:http://juliusdavies.ca/commons-ssl/pkcs8.html。 (我不确定Not-Yet-Commons-SSL是否符合FIPS标准)
通过OpenSSL手册页的推断,两种格式的关键标题如下:
PKCS8格式
未加密:-----BEGIN PRIVATE KEY-----
加密:-----BEGIN ENCRYPTED PRIVATE KEY-----
SSLeay格式
-----BEGIN RSA PRIVATE KEY-----
(这些似乎与其他答案相矛盾,但我使用PKCS8EncodedKeySpec
测试了OpenSSL的输出。只有PKCS8键,显示----BEGIN PRIVATE KEY-----
本地工作)
答案 3 :(得分:16)
解析PKCS1(只有PKCS8格式在Android上开箱即用)键在Android上是一项繁琐的任务,因为缺少ASN1支持,但如果你包含Spongy castle jar来读取DER整数,则可以解决这个问题
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);
}
答案 4 :(得分:5)
正如其他人已经做出回应,您尝试解析的密钥没有正确的PKCS#8标头,Oracle PKCS8EncodedKeySpec
需要了解它。如果您不想使用openssl pkcs8
转换密钥或使用JDK内部API解析密钥,您可以添加PKCS#8标头,如下所示:
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));
答案 5 :(得分:0)
确保您的id_rsa文件没有任何扩展名,例如.txt或.rtf。富文本格式为文件添加了其他字符,并将这些字符添加到字节数组中。这最终导致无效的私钥错误。长话短说,复制文件,而不是内容。
答案 6 :(得分:0)
您将在下面找到一些代码,用于读取以以下格式编码的未加密 RSA密钥:
-----BEGIN RSA PRIVATE KEY-----
)-----BEGIN PRIVATE KEY-----
)它可与Java 7+(及之后的9)一起使用,并且不使用第三方库(例如BouncyCastle)或内部Java API(例如DerInputStream
或DerValue
)。
private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----";
private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----";
private static final String PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----";
public static PrivateKey loadKey(String keyFilePath) throws GeneralSecurityException, IOException {
byte[] keyDataBytes = Files.readAllBytes(Paths.get(keyFilePath));
String keyDataString = new String(keyDataBytes, StandardCharsets.UTF_8);
if (keyDataString.contains(PKCS_1_PEM_HEADER)) {
// OpenSSL / PKCS#1 Base64 PEM encoded file
keyDataString = keyDataString.replace(PKCS_1_PEM_HEADER, "");
keyDataString = keyDataString.replace(PKCS_1_PEM_FOOTER, "");
return readPkcs1PrivateKey(Base64.decodeBase64(keyDataString));
}
if (keyDataString.contains(PKCS_8_PEM_HEADER)) {
// PKCS#8 Base64 PEM encoded file
keyDataString = keyDataString.replace(PKCS_8_PEM_HEADER, "");
keyDataString = keyDataString.replace(PKCS_8_PEM_FOOTER, "");
return readPkcs8PrivateKey(Base64.decodeBase64(keyDataString));
}
// We assume it's a PKCS#8 DER encoded binary file
return readPkcs8PrivateKey(Files.readAllBytes(Paths.get(keyFilePath)));
}
private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecurityException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SunRsaSign");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
try {
return keyFactory.generatePrivate(keySpec);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException("Unexpected key format!", e);
}
}
private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes) throws GeneralSecurityException {
// We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand
int pkcs1Length = pkcs1Bytes.length;
int totalLength = pkcs1Length + 22;
byte[] pkcs8Header = new byte[] {
0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte) (totalLength & 0xff), // Sequence + total length
0x2, 0x1, 0x0, // Integer (0)
0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte) (pkcs1Length & 0xff) // Octet string + length
};
byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes);
return readPkcs8PrivateKey(pkcs8bytes);
}
private static byte[] join(byte[] byteArray1, byte[] byteArray2){
byte[] bytes = new byte[byteArray1.length + byteArray2.length];
System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length);
return bytes;
}