Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EC private key recovery from PEM format with BouncyCastle

My application stores private keys in PEM format, the existing code works for RSA keys but I am trying to switch to EC keys and there is a problem. The key recovery seems to work, and the equals method on the recovered key returns true for the original key, but getAlgorithm() on the original key returns "EC" and on the recovered key "ECDSA". The discrepancy in the algorithm later causes problems because it does not match the algorithm for the corresponding public key.

Am I doing something wrong or is this a bug in the PEM parser?

Here is a test program which 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 from 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
like image 949
Bruce Skingle Avatar asked Jan 02 '15 13:01

Bruce Skingle


1 Answers

The problem is not the PEMParser but JcaPEMKeyConverter which treats EC keys as keys for 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 keys, so the algorithm selection is not unique here and probably the BC devs have chosen ECDSA as the most suitable choice. You could do something similar like JcaPEMKeyConverter with you own KeyFactory but choose your correct algorithm for EC keys.

like image 187
Drunix Avatar answered Oct 22 '22 06:10

Drunix