You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助: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库能更好地处理这类旧加密算法。

步骤:

  1. 引入BouncyCastle依赖(Maven为例):
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.77</version>
</dependency>
  1. 使用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,再手动完成解密:

核心步骤:

  1. 解析加密私钥的ASN.1结构,提取PBKDF2的salt、迭代次数,以及3DES的IV。
  2. 用PBKDF2+HmacSHA256生成3DES密钥。
  3. 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:07:04