Как BouncyCastle генерирует ECDH "ключи"? - PullRequest
5 голосов
/ 23 июня 2019

У меня есть приложение, которое использует BouncyCastle в качестве поставщика безопасности, но я хочу переключиться на другое, которое использует OpenSSL напрямую (Conscrypt).У меня проблема в том, что я использую ECDH «Ключи» от KeyGenerator, предоставленного BouncyCastle, но не имею аналогичного KeyGenerator в моей другой библиотеке.

Для сравнения двух я собираюсь декодироватьточки, использующие оба метода, со следующими вводами -

добавлены разрывы строк для удобства чтения

BADX_GAXp03z_5p05O1-op61KJAl4j9U2sBnAnJ4p_6GSAIyFGU3lM
oC4aIXw_2qlTnplykArgjvwCWw-2g6L44

Использование метода BouncyCastle-

public org.bouncycastle.jce.interfaces.ECPublicKey loadECPublicKeyBC(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
    KeyFactory keyFactory = KeyFactory.getInstance("ECDH", "BC");

    ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1");
    ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
    org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = (org.bouncycastle.jce.interfaces.ECPublicKey)keyFactory.generatePublic(pubSpec);

    return ecPublicKey;
}

getAlgorithm возвращается EC.Возвращается getFormat X.509.

Значение getEncoded это -

[48,-126,1,51,48,-127,-20,6,7,42,-122,72,-50,61,2,1,48,
-127,-32,2,1,1,48,44,6,7,42,-122,72,-50,61,1,1,2,33,0,
-1,-1,-1,-1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,48,68,4,32,-1,-1,-1,-1,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-4,4,32,90,-58,53,-40,-86,58,-109,-25,-77,-21,-67,85,
118,-104,-122,-68,101,29,6,-80,-52,83,-80,-10,59,-50,60,
62,39,-46,96,75,4,65,4,107,23,-47,-14,-31,44,66,71,-8,-68,
-26,-27,99,-92,64,-14,119,3,125,-127,45,-21,51,-96,-12,
-95,57,69,-40,-104,-62,-106,79,-29,66,-30,-2,26,127,-101,
-114,-25,-21,74,124,15,-98,22,43,-50,51,87,107,49,94,-50,
-53,-74,64,104,55,-65,81,-11,2,33,0,-1,-1,-1,-1,0,0,0,0,-1,
-1,-1,-1,-1,-1,-1,-1,-68,-26,-6,-83,-89,23,-98,-124,-13,
-71,-54,-62,-4,99,37,81,2,1,1,3,66,0,4,0,-41,-4,96,23,-89,
77,-13,-1,-102,116,-28,-19,126,-94,-98,-75,40,-112,37,-30,
63,84,-38,-64,103,2,114,120,-89,-2,-122,72,2,50,20,101,55,
-108,-54,2,-31,-94,23,-61,-3,-86,-107,57,-23,-105,41,0,-82,
8,-17,-64,37,-80,-5,104,58,47,-114]

Просто используя алгоритм BouncyCastle EC (не ECDH) я получаю -

[48,-126,1,51,48,-127,-20,6,7,42,-122,72,
-50,61,2,1,48,-127,-32,2,1,1,48,44,6,7,42,
-122,72,-50,61,1,1,2,33,0,-1,-1,-1,-1,0,0,
0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,48,68,4,32,-1,-1,-1,-1,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-4,4,32,90,-58,53,-40,
-86,58,-109,-25,-77,-21,-67,85,118,-104,-122,
-68,101,29,6,-80,-52,83,-80,-10,59,-50,60,62,
39,-46,96,75,4,65,4,107,23,-47,-14,-31,44,66,
71,-8,-68,-26,-27,99,-92,64,-14,119,3,125,
-127,45,-21,51,-96,-12,-95,57,69,-40,-104,-62,
-106,79,-29,66,-30,-2,26,127,-101,-114,-25,
-21,74,124,15,-98,22,43,-50,51,87,107,49,94,
-50,-53,-74,64,104,55,-65,81,-11,2,33,0,-1,-1,
-1,-1,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-68,-26,
-6,-83,-89,23,-98,-124,-13,-71,-54,-62,-4,99,
37,81,2,1,1,3,66,0,4,0,-41,-4,96,23,-89,77,
-13,-1,-102,116,-28,-19,126,-94,-98,-75,40,
-112,37,-30,63,84,-38,-64,103,2,114,120,-89,
-2,-122,72,2,50,20,101,55,-108,-54,2,-31,-94,
23,-61,-3,-86,-107,57,-23,-105,41,0,-82,8,-17,
-64,37,-80,-5,104,58,47,-114]

Теперь с методом Conscrypt-

public ECPublicKey loadECPublicKey(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
    KeyFactory keyFactory = KeyFactory.getInstance("EC", "Conscrypt");

    ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1");
    ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
    ECPublicKey ecPublicKey = (ECPublicKey)keyFactory.generatePublic(pubSpec);

    return ecPublicKey;
}

Возвращается getAlgorithm EC.getFormat возвращается X.509.Значение getEncoded этого значения -

[48,89,48,19,6,7,42,-122,72,-50,61,2,1,6,8,42,
-122,72,-50,61,3,1,7,3,66,0,4,0,-41,-4,96,23,
-89,77,-13,-1,-102,116,-28,-19,126,-94,-98,-75,
40,-112,37,-30,63,84,-38,-64,103,2,114,120,-89,
-2,-122,72,2,50,20,101,55,-108,-54,2,-31,-94,23,
-61,-3,-86,-107,57,-23,-105,41,0,-82,8,-17,-64,
37,-80,-5,104,58,47,-114]

