Страницы

Поиск по вопросам

среда, 26 февраля 2020 г.

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

#java #c_sharp #криптография #rsa


Почему при генерации 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 часть и внедрил в свой проект.

    


Ответы

Ответ 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?

Комментариев нет:

Отправить комментарий