Общий секрет Java X25519 неверен при использовании тестовых векторов из RFC7748

При использовании Тестовые векторы RFC7748 для эллиптической кривой diffie hellman в java я не могу получить ожидаемый общий секретный ключ. Я могу сделать это на других языках. Я использую openjdk 11 с поставщиком безопасности Sun по умолчанию. Я нашел официальные тесты, которые используют эти тестовые векторы. Но я не могу получить ожидаемый результат, даже если скопирую-вставлю и запущу их. Например, здесь — это тест, в котором используются те же самые векторы, которые потерпят неудачу, если я скопирую-вставлю и запущу локально. Он использует некоторые служебные функции из здесь, которые я также скопировал. Я знаю, что я должен делать что-то не так, но я не могу понять, что именно. Вот мой код:

public class main {
    public static BigInteger hexStringToBigInteger(boolean clearHighBit, String str) {
        BigInteger result = BigInteger.ZERO;
        for (int i = 0; i < str.length() / 2; i++) {
            int curVal = Character.digit(str.charAt(2 * i), 16);
            curVal <<= 4;
            curVal += Character.digit(str.charAt(2 * i + 1), 16);
            if (clearHighBit && i == str.length() / 2 - 1) {
                curVal &= 0x7F;
                result = result.add(BigInteger.valueOf(curVal).shiftLeft(8 * i));
            }
        }
        return result;
    }

    public static byte[] hexStringToByteArray(String str) {
        byte[] result = new byte[str.length() / 2];
        for (int i = 0; i < result.length; i++) {
            result[i] = (byte) Character.digit(str.charAt(2 * i), 16);
            result[i] <<= 4;
            result[i] += Character.digit(str.charAt(2 * i + 1), 16);
        }
        return result;
    }

    public static String byteArrayToHexString(byte[] arr) {
        StringBuilder result = new StringBuilder();
        for (byte curVal : arr) {
            result.append(Character.forDigit(curVal >> 4 & 0xF, 16));
            result.append(Character.forDigit(curVal & 0xF, 16));
        }
        return result.toString();
    }

    private static void runDiffieHellmanTest(String curveName, String a_pri,
                                             String b_pub, String result) throws Exception {

        NamedParameterSpec paramSpec = new NamedParameterSpec(curveName);
        KeyFactory kf = KeyFactory.getInstance("XDH");
        KeySpec privateSpec = new XECPrivateKeySpec(paramSpec, hexStringToByteArray(a_pri));
        PrivateKey privateKey = kf.generatePrivate(privateSpec);
        boolean clearHighBit = curveName.equals("X25519");
        KeySpec publicSpec = new XECPublicKeySpec(paramSpec, hexStringToBigInteger(clearHighBit, b_pub));
        PublicKey publicKey = kf.generatePublic(publicSpec);

        byte[] encodedPrivateKey = privateKey.getEncoded();
        System.out.println("Encoded private: " + byteArrayToHexString(encodedPrivateKey));
        byte[] encodedPublicKey = publicKey.getEncoded();
        System.out.println("Encoded public: " + byteArrayToHexString(encodedPublicKey));

        KeyAgreement ka = KeyAgreement.getInstance("XDH");
        ka.init(privateKey);
        ka.doPhase(publicKey, true);

        byte[] sharedSecret = ka.generateSecret();
        byte[] expectedResult = hexStringToByteArray(result);
        if (!Arrays.equals(sharedSecret, expectedResult)) {
            throw new RuntimeException("fail: expected=" + result + ", actual="
                    + byteArrayToHexString(sharedSecret));
        }
    }

    public static void main(String[] args) throws Exception {
        runDiffieHellmanTest(
                "X25519",
                "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
                "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
                "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
    }
}

Добро пожаловать в StackOverflow. Это потенциально хороший вопрос, но вы не следовали правилам публикации здесь. Пожалуйста, посетите центр помощи, возьмите тур и особенно прочитайте Как спросить. Ожидается, что вы включите свой код в вопрос, а не в качестве ссылки, которая в конечном итоге приведет к ошибке 404. Вопросы должны быть автономными, чтобы они оставались ресурсом для будущих посетителей. Пожалуйста, редактировать ваш вопрос и включите весь соответствующий код, данные и сообщения об ошибках/полную трассировку стека (если применимо). Форматируйте трассировки стека и сообщения об ошибках так же, как и код.

Jim Garrison 16.05.2022 23:16

Ваш метод hexStringToBigInteger выглядит неправильно. Я не совсем уверен, что вы там делаете с «очисткой старшего бита», но у BigInteger уже есть конструктор BigInteger(String value, int radix) для этого. Просто сделайте new BigInteger("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674d‌​adfc7e146f882b4f", 16) например.

President James K. Polk 17.05.2022 00:35

@PresidentJamesK.Polk: то, что я считаю настоящая версия, имеет комментарии, объясняющие прямой порядок байтов общедоступных значений XDH (это причуда Бернштейна), и это указано в разделе 5 rfc7748 вместе с необходимостью в целом (хотя и не для этих тестовых случаев) чтобы очистить старший бит старшего = самого правого байта. Java-ctor, на который вы ссылаетесь, предназначен для прямого порядка байтов.

dave_thompson_085 17.05.2022 02:36

@dave_thompson_085: хорошо, тогда проблема просто в том, что hexStringToBigInteger отбрасывает каждый байт, сбрасывая curVal в начале цикла. Только для последней итерации цикла значение сохраняется.

President James K. Polk 17.05.2022 02:53
Основы программирования на Java
Основы программирования на Java
Java - это высокоуровневый объектно-ориентированный язык программирования, основанный на классах.
Концепции JavaScript, которые вы должны знать как JS программист!
Концепции JavaScript, которые вы должны знать как JS программист!
JavaScript (Js) - это язык программирования, объединяющий HTML и CSS с одной из основных технологий Всемирной паутины. Более 97% веб-сайтов используют...
1
4
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы неправильно поменяли местами две строки в hexStringToBigInteger:

            if (clearHighBit && i == str.length() / 2 - 1) {
                curVal &= 0x7F;
                result = result.add(BigInteger.valueOf(curVal).shiftLeft(8 * i));
            }

вместо этого должно быть:

            if (clearHighBit && i == str.length() / 2 - 1) {
                curVal &= 0x7F;
            }
            result = result.add(BigInteger.valueOf(curVal).shiftLeft(8 * i));

Таким образом, реализация Java не очищает MSB старшего байта. А так как XECPublicKeySpec уже ожидает BigInteger вместо массива байтов, он уже должен быть в формате с прямым порядком байтов. Я также мог бы сделать hexStringToByteArray, чтобы получить байты, затем &= 0x7F последний байт и перевернуть весь массив, прежде чем передать его в BigInteger.

Pain 17.05.2022 11:39

Я не мог установить общий секрет между приложениями Go и Java, поэтому я попытался отладить причину того, что привело меня к чтению RFC7748 и копанию исходного кода для X25519 обмена ключами в Java. Итак, для людей, которые хотят выполнить X25519 обмен ключами между Java и некоторыми другими приложениями, отличными от Java, вот главный вывод. Java уже ожидает, что входной открытый ключ будет BigInteger вместо массива байтов. Некоторые другие языки могут возвращать открытый ключ в виде массива байтов в формате big-endian. Согласно RFC7748 спецификации, X координата точки на эллиптической кривой (которая является вашим массивом байтов открытого ключа) должен быть в формате little-endian. Таким образом, вам нужно только изменить входной массив байтов открытого ключа, чтобы сделать его little-endian, прежде чем подавать на BigInteger.

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