Игнорирование расхождений между обоими сгенерированными ключами EC.Что BouncyCastle делает в ECDH KeyGenerator?

DH, являющийся KeyAgreement, я предполагаю, что он генерирует ключ EC и запускает его через DH KeyAgreement, но что он инициализирует как закрытый ключ, когда в параметре ничего не указаноKeyGenerator spec?

Также.Почему, когда я использую алгоритм EC с обоими провайдерами, я получаю разные результаты для одного и того же алгоритма, когда оба используют спецификацию prime256v1?Я бы предположил, что они будут по крайней мере равны.

Редактировать:

ECUtil приходит от sun.security.util.ECUtil.

Для любого из классов, гдеи BC, и библиотека безопасности Java имеют общие имена в моих примерах (например, ECPoint) - это всегда библиотека безопасности Java.Только когда у класса есть префикс пути bouncycastle (например, org.bouncycastle.jce.interfaces.ECPublicKey), он становится классом BC.openSSLProvider является экземпляром OpenSSLProvider из библиотеки Conscrypt.

Этот проект можно найти здесь.

https://github.com/google/conscrypt

И пом для установки здесь -

<dependency>
    <groupId>org.conscrypt</groupId>
    <artifactId>conscrypt-openjdk-uber</artifactId>
    <version>2.1.0</version>
</dependency>

///

import org.conscrypt.OpenSSLProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.security.util.ECUtil;

Security.addProvider(new BouncyCastleProvider());
Security.addProvider(new OpenSSLProvider());

ECUtil.getECParameterSpec(new OpenSSLProvider, "prime256v1");

Редактировать Редактировать:

Полный минимальный воспроизводимый пример-

Редактировать Редактировать Редактировать:

Примертеперь включает в себя загрузку открытого ключа вручную, а не с помощью KeyFactory.

При ручной загрузке открытого ключа BC его закодированное значение соответствует закодированному значению открытого ключа Conscrypt при загрузке через Key Factory ...

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.JCEECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.conscrypt.OpenSSLProvider;
import sun.security.util.ECUtil;

import java.io.IOException;
import java.security.*;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.*;

public class Main {

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        Security.addProvider(new BouncyCastleProvider());
        Security.addProvider(new OpenSSLProvider());

        String pubKey = "BADX_GAXp03z_5p05O1-op61KJAl4j9U2sBnAnJ4p_6GSAIyFGU3lMoC4aIXw_2qlTnplykArgjvwCWw-2g6L44";

        ECPublicKey publicKey = (ECPublicKey)loadPublicKey(pubKey, "Conscrypt");
        org.bouncycastle.jce.interfaces.ECPublicKey publicKeyBC = (org.bouncycastle.jce.interfaces.ECPublicKey)loadPublicKey(pubKey, "BC");
        org.bouncycastle.jce.interfaces.ECPublicKey publicKeyBC2 = (org.bouncycastle.jce.interfaces.ECPublicKey) loadPublicKeyManually(pubKey);

        System.out.println(Arrays.toString(publicKey.getEncoded()));
        System.out.println(Arrays.toString(publicKeyBC.getEncoded()));
        System.out.println(Arrays.toString(publicKeyBC2.getEncoded()));
    }

    public static PublicKey loadPublicKey(String encodedPublicKey, String provider) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        Base64.Decoder base64Decoder = Base64.getUrlDecoder();
        byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);

        ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(new OpenSSLProvider(), "prime256v1");
        ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

        ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
        ECPublicKey ecPublicKey = (ECPublicKey)keyFactory.generatePublic(pubSpec);

        return ecPublicKey;
    }

    public static PublicKey loadPublicKeyManually(String encodedPublicKey) {
        Base64.Decoder base64Decoder = Base64.getUrlDecoder();
        byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);

        ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");

        org.bouncycastle.jce.spec.ECPublicKeySpec ecPublicKeySpec = new org.bouncycastle.jce.spec.ECPublicKeySpec(
                parameterSpec.getCurve().decodePoint(decodedPublicKey),
                parameterSpec
        );
        org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = new JCEECPublicKey(
                "EC",
                ecPublicKeySpec
        );

        return ecPublicKey;
    }
}

1 Ответ

5 голосов
/ 24 июня 2019

Два представления открытого ключа по существу эквивалентны.Оба являются экземплярами структуры SubjectPublicKeyInfo в кодировке DER, описанной в RFC 5280 .Эта структура содержит не только открытый ключ, но также некоторые метаданные, описывающие алгоритмический контекст открытого ключа.Для одной из форм метаданные просто утверждают, что контекст является кривой «prime256v1».Для других, все параметры этой кривой предоставляются вместо.Открытый ключ сам по себе является последней частью обеих форм, и, как вы можете видеть, они идентичны.

То, что у вас есть, - это 3 разных представления одного и того же открытого ключа.Кодированная строка base-64 содержит только точку эллиптической кривой типа 4 (несжатый), закодированную в соответствии с разделом 2.3.3 SEC 1 .Вы нашли недокументированный и неподдерживаемый API в классе sun.security.util.ECUtil, чтобы превратить его в PublicKey.

Я не совсем уверен, что ваш вопрос о закрытом ключе.Для схем DH, включая ECDH, закрытый ключ - это просто целое число, выбранное «надежно» из диапазона порядка основной группы.Затем открытый ключ вычисляется умножением (в смысле эллиптической кривой) этого целого числа на базовую точку кривой.Открытый ключ результата также является точкой на кривой.

...