RSA криптография между Java и C# приложениями

178
13 января 2021, 16:30

Почему при генерации RSA ключа длиной 512 бит в C# и в Java разная длина в байтах публичного ключа?

Пример кода C#:

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(512);
XDocument xdocPub = XDocument.Parse(rsa.ToXmlString(false));
string publicKey = xdocPub.Element("RSAKeyValue").Element("Modulus").Value;
byte[] publicArray = Convert.FromBase64String(publicKey);
Console.WriteLine("Public Key Byte Array Length: " + publicArray.Length);
Console.WriteLine("Public Key Base64 Length: " + publicKey.Length);
Console.WriteLine("Public Key Text: " + publicKey);

Результат:

Public Key Byte Array Length: 64
Public Key Base64 Length: 88
Public Key Text: uELcIjgR6GpiayZ1wHruHOtO8dpg+xmg85VAvunWUEL+aifoyk9+CA/dez4UmuIwLEgRlpVoIIOD0e5i9v8lrQ==

Пример кода Java:

final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(new RSAKeyGenParameterSpec(512, RSAKeyGenParameterSpec.F4));
KeyPair pair = keyGen.genKeyPair();
final byte[] arrayPublic = ((RSAPublicKey) pair.getPublic()).getModulus().toByteArray();
System.out.println("Public Key [origin] lenght: " + arrayPublic.length);
String publicBase64 = Base64.getEncoder().encodeToString(arrayPublic);
System.out.println("Public Key [base64] lenght : " + publicBase64.length());
System.out.println("Public Key [base64]: " + publicBase64);

Результат:

Public Key [origin] lenght: 65
Public Key [base64] lenght : 88
Public Key [base64]: ANuun1/CnOmk2ghsydz4cKvPCd7q0YqqeyjzLqtKx7QHqv1tFjJYsXquYGcY8zkaPsPBrRlCAJov7+J88GaFDKc=

Заметил, что в байт массиве ключа сгенерированного в Java присутствует нулевой байт в начале массива ключа.

Самое интересное, что если я отправляю ключ из C# приложения длиной 64 байта, то в Java приложении получаю ошибку "exponent is larger than modulus" в следующем коде:

try
{
    final KeyFactory kfac = KeyFactory.getInstance("RSA");
    final BigInteger modulus = new BigInteger("здесь вставляется byte[] массив публичного ключа от C#");
    final RSAPublicKeySpec kspec1 = new RSAPublicKeySpec(modulus, RSAKeyGenParameterSpec.F4);
    RSAPublicKey publicKey = (RSAPublicKey) kfac.generatePublic(kspec1);
}
catch (GeneralSecurityException e)
{
    e.printStackTrace();
}

При этом, если я добавлю нулевой байт в начало ключа перед отправкой из C# приложения в Java приложение, то со стороны Java приложения нет никаких проблем с инициализацией криптографии и шифровкой отправляемых данных. Но в этом случае я не могу декодировать входящие данные в C# приложении этим же экземпляром криптографии. Получаю ошибку "Плохие данные".

Подскажите, что я делаю не так и почему длина ключей разная?

UPD 1: добавлен тест в Java

PublicKey взят из первого примера сгенерированного в C#.

public class Test
{
    public static void main(String[] args)
    {
        final String base64key = "uELcIjgR6GpiayZ1wHruHOtO8dpg+xmg85VAvunWUEL+aifoyk9+CA/dez4UmuIwLEgRlpVoIIOD0e5i9v8lrQ==";
        try
        {
            byte[] publicKeyArray = Base64.getDecoder().decode(base64key);
            final KeyFactory kfac = KeyFactory.getInstance("RSA");
            final BigInteger modulus = new BigInteger(1, publicKeyArray);
            final RSAPublicKeySpec kspec1 = new RSAPublicKeySpec(modulus, RSAKeyGenParameterSpec.F4);
            RSAPublicKey publicKey = (RSAPublicKey) kfac.generatePublic(kspec1);
            byte[] array = publicKey.getModulus().toByteArray();
            System.out.println("Public Key [Array] lenght : " + array.length);
            String publicBase64 = Base64.getEncoder().encodeToString(array);
            System.out.println("Public Key [base64] lenght : " + publicBase64.length());
            System.out.println("Public Key [base64]: " + publicBase64);
        }
        catch (GeneralSecurityException e)
        {
            e.printStackTrace();
        }
    }
}

В результате инициализации RSAPublicKey в Java Base64 ключ не соответствует входному. Хотя по длине 65 байт. Но это работает только с signum = 1.

Public Key [Array] lenght : 65
Public Key [base64] lenght : 88
Public Key [base64]: ALhC3CI4EehqYmsmdcB67hzrTvHaYPsZoPOVQL7p1lBC/mon6MpPfggP3Xs+FJriMCxIEZaVaCCDg9HuYvb/Ja0=

UPD 2: проблему C# генерации ключей RSA решила библиотека Bouncy Castle. У библиотеки есть исходники поэтому я взял только RSA часть и внедрил в свой проект.

Answer 1

Лишний байт

BigInteger.toByteArray включает в результат знаковый бит числа:

... The array will contain the minimum number of bytes required to represent this BigInteger, including at least one sign bit, which is (ceil((this.bitLength() + 1)/8)) ...

Т.ч. да, нулевой байт в начале можно отрезать по необходимости. Сделать это можно с помощью System.arraycopy:

byte[] array = publicKey.getModulus().toByteArray();
//убираем лишний нолик
final byte[] keyBytes = new byte[64];
System.arraycopy(array, 1, keyBytes, 0, 64);

Посмотрите ответ @Maarten Bodewes на близкий вопрос на английском (How insert bits into block in java cryptography?), там приводится готовый метод для «нормализации» массива байтов с учетом ведущего нуля, также учитывается случай, когда массив короче 64 (вроде возникает только для приватных ключей).

Плохие данные

Конструктор BigInteger​(byte[] val) также ожидает получить знаковое представление числа и пытается считать у ключа знаковый бит. Попробуйте использовать конструктор, который принимает знаковый бит отдельно BigInteger​(int signum, byte\[\] magnitude):

new BigInteger(1, /*байты ключа*/)

Похожие вопросы на en.SO:

  • how to build a rsa key with a modulus of 64 bytes in java
  • How insert bits into block in java cryptography?
READ ALSO
добавление элемента в SimpleListProperty

добавление элемента в SimpleListProperty

у меня есть такая строка

186
Hibernate и уровни изоляциии транзакций

Hibernate и уровни изоляциии транзакций

Есть два тестовых метода:

178
Не могу получить доступ к файлу. Android Studio

Не могу получить доступ к файлу. Android Studio

Я получаю путь к файлу, сохраняю в переменную(Путь корректный, я проверял)

181
Правильное объявление коллекций

Правильное объявление коллекций

Почему для этих реализаций пишут вот так, а не вот так:

179