Q:

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

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

A:

Лишний байт
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?

