使用 BouncyCastle 从 PEM 格式恢复 EC 私钥
EC private key recovery from PEM format with BouncyCastle
我的应用程序以 PEM 格式存储私钥,现有代码适用于 RSA 密钥,但我正在尝试切换到 EC 密钥,但出现问题。密钥恢复似乎有效,恢复密钥 returns 上的 equals 方法对原始密钥为真,但原始密钥 returns "EC" 和恢复密钥 "EC" 上的 getAlgorithm() =22=]。算法中的差异后来导致问题,因为它与相应 public 密钥的算法不匹配。
我是不是做错了什么,或者这是 PEM 解析器中的错误?
这是一个演示问题的测试程序:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.immutify.janus.keytool.KeyToolUtils;
public class TestPrivateKeyRecovery
{
private static final String KEY_ALGORITHM = "EC";
private static final String SIGNATURE_ALGORITHM = "SHA512withECDSA";
private static final String PROVIDER = "BC";
private static final String CURVE_NAME = "secp521r1";
private static final String WRAPPING_CIPHER_SPEC = "ECIESwithAES";
private ECGenParameterSpec ecGenSpec;
private KeyPairGenerator keyGen_;
private SecureRandom rand_;
public void run()
{
try
{
rand_ = new SecureRandom();
ecGenSpec = new ECGenParameterSpec(CURVE_NAME);
keyGen_ = KeyPairGenerator.getInstance(KEY_ALGORITHM, PROVIDER);
keyGen_.initialize(ecGenSpec, rand_);
PrivateKey privateKey = keyGen_.generateKeyPair().getPrivate();
String der = privateKeyToDER(privateKey);
PrivateKey recoveredKey = privateKeyFromDER(der);
System.out.println("privateKey=" + privateKey);
System.out.println("privateKey.getAlgorithm()=" + privateKey.getAlgorithm());
System.out.println("der=" + der);
System.out.println("recoveredKey=" + privateKey);
System.out.println("recoveredKey.getAlgorithm()=" + recoveredKey.getAlgorithm());
System.out.println();
if(privateKey.equals(recoveredKey))
System.out.println("Key recovery ok");
else
System.err.println("Private key recovery failed");
if(privateKey.getAlgorithm().equals(recoveredKey.getAlgorithm()))
System.out.println("Key algorithm ok");
else
System.err.println("Key algorithms do not match");
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static String privateKeyToDER(PrivateKey key) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PEMWriter pemWriter = new PEMWriter(new OutputStreamWriter(bos));
pemWriter.writeObject(key);
pemWriter.close();
return new String(bos.toByteArray());
}
public static PrivateKey privateKeyFromDER(String der) throws IOException
{
StringReader reader = new StringReader(der);
PEMParser pemParser = new PEMParser(reader);
try
{
Object o = pemParser.readObject();
if (o == null || !(o instanceof PEMKeyPair))
{
throw new IOException("Not an OpenSSL key");
}
KeyPair kp = new JcaPEMKeyConverter().setProvider("BC").getKeyPair((PEMKeyPair)o);
return kp.getPrivate();
}
finally
{
pemParser.close();
}
}
}
测试程序的输出是:
privateKey=EC私钥
S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146
privateKey.getAlgorithm()=EC
der=-----开始EC私钥-----
MIHcAgEBBEIBPRmShGjRT6u5I1qB/B7HBv9UE6cKdgtj4H1FpdBKLxhCXvc1UAGQ
KRqsr1jJIwas2H+gGkfZB9XT/AFTEYA1MUagBwYFK4EEACOhgYkDgYYABAFN5ZcE
zg9fV13u57ffwyN9bm9Wa9Pe0MtL2cd5CW2ku4mWzgS5m8IfNMAw2QMah5Z9fuXW
1fGJgUx1RsC09R6legFTgymlbqt+CaPhNsJkr12cjyzhT1NxR6uEzMUtBcYxqLHy
ANkhHmvAk221//YIRIWix7ZlRsRrs+iYrpWw4bMt9A==
-----EC 私钥结束-----
recoveredKey=EC私钥
S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146
recoveredKey.getAlgorithm()=ECDSA
密钥恢复正常
关键算法不匹配
问题不在于 PEMParser
,而是 JcaPEMKeyConverter
将 EC 密钥视为 ECDSA 的密钥:
algorithms.put(X9ObjectIdentifiers.id_ecPublicKey, "ECDSA");
...
private KeyFactory getKeyFactory(AlgorithmIdentifier algId)
throws NoSuchAlgorithmException, NoSuchProviderException
{
ASN1ObjectIdentifier algorithm = algId.getAlgorithm();
String algName = (String)algorithms.get(algorithm);
...
算法标识符是 id-ecPublicKey,它也用于 ECDSA 密钥,因此这里的算法选择不是唯一的,可能 BC 开发人员选择了 ECDSA 作为最合适的选择。您可以使用自己的 KeyFactory
执行类似于 JcaPEMKeyConverter
的操作,但要为 EC 密钥选择正确的算法。
我的应用程序以 PEM 格式存储私钥,现有代码适用于 RSA 密钥,但我正在尝试切换到 EC 密钥,但出现问题。密钥恢复似乎有效,恢复密钥 returns 上的 equals 方法对原始密钥为真,但原始密钥 returns "EC" 和恢复密钥 "EC" 上的 getAlgorithm() =22=]。算法中的差异后来导致问题,因为它与相应 public 密钥的算法不匹配。
我是不是做错了什么,或者这是 PEM 解析器中的错误?
这是一个演示问题的测试程序:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.immutify.janus.keytool.KeyToolUtils;
public class TestPrivateKeyRecovery
{
private static final String KEY_ALGORITHM = "EC";
private static final String SIGNATURE_ALGORITHM = "SHA512withECDSA";
private static final String PROVIDER = "BC";
private static final String CURVE_NAME = "secp521r1";
private static final String WRAPPING_CIPHER_SPEC = "ECIESwithAES";
private ECGenParameterSpec ecGenSpec;
private KeyPairGenerator keyGen_;
private SecureRandom rand_;
public void run()
{
try
{
rand_ = new SecureRandom();
ecGenSpec = new ECGenParameterSpec(CURVE_NAME);
keyGen_ = KeyPairGenerator.getInstance(KEY_ALGORITHM, PROVIDER);
keyGen_.initialize(ecGenSpec, rand_);
PrivateKey privateKey = keyGen_.generateKeyPair().getPrivate();
String der = privateKeyToDER(privateKey);
PrivateKey recoveredKey = privateKeyFromDER(der);
System.out.println("privateKey=" + privateKey);
System.out.println("privateKey.getAlgorithm()=" + privateKey.getAlgorithm());
System.out.println("der=" + der);
System.out.println("recoveredKey=" + privateKey);
System.out.println("recoveredKey.getAlgorithm()=" + recoveredKey.getAlgorithm());
System.out.println();
if(privateKey.equals(recoveredKey))
System.out.println("Key recovery ok");
else
System.err.println("Private key recovery failed");
if(privateKey.getAlgorithm().equals(recoveredKey.getAlgorithm()))
System.out.println("Key algorithm ok");
else
System.err.println("Key algorithms do not match");
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static String privateKeyToDER(PrivateKey key) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PEMWriter pemWriter = new PEMWriter(new OutputStreamWriter(bos));
pemWriter.writeObject(key);
pemWriter.close();
return new String(bos.toByteArray());
}
public static PrivateKey privateKeyFromDER(String der) throws IOException
{
StringReader reader = new StringReader(der);
PEMParser pemParser = new PEMParser(reader);
try
{
Object o = pemParser.readObject();
if (o == null || !(o instanceof PEMKeyPair))
{
throw new IOException("Not an OpenSSL key");
}
KeyPair kp = new JcaPEMKeyConverter().setProvider("BC").getKeyPair((PEMKeyPair)o);
return kp.getPrivate();
}
finally
{
pemParser.close();
}
}
}
测试程序的输出是:
privateKey=EC私钥 S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146 privateKey.getAlgorithm()=EC der=-----开始EC私钥----- MIHcAgEBBEIBPRmShGjRT6u5I1qB/B7HBv9UE6cKdgtj4H1FpdBKLxhCXvc1UAGQ KRqsr1jJIwas2H+gGkfZB9XT/AFTEYA1MUagBwYFK4EEACOhgYkDgYYABAFN5ZcE zg9fV13u57ffwyN9bm9Wa9Pe0MtL2cd5CW2ku4mWzgS5m8IfNMAw2QMah5Z9fuXW 1fGJgUx1RsC09R6legFTgymlbqt+CaPhNsJkr12cjyzhT1NxR6uEzMUtBcYxqLHy ANkhHmvAk221//YIRIWix7ZlRsRrs+iYrpWw4bMt9A== -----EC 私钥结束----- recoveredKey=EC私钥 S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146 recoveredKey.getAlgorithm()=ECDSA 密钥恢复正常 关键算法不匹配
问题不在于 PEMParser
,而是 JcaPEMKeyConverter
将 EC 密钥视为 ECDSA 的密钥:
algorithms.put(X9ObjectIdentifiers.id_ecPublicKey, "ECDSA");
...
private KeyFactory getKeyFactory(AlgorithmIdentifier algId)
throws NoSuchAlgorithmException, NoSuchProviderException
{
ASN1ObjectIdentifier algorithm = algId.getAlgorithm();
String algName = (String)algorithms.get(algorithm);
...
算法标识符是 id-ecPublicKey,它也用于 ECDSA 密钥,因此这里的算法选择不是唯一的,可能 BC 开发人员选择了 ECDSA 作为最合适的选择。您可以使用自己的 KeyFactory
执行类似于 JcaPEMKeyConverter
的操作,但要为 EC 密钥选择正确的算法。