Восстановление закрытого ключа EC из формата PEM с помощью BouncyCastle

Мое приложение хранит закрытые ключи в формате PEM, существующий код работает для ключей RSA, но я пытаюсь переключиться на ключи EC, и возникает проблема. Восстановление ключа, кажется, работает, и метод equals на восстановленном ключе возвращает true для исходного ключа, но getAlgorithm() на исходном ключе возвращает "EC" и на восстановленном ключе "ECDSA". Расхождение в алгоритме позже вызывает проблемы, потому что оно не соответствует алгоритму для соответствующего открытого ключа.

Я делаю что-то не так или это ошибка в 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=----- НАЧАТЬ ЕС ЧАСТНЫЙ КЛЮЧ -----
MIHcAgEBBEIBPRmShGjRT6u5I1qB/B7HBv9UE6cKdgtj4H1FpdBKLxhCXvc1UAGQ
KRqsr1jJIwas2H+gGkfZB9XT/AFTEYA1MUagBwYFK4EEACOhgYkDgYYABAFN5ZcE
zg9fV13u57ffwyN9bm9Wa9Pe0MtL2cd5CW2ku4mWzgS5m8IfNMAw2QMah5Z9fuXW
1fGJgUx1RsC09R6legFTgymlbqt+CaPhNsJkr12cjyzhT1NxR6uEzMUtBcYxqLHy
ANkhHmvAk221//YIRIWix7ZlRsRrs+iYrpWw4bMt9A==
----- КОНЕЦ ЕС ЧАСТНЫЙ КЛЮЧ -----

recoveredKey= закрытый ключ EC
             S: 13d19928468d14fabb9235a81fc1ec706ff5413a70a760b63e07d45a5d04a2f18425ef735500190291aacaf58c92306acd87fa01a47d907d5d3fc01531180353146

recoveredKey.getAlgorithm()=ECDSA

Восстановление ключа в порядке
Ключевые алгоритмы не совпадают

1 ответ

Решение

Проблема не в 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 в качестве наиболее подходящего варианта. Вы могли бы сделать что-то подобное, как JcaPEMKeyConverter с тобой KeyFactory но выберите правильный алгоритм для ключей EC.

Другие вопросы по тегам