以编程方式解析加密的 PKCS#8 编码的 pem 文件
Parsing encrypted PKCS#8 encoded pem file programatically
我无法弄清楚如何正确读取 pem 文件的私钥。我浏览了关于 Whosebug 的不同主题,但找不到解决方案。我想要实现的是从类路径中读取 pkcs#8 编码文件中的加密私钥,并将其作为内存密钥库中的密钥条目加载。下面是我尝试解析的私钥示例,密码是 secret
。它纯粹是为了在这里共享而创建的,因此它不是生产机器上使用的私钥。
我使用以下命令从 p12 文件创建了它:openssl pkcs12 -in key-pair.p12 -out key-pair.pem
有效示例(一次性)密钥对
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA
MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay
MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz
4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG
AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja
yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk
NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf
s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY
19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc
PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO
arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa
R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK
eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH
Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT
a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk
l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ
oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP
67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M
TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi
h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd
PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc
vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ
8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL
CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp
VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r
T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG
7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4
SRo=
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM
BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL
MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z
dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g
WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8
ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj
zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa
FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn
9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg
oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY
YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf
eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW
qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2
V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp
aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==
-----END CERTIFICATE-----
我知道 BouncyCastle 能够解析它,但我想避免使用额外的库。所以我想知道是否可以只使用普通 jdk 或其他一些轻量级库。
我已经能够使用以下 header/footer:
解析私钥
-----BEGIN PRIVATE KEY-----
*
-----END PRIVATE KEY-----
和
-----BEGIN RSA PRIVATE KEY-----
*
-----END RSA PRIVATE KEY-----
我已经使用以下代码片段来完成此操作:
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class App {
private static final String KEYSTORE_TYPE = "PKCS12";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String CERTIFICATE_TYPE = "X.509";
private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----", Pattern.DOTALL);
private static final String NEW_LINE = "\n";
private static final String EMPTY = "";
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
String privateKeyContent = "";
String certificateContent = "";
PrivateKey privateKey = parsePrivateKey(privateKeyContent);
Certificate[] certificates = parseCertificate(certificateContent).values()
.toArray(new Certificate[]{});
KeyStore keyStore = createEmptyKeyStore();
keyStore.setKeyEntry("client", privateKey, null, certificates);
}
private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
keyStore.load(null, null);
return keyStore;
}
private static PrivateKey parsePrivateKey(String identityContent) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
KeySpec keySpec = null;
Matcher privateKeyMatcher = PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = new PKCS8EncodedKeySpec(decodedPrivateKeyContent);
}
privateKeyMatcher = RSA_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = getKeySpecFromAsn1StructuredData(decodedPrivateKeyContent);
}
privateKeyMatcher = ENCRYPTED_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = null; //TODO
}
Objects.requireNonNull(keySpec);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws IOException {
try(ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
if (asn1Sequence.getValue().size() < 9) {
throw new RuntimeException("Parsed key content doesn't have the minimum required sequence size of 9");
}
BigInteger modulus = extractIntValueFrom(asn1Sequence.get(1));
BigInteger publicExponent = extractIntValueFrom(asn1Sequence.get(2));
BigInteger privateExponent = extractIntValueFrom(asn1Sequence.get(3));
BigInteger primeP = extractIntValueFrom(asn1Sequence.get(4));
BigInteger primeQ = extractIntValueFrom(asn1Sequence.get(5));
BigInteger primeExponentP = extractIntValueFrom(asn1Sequence.get(6));
BigInteger primeExponentQ = extractIntValueFrom(asn1Sequence.get(7));
BigInteger crtCoefficient = extractIntValueFrom(asn1Sequence.get(8));
return new RSAPrivateCrtKeySpec(
modulus, publicExponent, privateExponent,
primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient
);
}
}
private static BigInteger extractIntValueFrom(ASN1Object<?> asn1Object) {
if (asn1Object instanceof ASN1Integer) {
return ((ASN1Integer) asn1Object).getValue();
} else {
throw new RuntimeException(String.format(
"Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]",
asn1Object.getClass().getName(), ASN1Integer.class.getName())
);
}
}
private static Map<String, Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
Map<String, Certificate> certificates = new HashMap<>();
Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);
while (certificateMatcher.find()) {
String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
certificates.put(UUID.randomUUID().toString(), certificate);
}
}
return certificates;
}
}
我还需要处理 asn.1 编码的私钥。我可以使用 BouncyCastle,但我还是想避免使用它,因为我对替代方案很好奇。我从这个主题中得到启发: but DerInputStream and DerValue aren't accessible anymore from jdk 11 onwards. And the sun packages should be avoided... So I discovered asn-one java library 解析 asn.1 编码的私钥,而且效果很好。
我已经分享了我处理其他案例的完整片段,所以当有人想帮助我时,尝试起来会更容易。
来自以下文章:ASN.1 key structures in DER and PEM 我了解到具有以下 header/footer 的私钥:-----BEGIN ENCRYPTED PRIVATE KEY----- * -----END ENCRYPTED PRIVATE KEY-----
也是具有以下结构的 asn.1 编码私钥:
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm EncryptionAlgorithmIdentifier,
encryptedData EncryptedData
}
EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
EncryptedData ::= OCTET STRING
我也尝试了此处提供的答案 How to read a password encrypted key with java?,但这些也没有用。但是我不太确定如何将它正确解析为 java 对象并将其加载为 KeySpec。欢迎任何帮助!
======>
根据 dave_thompson_085
的输入更新了 2020 年 9 月 12 日的进度
Matcher privateKeyMatcher = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL)
.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
}
}
结果变成如下异常:java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required
语句cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
======>
根据 Michael Fehr
的输入更新了 2020 年 9 月 13 日的进度
Matcher privateKeyMatcher = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL)
.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
int keyLength = 24 * 8;
PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec);
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
}
}
此 PEM 格式的更官方参考是 rfc7468 section 11 which specifies that the ASN.1 content (after de-PEM) is PKCS8's encrypted form defined in rfc5208 section 6 and appendix A,将其稍微修改为:
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
encryptedData EncryptedData
}
EncryptedData ::= OCTET STRING
(redacted)
KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
... -- For local profiles
}
未能解释 PKCS8(PKCS12 除外)key-encryption 常用 算法是 PKCS5 v2 [=] 中定义的 password-based 加密24=](细微差别在这里无关紧要)。特别是对于 Q 中的密钥文件,AlgorithmIdentifier 解析(使用 OpenSSL)为:
4:d=1 hl=2 l= 64 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 51 cons: SEQUENCE
19:d=3 hl=2 l= 27 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 14 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:F7F1520689D469C6
44:d=5 hl=2 l= 2 prim: INTEGER :0800
48:d=3 hl=2 l= 20 cons: SEQUENCE
50:d=4 hl=2 l= 8 prim: OBJECT :des-ede3-cbc
60:d=4 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:9D1A44BCC1884EA7
PBES2 在 section 6.2 中被定义为由 PBKDF2 密钥派生和几乎任何对称 encryption/decryption 组成。
您应该能够看到 top-level 参数匹配 appendix A.4 and the parameters within the keyDerivationFunc part match PBKDF2-Params
in appendix A.2 (salt and iterationCount present, keyLength and prf omitted), while the parameter for the encryptionScheme part is simply an OCTET STRING containing the Initialization Vector (IV), see appendix B.2.2.
中的 PBES2-Params
因此,要解密它,您需要使用这些参数实现 section 5.2 中指定的 PBKDF2 密钥派生,Java 加密支持 SecretKeyFactory.getInstance("PBKDF2withHmacSHA1")
和 PBEKeySpec
、 然后 triple-DES 又名 TDEA 解密,使用 triple-length 密钥又名密钥选项 1 在 CBC 模式下使用 PKCS5/7 填充,Java 加密支持作为Cipher.getInstance("DESede/CBC/PKCS5Padding")
,使用第一步导出的密钥和输入数据的 IV 作为 IVParameterSpec
。结果将是一个 PKCS8-unencrypted 密钥结构,您可以 运行 通过合适的 KeyFactory
作为 PKCS8EncodedKeySpec
,就像您已经为未加密的输入格式。
虽然考虑到 JRE 通常在 100MB 以上,而 Bouncy 增加了大约 5MB 并且 shedloads 更易于使用,但我不能说我同意你的评估。
更新:我没有意识到,但是 JCE 的 PBKDF2 SKF 实际上并没有进行密钥推导,它只是打包数据元素以供合适的 Cipher
算法使用——而且它没有' 为我们这里需要的情况实现 Cipher
算法。所以你需要 手动编码 PBKDF2 幸运的是(?)我已经为 编码了。将它们放在一起,这是经过测试的代码:
static void SO63832456ManualPKCS8EPBES2 (String[] args) throws Exception {
String file = args[0], pass = args[1];
// I use Bouncy to read because it's much easier for me,
// but you should get the same results with any ASN.1 lib
PemObject obj; try(Reader rdr = new FileReader(file);
PEMParser p = new PEMParser(rdr) ){ obj = p.readPemObject(); }
if( ! obj.getType().equals("ENCRYPTED PRIVATE KEY") ) throw new Exception ("wrong type");
org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eki = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(obj.getContent());
AlgorithmIdentifier algid = eki.getEncryptionAlgorithm();
if( ! algid.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBES2) ) throw new Exception("not PBES2");
PBES2Parameters top = PBES2Parameters.getInstance(algid.getParameters());
KeyDerivationFunc kdf = top.getKeyDerivationFunc();
if( ! kdf.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBKDF2) ) throw new Exception("not PBKDF2");
PBKDF2Params kdp = PBKDF2Params.getInstance(kdf.getParameters());
EncryptionScheme esa = top.getEncryptionScheme();
if( ! esa.getAlgorithm().equals(PKCSObjectIdentifiers.des_EDE3_CBC) ) throw new Exception("not DES3");
ASN1OctetString esp = ASN1OctetString.getInstance(esa.getParameters());
// these are the (only) items actually used
byte[] salt = kdp.getSalt(), iv = esp.getOctets(), edata = eki.getEncryptedData();
int iter = kdp.getIterationCount().intValueExact();
//SecretKeyFactory fact = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
//SecretKey skey = fact.generateSecret(new PBEKeySpec(pass.toCharArray(), salt, iter, 24));
byte[] skey = PBKDF2 ("HmacSHA1", pass.getBytes("UTF-8"), salt, iter, 24);
Cipher ciph = Cipher.getInstance("DESEDE/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skey,"DESEDE"), new IvParameterSpec(iv));
byte[] plain = ciph.doFinal(edata);
KeyFactory fac2 = KeyFactory.getInstance("RSA");
PrivateKey pkey = fac2.generatePrivate(new PKCS8EncodedKeySpec(plain));
System.out.println ( ((java.security.interfaces.RSAPrivateKey)pkey).getModulus() );
}
public static byte[] PBKDF2 (String prf, byte[] pass, byte[] salt, int iter, int len)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
byte[] result = new byte[len];
Mac mac = Mac.getInstance(prf);
mac.init (new SecretKeySpec (pass,prf));
byte[] saltcnt = Arrays.copyOf (salt, salt.length+4);
while( /*remaining*/len>0 ){
for( int o = saltcnt.length; --o>=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break;
byte[] u = saltcnt, x = new byte[mac.getMacLength()];
for( int i = 1; i <= iter; i++ ){
u = mac.doFinal (u);
for( int o = 0; o < x.length; o++ ) x[o] ^= u[o];
}
int len2 = Math.min (len, x.length);
System.arraycopy (x,0, result,result.length-len, len2);
len -= len2;
}
return result;
}
-->
18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
哎呀!
如果您不怕在使用 openssl 之前转换给定的加密私钥,您可以使用示例代码来轻松解析。
对于 运行 我的示例,我将您的加密私钥保存到文件 'so_privatekey_secret.pem'。
在 openssl 中使用这个命令行我用另一个密码转换了(已经)加密的密钥并生成了(新的)密钥文件 'so_privatekey_3des_secret.pem':
openssl pkcs8 -v1 PBE-SHA1-3DES -topk8 -in so_privatekey_secret.pem -out so_privatekey_3des_secret.pem
您的加密私钥将如下所示:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECAdT0CV4sPOPAgIIAASCBMi3H3WjyWEiAS/G
gw6gZTHiZFmidehU8XPh1HPYVb385u36WO16CwZVIBperQoXptCaE2LRwxmrN7T/
K2l59EHlV8i8FAfGKqvc7b1DW1fqx7jvw+/iT//3uNc3GHUNXfBrwHDi/MTghII4
gEFNUm7ZO7rAqeR7pJnZxm3giCvRHFbFcZRkz6LhCif3eYF9Lx3gSe2x6SPeNG/T
p0vrierxhHyCJk+S543J3cZ4vHUOjYzOsT1xz9hTbxAxeAEPkaCpD0P9jyth8M8k
2SRedRvR1Ia6r7sAfFz6XoHz5jH/lhd47EYpOc/A7rsgWkFLObwdFwGLRIvQsgeN
VXnwg5UoxRdjE3ZXQMFgHgzc4igmoVCjwgNZW6nAMwlcTctjsQyvRnAEnQbqLUw1
kddkjDzC1LFk+NiZ7L9oKVWAQuAQnLHMOxr8kJrTlSoS8zt5x5OEnR4I7JnUEHqs
2RythkJnoAsXaslBIkIXw4Kg6wisZjr/fL7ER0D0TBdZFPMvdMw8p4TxNxEF7v/K
G97HmCe39cVl3DkYayK2Bs4T6FxqbqUzUMOQnNHpbj82+ybEhm/5duhD5Kd/Inz7
8mTpDM08P7OMG27AiJDReQJ18XQ5y3DxQJu8UgfQEtjpVW3BQIVzYRM4JqEEv72h
YLELhtmBQom1SWadMW6jzRJRS+f+ivEApVyLcKlnPj2SY7niSmDjYChDx30fHoDt
z25UYBb8LmZ6P5rwNpMgGutBYqg721ioYt86mQsDDaLYOxjlhPfwbrQ9HqUrPCyQ
0UBeP9hNYkGbZQ+erZmyJ4hhlGGXz9v1LZ4xqNelKuUUREZaLWNVO7UwX2AgUebv
V7V3ne3ZNuLo5kDQqGz3hxwI4tcHgneNMpbxwdR7/B9QTXtirgZck9QIU6zxPhIf
agyCfpqE5pE3ln5n+FfbOik1pNcQxFr0nLy6vnlcTATLHftX0JycFc9OcJ9eRnI/
zk0ekYcw7QLxzNp/ews+Krwy+2SXaacTi+fXihCks10FZ/TEMYV0wC7OMW1yC35K
jUU12u7kSccvUk4xQftyA39XYCN6g7kxDqLjCfc6pMMWZZ9anSo7rmuQ6scoMhdr
904PcX3ne9rBJLv3a3W25TolNKZx6JofCViyIipcJY4FRyIRoIfwUTZ643KLSZ4o
nAPxtWHpbgA8ucrMKwQ0kbhT/qEvz3HxliL91N7qqMfZ3RfsgK2fUXQZ4zsU1oZ2
f7UT9+D0w/Pusond7SHPFo9G68GjsE48CQ1J/l5QreZ+bLxBohs2xM1WIz2TBJX9
ghdbo+0eatoxfq0o/H0vj66xMd8fvqePrzNw3kSqSpdQiZl95vqWM8Y7xEW0PxAU
78I0koI98v/3n9abSUNDhodPsk8I3abr7MoQsKe7VSu/x+mgMcfvNOiH6j6LuUDx
Eii7nfHhC/M69hWoh8Y+a2qPdbT2WMZSgXdBC+dCQeL4o6+flht6/24Eg+HAQMyY
3X5aH2ZM4BJAyJvdtj7btMDKq6nunyMywYhIOgKTZUg8wytxDsi2F4KyNKpXXgsj
0yR2L6EUYeQ1ZLG/NcM86sHueF1x38RznpyD4rujRGslWAKRXk4D2O2GpYS4rYRk
4r4pPXltZ472q7PGBfM=
-----END ENCRYPTED PRIVATE KEY-----
运行 我的示例程序(没有异常处理)给出了输出:
pkInfo.getAlgName: PBEWithSHA1AndDESede
decryptedPrivateKey: SunRsaSign RSA private CRT key, 2048 bits
params: null
modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
private exponent: 2470690692670644289832084636740463653655882622624760758103597888447170520657826825702415751838767348735186037205082706146786714644526202528897926495648786865000645626802269367582073915029633022103975204027927584273373553784482725392082470421529769049284506228997163157587212073758970565083984956787424047216319298607371397767542633302071090323391531162434678662485530085857362259661656308472034152915312425224731842425134593468185574918070451135641322780271191791839285884885643517240131520070881951542294552185519522325857178404160441369354693465035929601010771762928436963178293113507661955562266846499005032897533
代码:
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class ReadPkcs8PrivateKeysSo {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
String encrypted = new String(Files.readAllBytes(Paths.get("so_privatekey_3des_secret.pem")));
String password = "secret";
//Create object from encrypted private key
encrypted = encrypted.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
encrypted = encrypted.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(Base64.getMimeDecoder().decode(encrypted));
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); // password
System.out.println("pkInfo.getAlgName: " + pkInfo.getAlgName());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());
SecretKey secretKey = pbeKeyFactory.generateSecret(keySpec);
PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(secretKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey decryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);
System.out.println("decryptedPrivateKey: " + decryptedPrivateKey);
}
}
您编辑的代码出现错误“java.security.InvalidKeyException:密钥大小错误”,这是因为您使用以下行“读取”了 keyLength:
int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
对于给定的私钥,长度为 1247 位,这不是允许的 TripleDES 长度。您可以将其修复为(硬编码):
int keyLength = 24 * 8;
现在我们得到 192 位 = 24 字节的 keyLength,这对于 TripleDES-key 没问题,但我们收到一个新错误:
java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required
此行为的原因是以下行(已注释掉)可以通过另外 2 行得到修复:
// SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // ### old routine
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec); // ### new
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede"); // ### new
现在您的代码已成功运行并打印出私钥和证书:
original keyLength: 1247
fixed keyLength(bit): 192
privateKey:
SunRsaSign RSA private CRT key, 2048 bits
params: null
modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
private exponent: 2470690692670644289832084636740463653655882622624760758103597888447170520657826825702415751838767348735186037205082706146786714644526202528897926495648786865000645626802269367582073915029633022103975204027927584273373553784482725392082470421529769049284506228997163157587212073758970565083984956787424047216319298607371397767542633302071090323391531162434678662485530085857362259661656308472034152915312425224731842425134593468185574918070451135641322780271191791839285884885643517240131520070881951542294552185519522325857178404160441369354693465035929601010771762928436963178293113507661955562266846499005032897533
number of certificates: 1
certificate: 0 data:
[
[
Version: V3
Subject: CN=Hakan, OU=Amsterdam, O=Thunderberry, C=NL
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 2048 bits
params: null
modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
public exponent: 65537
Validity: [From: Mon Aug 31 10:07:39 CEST 2020,
To: Thu Aug 29 10:07:39 CEST 2030]
Issuer: CN=Hakan, OU=Amsterdam, O=Thunderberry, C=NL
SerialNumber: [ 5671c8dd]
Certificate Extensions: 2
[1]: ObjectId: 2.5.29.17 Criticality=true
SubjectAlternativeName [
DNSName: localhost
IPAddress: 127.0.0.1
]
[2]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 78 53 EC 04 E0 A1 55 45 22 C8 25 C4 3F F5 89 40 xS....UE".%.?..@
0010: 51 07 A8 03 Q...
]
]
]
Algorithm: [SHA256withRSA]
Signature:
0000: 85 55 87 FC 28 19 D1 93 66 4D F8 85 00 E9 D6 75 .U..(...fM.....u
0010: A6 55 69 AE EF 00 53 D3 D9 86 D7 BA F9 58 60 84 .Ui...S......X`.
0020: 65 AE ED 01 FF 39 F6 31 FC 26 9F 95 B6 A3 50 AA e....9.1.&....P.
0030: A0 1B A1 C9 F1 2B 92 41 78 69 13 35 BD DB 88 8A .....+.Axi.5....
0040: B5 23 95 C1 F8 F9 C1 57 AD 5D A0 B6 59 DF 79 64 .#.....W.]..Y.yd
0050: E0 FE C2 49 99 8C E4 25 31 9E 38 8A 91 94 1D 47 ...I...%1.8....G
0060: 28 2A A8 78 D0 AB CA 49 1C B2 29 04 09 0D 67 0A (*.x...I..)...g.
0070: AA F5 A8 B8 7D 0D 7B 14 81 65 56 E7 B6 16 AA F3 .........eV.....
0080: F5 3F CC 4E 3B 6F C9 F6 C8 4C 07 A9 4E 85 FB 69 .?.N;o...L..N..i
0090: 6B 0A EA 67 68 4D 1B C7 EA 30 A8 D5 56 E5 29 14 k..ghM...0..V.).
00A0: 13 51 F9 63 83 43 FA D1 46 DA 9A EA E5 36 57 C6 .Q.c.C..F....6W.
00B0: 8E 39 9C 75 40 95 24 4D D3 7B E9 83 82 B8 61 13 .9.u@.$M......a.
00C0: ED E7 5E BE 3A 5D 6D 15 DD 7B E1 D0 37 A9 9B 4F ..^.:]m.....7..O
00D0: D3 8B C5 26 47 3B 46 90 AC 3C 83 98 D8 69 69 51 ...&G;F..<...iiQ
00E0: EE 42 7F CA B5 83 56 09 9E FC 5C A2 BC C2 D5 67 .B....V...\....g
00F0: 7E 59 9F A8 4F DC F7 13 B5 9F F1 1C 56 F2 C2 76 .Y..O.......V..v
]
这是 运行 打开 Java 11 时的完整代码:
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import com.hierynomus.asn1.types.string.ASN1OctetString;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// https://mvnrepository.com/artifact/com.hierynomus/asn-one/0.4.0
// additionally you need https://mvnrepository.com/artifact/org.slf4j/slf4j-api/1.7.30
// and https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12/1.7.30
// https://mvnrepository.com/artifact/org.eclipse.equinox/org.apache.log4j/1.2.13.v200706111418
class MainOrg3 {
private static final String KEYSTORE_TYPE = "PKCS12";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String CERTIFICATE_TYPE = "X.509";
private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----", Pattern.DOTALL);
private static final String NEW_LINE = "\n";
private static final String EMPTY = "";
static char[] keyPassword = "secret".toCharArray();
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
String privateKeyContent = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
"MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA\n" +
"MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay\n" +
"MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz\n" +
"4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG\n" +
"AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja\n" +
"yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk\n" +
"NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf\n" +
"s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY\n" +
"19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc\n" +
"PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO\n" +
"arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa\n" +
"R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK\n" +
"eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH\n" +
"Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT\n" +
"a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk\n" +
"l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ\n" +
"oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP\n" +
"67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M\n" +
"TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi\n" +
"h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd\n" +
"PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc\n" +
"vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ\n" +
"8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL\n" +
"CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp\n" +
"VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r\n" +
"T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG\n" +
"7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4\n" +
"SRo=\n" +
"-----END ENCRYPTED PRIVATE KEY-----";
String certificateContent = "-----BEGIN CERTIFICATE-----\n" +
"MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO\n" +
"TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM\n" +
"BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL\n" +
"MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z\n" +
"dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" +
"AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g\n" +
"WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8\n" +
"ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj\n" +
"zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa\n" +
"FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn\n" +
"9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg\n" +
"oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN\n" +
"BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY\n" +
"YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf\n" +
"eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW\n" +
"qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2\n" +
"V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp\n" +
"aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==\n" +
"-----END CERTIFICATE-----";
PrivateKey privateKey = parsePrivateKey(privateKeyContent);
Certificate[] certificates = parseCertificate(certificateContent).values()
.toArray(new Certificate[]{});
KeyStore keyStore = createEmptyKeyStore();
keyStore.setKeyEntry("client", privateKey, null, certificates);
System.out.println("\nprivateKey:\n" + privateKey);
int certificatesLength = certificates.length;
System.out.println("\nnumber of certificates: " + certificatesLength);
for (int i = 0; i < certificatesLength; i++) {
System.out.println("\ncertificate: " + i + " data:\n" + certificates[i]);
}
}
private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
keyStore.load(null, null);
return keyStore;
}
private static PrivateKey parsePrivateKey(String identityContent) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
KeySpec keySpec = null;
Matcher privateKeyMatcher = PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = new PKCS8EncodedKeySpec(decodedPrivateKeyContent);
}
privateKeyMatcher = RSA_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = getKeySpecFromAsn1StructuredData(decodedPrivateKeyContent);
}
privateKeyMatcher = ENCRYPTED_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
System.out.println("original keyLength: " + keyLength);
keyLength = 24 * 8; // ### fixed
System.out.println("fixed keyLength(bit): " + keyLength);
PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");
// SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // ### old routine
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec); // ### new
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede"); // ### new
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
//PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
} catch (NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
Objects.requireNonNull(keySpec);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws IOException {
try(ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
if (asn1Sequence.getValue().size() < 9) {
throw new RuntimeException("Parsed key content doesn't have the minimum required sequence size of 9");
}
BigInteger modulus = extractIntValueFrom(asn1Sequence.get(1));
BigInteger publicExponent = extractIntValueFrom(asn1Sequence.get(2));
BigInteger privateExponent = extractIntValueFrom(asn1Sequence.get(3));
BigInteger primeP = extractIntValueFrom(asn1Sequence.get(4));
BigInteger primeQ = extractIntValueFrom(asn1Sequence.get(5));
BigInteger primeExponentP = extractIntValueFrom(asn1Sequence.get(6));
BigInteger primeExponentQ = extractIntValueFrom(asn1Sequence.get(7));
BigInteger crtCoefficient = extractIntValueFrom(asn1Sequence.get(8));
return new RSAPrivateCrtKeySpec(
modulus, publicExponent, privateExponent,
primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient
);
}
}
private static BigInteger extractIntValueFrom(ASN1Object<?> asn1Object) {
if (asn1Object instanceof ASN1Integer) {
return ((ASN1Integer) asn1Object).getValue();
} else {
throw new RuntimeException(String.format(
"Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]",
asn1Object.getClass().getName(), ASN1Integer.class.getName())
);
}
}
private static Map<String, Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
Map<String, Certificate> certificates = new HashMap<>();
Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);
while (certificateMatcher.find()) {
String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
certificates.put(UUID.randomUUID().toString(), certificate);
}
}
return certificates;
}
}
我花了几个星期的时间在 GitHub 上开发我的 pem-utils 项目,现在我的 pure-Java 代码没有外部库,似乎能够加载我抛出的任何东西在它。
我希望这对最初的发帖人(18 个月前!)以及其他追随我们脚步的人有所帮助。
最后我决定使用 BC 来解析 PEM 文件以获取证书和私钥。在我的用例中,我需要处理所有不同类型的私钥,因此复杂性也增加了,但我想让我自己和其他人尽可能简单,所以我通过创建一个库来简化它,见下面是一个示例用法:
import javax.net.ssl.X509ExtendedKeyManager;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
public class App {
public static void main(String[] args) {
char[] privateKeyPassword = "secret".toCharArray();
String privateKeyContent = """
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA
MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay
MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz
4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG
AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja
yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk
NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf
s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY
19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc
PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO
arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa
R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK
eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH
Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT
a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk
l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ
oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP
67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M
TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi
h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd
PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc
vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ
8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL
CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp
VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r
T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG
7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4
SRo=
-----END ENCRYPTED PRIVATE KEY-----
""";
String certificateChainContent = """
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM
BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL
MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z
dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g
WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8
ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj
zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa
FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn
9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg
oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY
YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf
eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW
qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2
V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp
aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==
-----END CERTIFICATE-----
""";
PrivateKey privateKey = PemUtils.parsePrivateKey(privateKeyContent, privateKeyPassword);
List<X509Certificate> certificates = PemUtils.parseCertificate(certificateChainContent);
X509ExtendedKeyManager keyManager = PemUtils.parseIdentityMaterial(certificateChainContent, privateKeyContent, privateKeyPassword);
}
}
图书馆在这里可用:GitHub - SSLContext Kickstart
我无法弄清楚如何正确读取 pem 文件的私钥。我浏览了关于 Whosebug 的不同主题,但找不到解决方案。我想要实现的是从类路径中读取 pkcs#8 编码文件中的加密私钥,并将其作为内存密钥库中的密钥条目加载。下面是我尝试解析的私钥示例,密码是 secret
。它纯粹是为了在这里共享而创建的,因此它不是生产机器上使用的私钥。
我使用以下命令从 p12 文件创建了它:openssl pkcs12 -in key-pair.p12 -out key-pair.pem
有效示例(一次性)密钥对
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA
MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay
MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz
4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG
AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja
yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk
NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf
s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY
19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc
PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO
arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa
R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK
eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH
Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT
a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk
l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ
oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP
67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M
TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi
h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd
PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc
vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ
8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL
CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp
VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r
T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG
7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4
SRo=
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM
BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL
MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z
dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g
WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8
ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj
zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa
FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn
9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg
oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY
YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf
eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW
qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2
V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp
aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==
-----END CERTIFICATE-----
我知道 BouncyCastle 能够解析它,但我想避免使用额外的库。所以我想知道是否可以只使用普通 jdk 或其他一些轻量级库。
我已经能够使用以下 header/footer:
解析私钥-----BEGIN PRIVATE KEY-----
*
-----END PRIVATE KEY-----
和
-----BEGIN RSA PRIVATE KEY-----
*
-----END RSA PRIVATE KEY-----
我已经使用以下代码片段来完成此操作:
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class App {
private static final String KEYSTORE_TYPE = "PKCS12";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String CERTIFICATE_TYPE = "X.509";
private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----", Pattern.DOTALL);
private static final String NEW_LINE = "\n";
private static final String EMPTY = "";
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
String privateKeyContent = "";
String certificateContent = "";
PrivateKey privateKey = parsePrivateKey(privateKeyContent);
Certificate[] certificates = parseCertificate(certificateContent).values()
.toArray(new Certificate[]{});
KeyStore keyStore = createEmptyKeyStore();
keyStore.setKeyEntry("client", privateKey, null, certificates);
}
private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
keyStore.load(null, null);
return keyStore;
}
private static PrivateKey parsePrivateKey(String identityContent) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
KeySpec keySpec = null;
Matcher privateKeyMatcher = PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = new PKCS8EncodedKeySpec(decodedPrivateKeyContent);
}
privateKeyMatcher = RSA_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = getKeySpecFromAsn1StructuredData(decodedPrivateKeyContent);
}
privateKeyMatcher = ENCRYPTED_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = null; //TODO
}
Objects.requireNonNull(keySpec);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws IOException {
try(ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
if (asn1Sequence.getValue().size() < 9) {
throw new RuntimeException("Parsed key content doesn't have the minimum required sequence size of 9");
}
BigInteger modulus = extractIntValueFrom(asn1Sequence.get(1));
BigInteger publicExponent = extractIntValueFrom(asn1Sequence.get(2));
BigInteger privateExponent = extractIntValueFrom(asn1Sequence.get(3));
BigInteger primeP = extractIntValueFrom(asn1Sequence.get(4));
BigInteger primeQ = extractIntValueFrom(asn1Sequence.get(5));
BigInteger primeExponentP = extractIntValueFrom(asn1Sequence.get(6));
BigInteger primeExponentQ = extractIntValueFrom(asn1Sequence.get(7));
BigInteger crtCoefficient = extractIntValueFrom(asn1Sequence.get(8));
return new RSAPrivateCrtKeySpec(
modulus, publicExponent, privateExponent,
primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient
);
}
}
private static BigInteger extractIntValueFrom(ASN1Object<?> asn1Object) {
if (asn1Object instanceof ASN1Integer) {
return ((ASN1Integer) asn1Object).getValue();
} else {
throw new RuntimeException(String.format(
"Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]",
asn1Object.getClass().getName(), ASN1Integer.class.getName())
);
}
}
private static Map<String, Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
Map<String, Certificate> certificates = new HashMap<>();
Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);
while (certificateMatcher.find()) {
String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
certificates.put(UUID.randomUUID().toString(), certificate);
}
}
return certificates;
}
}
我还需要处理 asn.1 编码的私钥。我可以使用 BouncyCastle,但我还是想避免使用它,因为我对替代方案很好奇。我从这个主题中得到启发:
我已经分享了我处理其他案例的完整片段,所以当有人想帮助我时,尝试起来会更容易。
来自以下文章:ASN.1 key structures in DER and PEM 我了解到具有以下 header/footer 的私钥:-----BEGIN ENCRYPTED PRIVATE KEY----- * -----END ENCRYPTED PRIVATE KEY-----
也是具有以下结构的 asn.1 编码私钥:
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm EncryptionAlgorithmIdentifier,
encryptedData EncryptedData
}
EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
EncryptedData ::= OCTET STRING
我也尝试了此处提供的答案 How to read a password encrypted key with java?,但这些也没有用。但是我不太确定如何将它正确解析为 java 对象并将其加载为 KeySpec。欢迎任何帮助!
======>
根据 dave_thompson_085
Matcher privateKeyMatcher = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL)
.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
}
}
结果变成如下异常:java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required
语句cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
======>
根据 Michael Fehr
Matcher privateKeyMatcher = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL)
.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
int keyLength = 24 * 8;
PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec);
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
}
}
此 PEM 格式的更官方参考是 rfc7468 section 11 which specifies that the ASN.1 content (after de-PEM) is PKCS8's encrypted form defined in rfc5208 section 6 and appendix A,将其稍微修改为:
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
encryptedData EncryptedData
}
EncryptedData ::= OCTET STRING
(redacted)
KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
... -- For local profiles
}
未能解释 PKCS8(PKCS12 除外)key-encryption 常用 算法是 PKCS5 v2 [=] 中定义的 password-based 加密24=](细微差别在这里无关紧要)。特别是对于 Q 中的密钥文件,AlgorithmIdentifier 解析(使用 OpenSSL)为:
4:d=1 hl=2 l= 64 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 51 cons: SEQUENCE
19:d=3 hl=2 l= 27 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 14 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:F7F1520689D469C6
44:d=5 hl=2 l= 2 prim: INTEGER :0800
48:d=3 hl=2 l= 20 cons: SEQUENCE
50:d=4 hl=2 l= 8 prim: OBJECT :des-ede3-cbc
60:d=4 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:9D1A44BCC1884EA7
PBES2 在 section 6.2 中被定义为由 PBKDF2 密钥派生和几乎任何对称 encryption/decryption 组成。
您应该能够看到 top-level 参数匹配 appendix A.4 and the parameters within the keyDerivationFunc part match PBKDF2-Params
in appendix A.2 (salt and iterationCount present, keyLength and prf omitted), while the parameter for the encryptionScheme part is simply an OCTET STRING containing the Initialization Vector (IV), see appendix B.2.2.
PBES2-Params
因此,要解密它,您需要使用这些参数实现 section 5.2 中指定的 PBKDF2 密钥派生,Java 加密支持 然后 triple-DES 又名 TDEA 解密,使用 triple-length 密钥又名密钥选项 1 在 CBC 模式下使用 PKCS5/7 填充,Java 加密支持作为SecretKeyFactory.getInstance("PBKDF2withHmacSHA1")
和 PBEKeySpec
、Cipher.getInstance("DESede/CBC/PKCS5Padding")
,使用第一步导出的密钥和输入数据的 IV 作为 IVParameterSpec
。结果将是一个 PKCS8-unencrypted 密钥结构,您可以 运行 通过合适的 KeyFactory
作为 PKCS8EncodedKeySpec
,就像您已经为未加密的输入格式。
虽然考虑到 JRE 通常在 100MB 以上,而 Bouncy 增加了大约 5MB 并且 shedloads 更易于使用,但我不能说我同意你的评估。
更新:我没有意识到,但是 JCE 的 PBKDF2 SKF 实际上并没有进行密钥推导,它只是打包数据元素以供合适的 Cipher
算法使用——而且它没有' 为我们这里需要的情况实现 Cipher
算法。所以你需要 手动编码 PBKDF2 幸运的是(?)我已经为
static void SO63832456ManualPKCS8EPBES2 (String[] args) throws Exception {
String file = args[0], pass = args[1];
// I use Bouncy to read because it's much easier for me,
// but you should get the same results with any ASN.1 lib
PemObject obj; try(Reader rdr = new FileReader(file);
PEMParser p = new PEMParser(rdr) ){ obj = p.readPemObject(); }
if( ! obj.getType().equals("ENCRYPTED PRIVATE KEY") ) throw new Exception ("wrong type");
org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eki = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(obj.getContent());
AlgorithmIdentifier algid = eki.getEncryptionAlgorithm();
if( ! algid.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBES2) ) throw new Exception("not PBES2");
PBES2Parameters top = PBES2Parameters.getInstance(algid.getParameters());
KeyDerivationFunc kdf = top.getKeyDerivationFunc();
if( ! kdf.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBKDF2) ) throw new Exception("not PBKDF2");
PBKDF2Params kdp = PBKDF2Params.getInstance(kdf.getParameters());
EncryptionScheme esa = top.getEncryptionScheme();
if( ! esa.getAlgorithm().equals(PKCSObjectIdentifiers.des_EDE3_CBC) ) throw new Exception("not DES3");
ASN1OctetString esp = ASN1OctetString.getInstance(esa.getParameters());
// these are the (only) items actually used
byte[] salt = kdp.getSalt(), iv = esp.getOctets(), edata = eki.getEncryptedData();
int iter = kdp.getIterationCount().intValueExact();
//SecretKeyFactory fact = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
//SecretKey skey = fact.generateSecret(new PBEKeySpec(pass.toCharArray(), salt, iter, 24));
byte[] skey = PBKDF2 ("HmacSHA1", pass.getBytes("UTF-8"), salt, iter, 24);
Cipher ciph = Cipher.getInstance("DESEDE/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skey,"DESEDE"), new IvParameterSpec(iv));
byte[] plain = ciph.doFinal(edata);
KeyFactory fac2 = KeyFactory.getInstance("RSA");
PrivateKey pkey = fac2.generatePrivate(new PKCS8EncodedKeySpec(plain));
System.out.println ( ((java.security.interfaces.RSAPrivateKey)pkey).getModulus() );
}
public static byte[] PBKDF2 (String prf, byte[] pass, byte[] salt, int iter, int len)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
byte[] result = new byte[len];
Mac mac = Mac.getInstance(prf);
mac.init (new SecretKeySpec (pass,prf));
byte[] saltcnt = Arrays.copyOf (salt, salt.length+4);
while( /*remaining*/len>0 ){
for( int o = saltcnt.length; --o>=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break;
byte[] u = saltcnt, x = new byte[mac.getMacLength()];
for( int i = 1; i <= iter; i++ ){
u = mac.doFinal (u);
for( int o = 0; o < x.length; o++ ) x[o] ^= u[o];
}
int len2 = Math.min (len, x.length);
System.arraycopy (x,0, result,result.length-len, len2);
len -= len2;
}
return result;
}
-->
18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
哎呀!
如果您不怕在使用 openssl 之前转换给定的加密私钥,您可以使用示例代码来轻松解析。
对于 运行 我的示例,我将您的加密私钥保存到文件 'so_privatekey_secret.pem'。
在 openssl 中使用这个命令行我用另一个密码转换了(已经)加密的密钥并生成了(新的)密钥文件 'so_privatekey_3des_secret.pem':
openssl pkcs8 -v1 PBE-SHA1-3DES -topk8 -in so_privatekey_secret.pem -out so_privatekey_3des_secret.pem
您的加密私钥将如下所示:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECAdT0CV4sPOPAgIIAASCBMi3H3WjyWEiAS/G
gw6gZTHiZFmidehU8XPh1HPYVb385u36WO16CwZVIBperQoXptCaE2LRwxmrN7T/
K2l59EHlV8i8FAfGKqvc7b1DW1fqx7jvw+/iT//3uNc3GHUNXfBrwHDi/MTghII4
gEFNUm7ZO7rAqeR7pJnZxm3giCvRHFbFcZRkz6LhCif3eYF9Lx3gSe2x6SPeNG/T
p0vrierxhHyCJk+S543J3cZ4vHUOjYzOsT1xz9hTbxAxeAEPkaCpD0P9jyth8M8k
2SRedRvR1Ia6r7sAfFz6XoHz5jH/lhd47EYpOc/A7rsgWkFLObwdFwGLRIvQsgeN
VXnwg5UoxRdjE3ZXQMFgHgzc4igmoVCjwgNZW6nAMwlcTctjsQyvRnAEnQbqLUw1
kddkjDzC1LFk+NiZ7L9oKVWAQuAQnLHMOxr8kJrTlSoS8zt5x5OEnR4I7JnUEHqs
2RythkJnoAsXaslBIkIXw4Kg6wisZjr/fL7ER0D0TBdZFPMvdMw8p4TxNxEF7v/K
G97HmCe39cVl3DkYayK2Bs4T6FxqbqUzUMOQnNHpbj82+ybEhm/5duhD5Kd/Inz7
8mTpDM08P7OMG27AiJDReQJ18XQ5y3DxQJu8UgfQEtjpVW3BQIVzYRM4JqEEv72h
YLELhtmBQom1SWadMW6jzRJRS+f+ivEApVyLcKlnPj2SY7niSmDjYChDx30fHoDt
z25UYBb8LmZ6P5rwNpMgGutBYqg721ioYt86mQsDDaLYOxjlhPfwbrQ9HqUrPCyQ
0UBeP9hNYkGbZQ+erZmyJ4hhlGGXz9v1LZ4xqNelKuUUREZaLWNVO7UwX2AgUebv
V7V3ne3ZNuLo5kDQqGz3hxwI4tcHgneNMpbxwdR7/B9QTXtirgZck9QIU6zxPhIf
agyCfpqE5pE3ln5n+FfbOik1pNcQxFr0nLy6vnlcTATLHftX0JycFc9OcJ9eRnI/
zk0ekYcw7QLxzNp/ews+Krwy+2SXaacTi+fXihCks10FZ/TEMYV0wC7OMW1yC35K
jUU12u7kSccvUk4xQftyA39XYCN6g7kxDqLjCfc6pMMWZZ9anSo7rmuQ6scoMhdr
904PcX3ne9rBJLv3a3W25TolNKZx6JofCViyIipcJY4FRyIRoIfwUTZ643KLSZ4o
nAPxtWHpbgA8ucrMKwQ0kbhT/qEvz3HxliL91N7qqMfZ3RfsgK2fUXQZ4zsU1oZ2
f7UT9+D0w/Pusond7SHPFo9G68GjsE48CQ1J/l5QreZ+bLxBohs2xM1WIz2TBJX9
ghdbo+0eatoxfq0o/H0vj66xMd8fvqePrzNw3kSqSpdQiZl95vqWM8Y7xEW0PxAU
78I0koI98v/3n9abSUNDhodPsk8I3abr7MoQsKe7VSu/x+mgMcfvNOiH6j6LuUDx
Eii7nfHhC/M69hWoh8Y+a2qPdbT2WMZSgXdBC+dCQeL4o6+flht6/24Eg+HAQMyY
3X5aH2ZM4BJAyJvdtj7btMDKq6nunyMywYhIOgKTZUg8wytxDsi2F4KyNKpXXgsj
0yR2L6EUYeQ1ZLG/NcM86sHueF1x38RznpyD4rujRGslWAKRXk4D2O2GpYS4rYRk
4r4pPXltZ472q7PGBfM=
-----END ENCRYPTED PRIVATE KEY-----
运行 我的示例程序(没有异常处理)给出了输出:
pkInfo.getAlgName: PBEWithSHA1AndDESede
decryptedPrivateKey: SunRsaSign RSA private CRT key, 2048 bits
params: null
modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
private exponent: 2470690692670644289832084636740463653655882622624760758103597888447170520657826825702415751838767348735186037205082706146786714644526202528897926495648786865000645626802269367582073915029633022103975204027927584273373553784482725392082470421529769049284506228997163157587212073758970565083984956787424047216319298607371397767542633302071090323391531162434678662485530085857362259661656308472034152915312425224731842425134593468185574918070451135641322780271191791839285884885643517240131520070881951542294552185519522325857178404160441369354693465035929601010771762928436963178293113507661955562266846499005032897533
代码:
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class ReadPkcs8PrivateKeysSo {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
String encrypted = new String(Files.readAllBytes(Paths.get("so_privatekey_3des_secret.pem")));
String password = "secret";
//Create object from encrypted private key
encrypted = encrypted.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
encrypted = encrypted.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(Base64.getMimeDecoder().decode(encrypted));
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); // password
System.out.println("pkInfo.getAlgName: " + pkInfo.getAlgName());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());
SecretKey secretKey = pbeKeyFactory.generateSecret(keySpec);
PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(secretKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey decryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);
System.out.println("decryptedPrivateKey: " + decryptedPrivateKey);
}
}
您编辑的代码出现错误“java.security.InvalidKeyException:密钥大小错误”,这是因为您使用以下行“读取”了 keyLength:
int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
对于给定的私钥,长度为 1247 位,这不是允许的 TripleDES 长度。您可以将其修复为(硬编码):
int keyLength = 24 * 8;
现在我们得到 192 位 = 24 字节的 keyLength,这对于 TripleDES-key 没问题,但我们收到一个新错误:
java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required
此行为的原因是以下行(已注释掉)可以通过另外 2 行得到修复:
// SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // ### old routine
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec); // ### new
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede"); // ### new
现在您的代码已成功运行并打印出私钥和证书:
original keyLength: 1247
fixed keyLength(bit): 192
privateKey:
SunRsaSign RSA private CRT key, 2048 bits
params: null
modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
private exponent: 2470690692670644289832084636740463653655882622624760758103597888447170520657826825702415751838767348735186037205082706146786714644526202528897926495648786865000645626802269367582073915029633022103975204027927584273373553784482725392082470421529769049284506228997163157587212073758970565083984956787424047216319298607371397767542633302071090323391531162434678662485530085857362259661656308472034152915312425224731842425134593468185574918070451135641322780271191791839285884885643517240131520070881951542294552185519522325857178404160441369354693465035929601010771762928436963178293113507661955562266846499005032897533
number of certificates: 1
certificate: 0 data:
[
[
Version: V3
Subject: CN=Hakan, OU=Amsterdam, O=Thunderberry, C=NL
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 2048 bits
params: null
modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
public exponent: 65537
Validity: [From: Mon Aug 31 10:07:39 CEST 2020,
To: Thu Aug 29 10:07:39 CEST 2030]
Issuer: CN=Hakan, OU=Amsterdam, O=Thunderberry, C=NL
SerialNumber: [ 5671c8dd]
Certificate Extensions: 2
[1]: ObjectId: 2.5.29.17 Criticality=true
SubjectAlternativeName [
DNSName: localhost
IPAddress: 127.0.0.1
]
[2]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 78 53 EC 04 E0 A1 55 45 22 C8 25 C4 3F F5 89 40 xS....UE".%.?..@
0010: 51 07 A8 03 Q...
]
]
]
Algorithm: [SHA256withRSA]
Signature:
0000: 85 55 87 FC 28 19 D1 93 66 4D F8 85 00 E9 D6 75 .U..(...fM.....u
0010: A6 55 69 AE EF 00 53 D3 D9 86 D7 BA F9 58 60 84 .Ui...S......X`.
0020: 65 AE ED 01 FF 39 F6 31 FC 26 9F 95 B6 A3 50 AA e....9.1.&....P.
0030: A0 1B A1 C9 F1 2B 92 41 78 69 13 35 BD DB 88 8A .....+.Axi.5....
0040: B5 23 95 C1 F8 F9 C1 57 AD 5D A0 B6 59 DF 79 64 .#.....W.]..Y.yd
0050: E0 FE C2 49 99 8C E4 25 31 9E 38 8A 91 94 1D 47 ...I...%1.8....G
0060: 28 2A A8 78 D0 AB CA 49 1C B2 29 04 09 0D 67 0A (*.x...I..)...g.
0070: AA F5 A8 B8 7D 0D 7B 14 81 65 56 E7 B6 16 AA F3 .........eV.....
0080: F5 3F CC 4E 3B 6F C9 F6 C8 4C 07 A9 4E 85 FB 69 .?.N;o...L..N..i
0090: 6B 0A EA 67 68 4D 1B C7 EA 30 A8 D5 56 E5 29 14 k..ghM...0..V.).
00A0: 13 51 F9 63 83 43 FA D1 46 DA 9A EA E5 36 57 C6 .Q.c.C..F....6W.
00B0: 8E 39 9C 75 40 95 24 4D D3 7B E9 83 82 B8 61 13 .9.u@.$M......a.
00C0: ED E7 5E BE 3A 5D 6D 15 DD 7B E1 D0 37 A9 9B 4F ..^.:]m.....7..O
00D0: D3 8B C5 26 47 3B 46 90 AC 3C 83 98 D8 69 69 51 ...&G;F..<...iiQ
00E0: EE 42 7F CA B5 83 56 09 9E FC 5C A2 BC C2 D5 67 .B....V...\....g
00F0: 7E 59 9F A8 4F DC F7 13 B5 9F F1 1C 56 F2 C2 76 .Y..O.......V..v
]
这是 运行 打开 Java 11 时的完整代码:
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import com.hierynomus.asn1.types.string.ASN1OctetString;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// https://mvnrepository.com/artifact/com.hierynomus/asn-one/0.4.0
// additionally you need https://mvnrepository.com/artifact/org.slf4j/slf4j-api/1.7.30
// and https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12/1.7.30
// https://mvnrepository.com/artifact/org.eclipse.equinox/org.apache.log4j/1.2.13.v200706111418
class MainOrg3 {
private static final String KEYSTORE_TYPE = "PKCS12";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String CERTIFICATE_TYPE = "X.509";
private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL);
private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----", Pattern.DOTALL);
private static final String NEW_LINE = "\n";
private static final String EMPTY = "";
static char[] keyPassword = "secret".toCharArray();
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
String privateKeyContent = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
"MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA\n" +
"MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay\n" +
"MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz\n" +
"4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG\n" +
"AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja\n" +
"yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk\n" +
"NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf\n" +
"s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY\n" +
"19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc\n" +
"PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO\n" +
"arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa\n" +
"R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK\n" +
"eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH\n" +
"Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT\n" +
"a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk\n" +
"l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ\n" +
"oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP\n" +
"67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M\n" +
"TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi\n" +
"h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd\n" +
"PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc\n" +
"vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ\n" +
"8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL\n" +
"CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp\n" +
"VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r\n" +
"T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG\n" +
"7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4\n" +
"SRo=\n" +
"-----END ENCRYPTED PRIVATE KEY-----";
String certificateContent = "-----BEGIN CERTIFICATE-----\n" +
"MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO\n" +
"TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM\n" +
"BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL\n" +
"MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z\n" +
"dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" +
"AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g\n" +
"WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8\n" +
"ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj\n" +
"zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa\n" +
"FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn\n" +
"9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg\n" +
"oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN\n" +
"BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY\n" +
"YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf\n" +
"eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW\n" +
"qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2\n" +
"V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp\n" +
"aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==\n" +
"-----END CERTIFICATE-----";
PrivateKey privateKey = parsePrivateKey(privateKeyContent);
Certificate[] certificates = parseCertificate(certificateContent).values()
.toArray(new Certificate[]{});
KeyStore keyStore = createEmptyKeyStore();
keyStore.setKeyEntry("client", privateKey, null, certificates);
System.out.println("\nprivateKey:\n" + privateKey);
int certificatesLength = certificates.length;
System.out.println("\nnumber of certificates: " + certificatesLength);
for (int i = 0; i < certificatesLength; i++) {
System.out.println("\ncertificate: " + i + " data:\n" + certificates[i]);
}
}
private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
keyStore.load(null, null);
return keyStore;
}
private static PrivateKey parsePrivateKey(String identityContent) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
KeySpec keySpec = null;
Matcher privateKeyMatcher = PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = new PKCS8EncodedKeySpec(decodedPrivateKeyContent);
}
privateKeyMatcher = RSA_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
keySpec = getKeySpecFromAsn1StructuredData(decodedPrivateKeyContent);
}
privateKeyMatcher = ENCRYPTED_PRIVATE_KEY_PATTERN.matcher(identityContent);
if (privateKeyMatcher.find()) {
String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
System.out.println("original keyLength: " + keyLength);
keyLength = 24 * 8; // ### fixed
System.out.println("fixed keyLength(bit): " + keyLength);
PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");
// SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // ### old routine
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec); // ### new
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede"); // ### new
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
//PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
} catch (NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
Objects.requireNonNull(keySpec);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws IOException {
try(ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
ASN1Sequence asn1Sequence = stream.readObject();
if (asn1Sequence.getValue().size() < 9) {
throw new RuntimeException("Parsed key content doesn't have the minimum required sequence size of 9");
}
BigInteger modulus = extractIntValueFrom(asn1Sequence.get(1));
BigInteger publicExponent = extractIntValueFrom(asn1Sequence.get(2));
BigInteger privateExponent = extractIntValueFrom(asn1Sequence.get(3));
BigInteger primeP = extractIntValueFrom(asn1Sequence.get(4));
BigInteger primeQ = extractIntValueFrom(asn1Sequence.get(5));
BigInteger primeExponentP = extractIntValueFrom(asn1Sequence.get(6));
BigInteger primeExponentQ = extractIntValueFrom(asn1Sequence.get(7));
BigInteger crtCoefficient = extractIntValueFrom(asn1Sequence.get(8));
return new RSAPrivateCrtKeySpec(
modulus, publicExponent, privateExponent,
primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient
);
}
}
private static BigInteger extractIntValueFrom(ASN1Object<?> asn1Object) {
if (asn1Object instanceof ASN1Integer) {
return ((ASN1Integer) asn1Object).getValue();
} else {
throw new RuntimeException(String.format(
"Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]",
asn1Object.getClass().getName(), ASN1Integer.class.getName())
);
}
}
private static Map<String, Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
Map<String, Certificate> certificates = new HashMap<>();
Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);
while (certificateMatcher.find()) {
String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
certificates.put(UUID.randomUUID().toString(), certificate);
}
}
return certificates;
}
}
我花了几个星期的时间在 GitHub 上开发我的 pem-utils 项目,现在我的 pure-Java 代码没有外部库,似乎能够加载我抛出的任何东西在它。
我希望这对最初的发帖人(18 个月前!)以及其他追随我们脚步的人有所帮助。
最后我决定使用 BC 来解析 PEM 文件以获取证书和私钥。在我的用例中,我需要处理所有不同类型的私钥,因此复杂性也增加了,但我想让我自己和其他人尽可能简单,所以我通过创建一个库来简化它,见下面是一个示例用法:
import javax.net.ssl.X509ExtendedKeyManager;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
public class App {
public static void main(String[] args) {
char[] privateKeyPassword = "secret".toCharArray();
String privateKeyContent = """
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA
MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay
MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz
4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG
AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja
yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk
NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf
s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY
19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc
PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO
arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa
R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK
eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH
Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT
a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk
l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ
oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP
67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M
TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi
h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd
PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc
vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ
8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL
CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp
VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r
T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG
7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4
SRo=
-----END ENCRYPTED PRIVATE KEY-----
""";
String certificateChainContent = """
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM
BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL
MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z
dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g
WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8
ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj
zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa
FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn
9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg
oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY
YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf
eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW
qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2
V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp
aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==
-----END CERTIFICATE-----
""";
PrivateKey privateKey = PemUtils.parsePrivateKey(privateKeyContent, privateKeyPassword);
List<X509Certificate> certificates = PemUtils.parseCertificate(certificateChainContent);
X509ExtendedKeyManager keyManager = PemUtils.parseIdentityMaterial(certificateChainContent, privateKeyContent, privateKeyPassword);
}
}
图书馆在这里可用:GitHub - SSLContext Kickstart