求助:OpenSSL生成的带密码RSA私钥在Java中签名失败
问题描述
我用OpenSSL生成了带密码保护的RSA私钥,Node.js中可以正常用该密钥签名,代码如下:
import * as crypto from 'node:crypto'; ... const sign = crypto.createSign('RSA-SHA256'); sign.update(data); return sign.sign({key: signKey, passphrase: signPassphrase}, "base64");
但在Java中实现相同功能时,无法正确读取该私钥。尝试的Java代码如下:
private PrivateKey createKey() throws NoSuchAlgorithmException, InvalidKeySpecException { var pkcs8KeySpec = passPhrase==null ? new PKCS8EncodedKeySpec(privateKey) : decryptEncodedKeySpec(); return KeyFactory.getInstance("RSA").generatePrivate(pkcs8KeySpec); } private PKCS8EncodedKeySpec decryptEncodedKeySpec() throws Exception{ EncryptedPrivateKeyInfo encryptPKInfo = new EncryptedPrivateKeyInfo(privateKey); Cipher cipher = Cipher.getInstance(encryptPKInfo.getAlgName()); PBEKeySpec pbeKeySpec = new PBEKeySpec(passPhrase); SecretKeyFactory secFac = SecretKeyFactory.getInstance(encryptPKInfo.getAlgName()); Key pbeKey = secFac.generateSecret(pbeKeySpec); AlgorithmParameters algParams = encryptPKInfo.getAlgParameters(); cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams); return encryptPKInfo.getKeySpec(cipher); }
执行时抛出异常:
Exception in thread "main" java.io.IOException: PBE parameter parsing error: expecting the object identifier for AES cipher at java.base/com.sun.crypto.provider.PBES2Parameters.parseES(PBES2Parameters.java:334) at java.base/com.sun.crypto.provider.PBES2Parameters.engineInit(PBES2Parameters.java:238) at java.base/java.security.AlgorithmParameters.init(AlgorithmParameters.java:311) at java.base/sun.security.x509.AlgorithmId.decodeParams(AlgorithmId.java:147) at java.base/sun.security.x509.AlgorithmId.<init>(AlgorithmId.java:129) at java.base/sun.security.x509.AlgorithmId.parse(AlgorithmId.java:435) at java.base/javax.crypto.EncryptedPrivateKeyInfo.<init>(EncryptedPrivateKeyInfo.java:101) at SignatureHelper.decryptEncodedKeySpec(SignatureHelper.java:207)
私钥的ASN.1信息(已简化):
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2 21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2 50:d=6 hl=2 l= 8 prim: OBJECT :hmacWithSHA256 64:d=4 hl=2 l= 8 prim: OBJECT :des-ede3-cbc
可以确定问题和最后一行的des-ede3-cbc算法相关。如果用openssl rsa -in key.pem移除密码,Java代码能正常读取密钥并签名。
示例加密私钥(密码:test),通过OpenSSL 3.0.2执行openssl genpkey -algorithm rsa -out test.key -des-ede3-cbc生成:
-----BEGIN ENCRYPTED PRIVATE KEY----- MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQItM++dY5H3H8CAggA MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECOQtQL9mBGDZBIIEyF16pXchc4dk szyX+20jGtpnYsOd0ANt0YrJ96NqmfOtfD/2J1zc2UcKBQSiPyHwAGMQoYZNb47c 1U9KSz3oDKEqkYc4pCeC/f3J5KqEvoYDLE15tAyBCAOa+ElSXvqUturYAsi5EQ3Q 5ADs5BZCa/xGF0nBHzA7w6zhKZi2Q00Za/CjFdfEmQLVLVOxPhk327+YO2K3iNQn WjBivhsNLqyLyOPePiXmTeOHs+rbhD+VHMTb8D0Gggo6Hqt8r3Mfuazh2Xhl4kgY OkUiLWEyxaD7InSNVXukfTmp0aXcTwL+8UBk38EMmfl4w9IVVaIcmDg3ASZVOvYw E1vObMxWySESEW1t4pZFrOw12nh8WquXMfAorSolXBDSIOx3GPIMQtzRmLO33j7G cj01MIgmIbtEF2ZIDuj0fyt8yYKGgDLp6c2c2piNI7646rWTCGIjJsjV9AjO5NHr ywpp9Jyu78kf3TWxptQCNypEyTOV0RD4dyIcVn1asbc7yPw8V13Cb2slPOyBceC+ HN1EMjALawbql1NgLWkLs5sKM4wcW0FxmwVh9Vx3FEKcK3UMhmr5dvjKPqMJWJIn 3BXqvDo+//FUy0hX3blxEsm+It20iRW6ehB2aqeQ2kBNEX2QwkjwwtKesjvfMaym U/tpKP13isTKwWYY1UtxwS4WVgT/To2ynuoTW3C0+LEtUHPBJ7EuPts2xOtuQqAT 68JZW9BuXAEtLVeerZTZhF4LVVeJ8Ugy20vjZ1pXNxiiCOSAnLCvjrvteR9FYLWe OTUTiTyLzZvDHMj0+nzjlAUoTi0FVJBJJU12QKB+0qtmpwJtxlVkvwAf4OXMhKOW ZwMGZ2MlaP5ZhtxV/1JckWUfTveW06F/fWGwcpIzSJbHAzrUW4+dljMqxIaP95oi zitthhD3dfXhPLFMwCb58Oy/032xakDiZQDNj9nFM8QAngGOE2Bn9AFzcFYGT6Io V4HZbPNGlhjUHSnXukKs4VU0ZIu7r5Pq7VDv2NIJJhkB4wSiCRhnjRyku+sKdnfL vtNKxPKfbGavBQMZVVg91y/FRvUi+HWvVi98RY5lLcII+nNoDfA9eS5SG7WLEjVl 7NvZLz/XGvVQGhcqSw4SWH+1eA/Qjxs0UK1Bk7mMuLcxIYRgSfZw85JUhoYNY1KJ y059qNMDt2iyVaZ/Um4pG0ZNrG9NV3QjXVvxohtl3sXdBgGzgAm2u/8HY0R4oikQ HkNkg9nlFj+yfuFEhrtAZmqwSbq34646744VDp6BjzsgX9p0e7TM26MZ4pbelhtU OHycV0CleFqGUuE0uyE8rhLzwYSyTx2toYbXxCx9Bg1RB5McThY+qnC/83j+N+0c rclAxd4FhaHuE8Qrtnz2kBm70BeSZ5aJT9h7bXQKoXaFvKZmAnom6bFuO2cPFbar N3pkeUbqW4mQQXmNUkkYt6Gq44m06b1yJQulbcLUom6KNkNmDo3iOZYmoMoT+PC+ 5+ojl1iMhiuktr0NskYcL8SnfLvygQ0ICy8xKybk2JvdZ2M+ocPKsbkM7r6Er1iW apeFw+yez4mV1lweTbQ6J66B8ybFOQEZnrGe/j9ydU5cHzFqjK4mfnixSfRAXTVJ Fw320Y0ZiCEt9h9aIgjqxg== -----END ENCRYPTED PRIVATE KEY-----
解决思路
方法1:改用BouncyCastle库处理(推荐)
Java原生的EncryptedPrivateKeyInfo对PBES2结合3DES的支持存在兼容性问题,BouncyCastle库能更好地处理这类旧加密算法。
步骤:
- 引入BouncyCastle依赖(Maven为例):
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.77</version> </dependency>
- 使用BouncyCastle读取加密私钥:
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import java.io.StringReader; import java.security.KeyPair; import java.security.PrivateKey; import java.security.Security; public class KeyReader { public static PrivateKey readEncryptedPrivateKey(String keyContent, char[] password) throws Exception { Security.addProvider(new BouncyCastleProvider()); PEMParser parser = new PEMParser(new StringReader(keyContent)); Object obj = parser.readObject(); parser.close(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); PrivateKey privateKey; if (obj instanceof PEMEncryptedKeyPair) { PEMDecryptorProvider decryptor = new JcePEMDecryptorProviderBuilder().build(password); PEMKeyPair keyPair = ((PEMEncryptedKeyPair) obj).decryptKeyPair(decryptor); privateKey = converter.getPrivateKey(keyPair.getPrivateKeyInfo()); } else if (obj instanceof PrivateKeyInfo) { privateKey = converter.getPrivateKey((PrivateKeyInfo) obj); } else { throw new IllegalArgumentException("Not a valid private key"); } return privateKey; } }
方法2:调整OpenSSL生成密钥的算法(最简单)
如果可以重新生成密钥,改用AES加密算法,Java原生代码就能正常处理:
生成命令改为:
openssl genpkey -algorithm rsa -out test.key -aes-256-cbc
之后原有的Java代码无需修改,即可正常读取密钥。
方法3:手动解析ASN.1并解密(无第三方库)
如果不能使用第三方库,需要手动解析PBES2的ASN.1结构,提取PBKDF2参数和3DES的IV,再手动完成解密:
核心步骤:
- 解析加密私钥的ASN.1结构,提取PBKDF2的salt、迭代次数,以及3DES的IV。
- 用PBKDF2+HmacSHA256生成3DES密钥。
- 用3DES-EDE-CBC模式解密私钥内容,得到PKCS8格式的私钥。
示例代码(仅核心逻辑):
// 省略ASN.1解析代码,需使用ASN.1解析库或手动处理字节流 // 假设已提取:byte[] salt, int iterations, byte[] iv, byte[] encryptedKeyData SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); PBEKeySpec spec = new PBEKeySpec(password); SecretKey tmp = factory.generateSecret(spec); // 3DES需要24字节密钥,PBKDF2生成的密钥取前24字节 byte[] keyBytes = Arrays.copyOf(tmp.getEncoded(), 24); SecretKey desKey = new SecretKeySpec(keyBytes, "DESede"); Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, desKey, ivSpec); byte[] decryptedKey = cipher.doFinal(encryptedKeyData); PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(decryptedKey); PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(pkcs8Spec);
注:手动解析ASN.1较为繁琐,容易出错,仅在无法使用第三方库时考虑。
内容的提问来源于stack exchange,提问作者rsenden
相关产品推荐
相关产品推荐

