Java – recover EC private key from PEM format using bouncycastle
My application stores the private key in PEM format. The existing code is applicable to RSA key, but I tried to switch to EC key and there is a problem The key recovery appears to be valid, and the equals method on the recovery key returns true for the original key, but getalgorithm() on the original key returns "EC" and returns the recovery key "ECDSA" The difference in the algorithm later led to problems because it did not match the algorithm of the corresponding public key
What did I do wrong or was this an error in the PEM parser?
This is a test program that demonstrates the problem:
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(); } } }
The output of the test program is:
privateKey=EC Private Key S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146 privateKey.getAlgorithm()=EC der=-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBPRmShGjRT6u5I1qB/B7HBv9UE6cKdgtj4H1FpdBKLxhcxvc1UAGQ KRqsr1jJIwas2H+gGkfZB9XT/AFTEYA1MUagBwYFK4EEACOhgYkDgYYABAFN5ZcE zg9fV13u57ffwyN9bm9Wa9Pe0MtL2cd5CW2ku4mWzgS5m8IfNMAw2QMah5Z9fuXW 1fGJgUx1RsC09R6legFTgymlbqt+CaPhNsJkr12cjyzhT1NxR6uEzMUtBcYxqLHy ANkhHmvAk221//YIRIWix7ZlRsRrs+iYrpWw4bMt9A== -----END EC PRIVATE KEY----- recoveredKey=EC Private Key S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146 recoveredKey.getAlgorithm()=ECDSA Key recovery ok Key algorithms do not match
Solution
The problem is not pemparser, but jcapemkeyconverter regards the EC key as the key of 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); ...
The algorithm identifier is ID ecpublickey, which is also used for ECDSA key. Therefore, the algorithm selection is not unique here, and BC devs may choose ECDSA as the most appropriate choice You can use your own keyfactory to perform operations similar to jcapemkeyconverter, but select the correct algorithm for the EC key