C# Как правильно получить ключ RSA с общедоступным показателем степени 3 из System.Security.Cryptography?

У меня есть программа С#, использующая System.Security.Cryptography (стандартный провайдер), которая должна генерировать ключи RSA определенного битового размера и экспоненты для взаимодействия с другой давней системой. Этот код кажется мне разумным:

            for (int trix = 0; trix < 1000; trix++)
            {
                using (var rsa2 = new RSACryptoServiceProvider(1024)) // public key length in bits
                { // PROBLEM: MS seems stuck on the big exponent
                    RSAParameters key2 = rsa2.ExportParameters(true);
                    key2.Exponent = new byte[1] { 3 }; // public key exponent
                    rsa2.ImportParameters(key2);
                    PrintToFeedback(rsa2.ToXmlString(true));
                    byte[] bm0 = Utilities.HexStringToByteArray("1002030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
                    byte[] bm1 = rsa2.Encrypt(bm0, false);
                    byte[] bm2 = rsa2.Decrypt(bm1, false);
                    string szbm0 = Utilities.ByteArrayToHexString(bm0);
                    string szbm2 = Utilities.ByteArrayToHexString(bm2);
                    if (szbm0 != szbm2)
                    {
                        PrintToFeedback("RSA module test FAILED with MS RSA keys with small exponent, bm0, bm1, bm2 follow:");
                        PrintToFeedback(szbm0);
                        PrintToFeedback(Utilities.ByteArrayToHexString(bm1));
                        PrintToFeedback(szbm2);
                        ok = false;
                        break;
                    }
                }
            }

В большинстве случаев, но не всегда, я получаю исключение Bad Parameter для rsa2.ImportParameters с показателем 3. Иногда это работает, и у меня были запуски, когда rsa2.ToXmlString показывает экспоненту 3:

<Exponent>Aw==</Exponent>

>base64 -d | xxd
Aw==
00000000: 03

Тестовый цикл иногда дает сбой с ненулевым триксом, поэтому работает немного. Смотрите скриншот и этот пост в социальной сети MSDN от 2019 года

Как правильно получить 1024-битный ключ с показателем степени 3 из System.Security.Cryptography?

(отредактировано для добавления ссылки MSDN)

C# Как правильно получить ключ RSA с общедоступным показателем степени 3 из System.Security.Cryptography?

Я не могу найти в документации никаких указаний на то, что спецификация общедоступной экспоненты поддерживается. И простой экспорт параметров, изменение общедоступной экспоненты и импорт параметров обычно должны приводить к противоречивым данным. Я бы использовал BouncyCastle для этой задачи. Эта библиотека позволяет вам сгенерировать ключ RSA, указав размер и общедоступную экспоненту.

Topaco 17.03.2022 20:00

@Topaco, спасибо за это предложение. Программа уже использует много кода 3DES и Hash из .Net. Я думаю, что импорт BouncyCastle или OpenSSL заставит сильно переписать, потому что между этими библиотеками и System.Security.Cryptography возникает конфликт имен. Я не думаю, что библиотеки могут сосуществовать. Что касается согласованности данных, я полагаю, что ключ должен обновляться с новым показателем. В тех немногих случаях, когда для ImportParameters нет исключений, тест шифрования-дешифрования работает.

Larry Martin 17.03.2022 21:03

Анализ успешно сгенерированного ключа в синтаксическом анализаторе ASN.1 (например, lapo.it/asn1js) показывает, что был изменен только общедоступный показатель степени, а другие параметры не были скорректированы, поэтому данные в целом противоречивы. Проверка OpenSSL (openssl rsa -check -in <ключ>) этого ключа вернула Ошибка ключа RSA: d e не соответствует 1, подтверждая это предположение. Вы можете убедиться в этом самостоятельно с помощью упомянутых инструментов.

Topaco 17.03.2022 21:56

BouncyCastle обычно без проблем используется параллельно с нативными криптографическими реализациями .NET, но, конечно, я не могу сказать это точно для вашего приложения, так как я не знаю вашего приложения в деталях. Что касается перезаписи, вы можете использовать BC исключительно для генерации ключей. Нет смысла мигрировать остальные в БК.

Topaco 17.03.2022 21:59

Спасибо, Топако. Буду внимательно смотреть БК. Для протокола я поместил тест в блок try и записал все значения ключей, которые не вызывали исключений с показателем степени 3. Из 100 тестов я получил 26 ключей. Перебирая эти значения N, P, Q с показателем степени 65537, все, кроме одного, прошли тест шифрования-дешифрования. Так что, возможно, проблема не в «периодической», а в том, что «MS выдает исключение для ImportParameters, когда новый показатель степени не будет работать со старыми значениями ключа».

Larry Martin 17.03.2022 22:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

После изменения публичной экспоненты остальные зависимые компоненты (а именно P, Q, Modulus, D, DP, DQ, InverseQ) ключа также должны быть скорректированы. Для этого определенно лучше использовать специализированные инструменты, например. БаунсиКасл.

В чистом С# вы можете сделать что-то вроде:

public static void Main()
{
    using (var rsa = new RSACryptoServiceProvider(1024))
    {
        RSAParameters key = rsa.ExportParameters(true);

        // new public exponent
        BigInteger e = 3;

        // update public exponent and adjust dependent components
        UpdatePublicExponent(ref key, e);
        
        rsa.ImportParameters(key);

        Console.WriteLine(rsa.ToXmlString(true));
        byte[] bm0 =
            HexStringToByteArray(
                "1002030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
        byte[] bm1 = rsa.Encrypt(bm0, false);
        byte[] bm2 = rsa.Decrypt(bm1, false);

        Console.WriteLine(bm0.SequenceEqual(bm2));
    }
}

Вот UpdatePublicExponent с другими вспомогательными функциями:

private static void UpdatePublicExponent(ref RSAParameters key, BigInteger e)
{
    int keyBytes = key.Modulus?.Length ?? 128;
    int keyBitLength = 8 * keyBytes;

    int pBitLength = (keyBitLength + 1) / 2;
    int qBitLength = keyBitLength - pBitLength;
    int minDiffBits = keyBitLength / 3;

    for (;;)
    {
        BigInteger p = GetRandomPrime(pBitLength, e);
        BigInteger q, n;
        for (;;)
        {
            q = GetRandomPrime(qBitLength, e);

            // p and q should not be too close together (or equal!)
            BigInteger diff = BigInteger.Abs(q - p);
            if (diff.GetBitLength() < minDiffBits)
            {
                continue;
            }

            // calculate the modulus
            n = p * q;

            if (n.GetBitLength() != keyBitLength)
            {
                // if we get here our primes aren't big enough, make the largest
                // of the two p and try again
                p = BigInteger.Max(p, q);
                continue;
            }

            break;
        }

        if (p < q)
        {
            BigInteger tmp = p;
            p = q;
            q = tmp;
        }

        BigInteger pSub1 = p - 1;
        BigInteger qSub1 = q - 1;

        BigInteger gcd = BigInteger.GreatestCommonDivisor(pSub1, qSub1);
        BigInteger lcm = pSub1 / gcd * qSub1;

        // calculate the private exponent
        BigInteger d = ModInverse(e, lcm);

        if (d.GetBitLength() <= qBitLength)
        {
            continue;
        }

        // calculate the CRT factors
        BigInteger dP = d % pSub1;
        BigInteger dQ = d % qSub1;
        BigInteger invQ = ModInverse(p, q);

        int halfBytes = (keyBytes + 1) / 2;

        // update key components
        key.P = GetBytes(p, halfBytes);
        key.Q = GetBytes(q, halfBytes);
        key.Modulus = GetBytes(n, keyBytes);

        key.Exponent = GetBytes(e, -1);

        key.D = GetBytes(d, keyBytes);
        key.DP = GetBytes(dP, halfBytes);
        key.DQ = GetBytes(dQ, halfBytes);
        key.InverseQ = GetBytes(invQ, halfBytes);

        break;
    }
}

private static BigInteger ModInverse(BigInteger a, BigInteger n)
{
    BigInteger i = n, v = 0, d = 1;
    while (a > 0)
    {
        BigInteger t = i / a, x = a;
        a = i % x;
        i = x;
        x = d;
        d = v - t * x;
        v = x;
    }
    v %= n;
    if (v < 0) v = (v + n) % n;
    return v;
}

private static BigInteger GetRandomPrime(int bitCount, BigInteger e)
{
    BigInteger prime;
    RandomNumberGenerator rng = RandomNumberGenerator.Create();
    int byteLength = (bitCount + 7) / 8;
    do
    {
        byte[] bytes = new byte[byteLength];
        rng.GetBytes(bytes);
        prime = new BigInteger(bytes, true);
    } while (prime.GetBitLength() != bitCount || prime % e == BigInteger.One || !IsProbablePrime(prime, 40));

    rng.Dispose();
    return prime;
}

// Miller-Rabin primality test as an extension method on the BigInteger type.
// http://rosettacode.org/wiki/Miller%E2%80%93Rabin_primality_test#C.23
private static bool IsProbablePrime(BigInteger source, int certainty)
{
    if (source == 2 || source == 3)
        return true;
    if (source < 2 || source % 2 == 0)
        return false;

    BigInteger d = source - 1;
    int s = 0;

    while (d % 2 == 0)
    {
        d /= 2;
        s += 1;
    }

    // There is no built-in method for generating random BigInteger values.
    // Instead, random BigIntegers are constructed from randomly generated
    // byte arrays of the same length as the source.
    RandomNumberGenerator rng = RandomNumberGenerator.Create();
    byte[] bytes = new byte[source.ToByteArray().LongLength];
    BigInteger a;

    for (int i = 0; i < certainty; i++)
    {
        do
        {
            // This may raise an exception in Mono 2.10.8 and earlier.
            // http://bugzilla.xamarin.com/show_bug.cgi?id=2761
            rng.GetBytes(bytes);
            a = new BigInteger(bytes);
        }
        while (a < 2 || a >= source - 2);

        BigInteger x = BigInteger.ModPow(a, d, source);
        if (x == 1 || x == source - 1)
            continue;

        for (int r = 1; r < s; r++)
        {
            x = BigInteger.ModPow(x, 2, source);
            if (x == 1)
                return false;
            if (x == source - 1)
                break;
        }

        if (x != source - 1)
            return false;
    }

    return true;
}

private static byte[] GetBytes(BigInteger value, int size = -1)
{
    byte[] bytes = value.ToByteArray();

    if (size == -1)
    {
        size = bytes.Length;
    }

    if (bytes.Length > size + 1)
    {
        throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
    }

    if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0)
    {
        throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
    }

    Array.Resize(ref bytes, size);
    Array.Reverse(bytes);
    return bytes;
}

public static byte[] HexStringToByteArray(string hex)
{
    byte[] bytes = new byte[hex.Length / 2];
    int[] hexValue = new int[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
        0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };

    for (int x = 0, i = 0; i < hex.Length; i += 2, x += 1)
    {
        bytes[x] = (byte) (hexValue[char.ToUpper(hex[i + 0]) - '0'] << 4 | hexValue[char.ToUpper(hex[i + 1]) - '0']);
    }

    return bytes;
}

руслан, какую реализацию BigInteger вы используете? Спасибо за код!

Larry Martin 18.03.2022 14:05

Я только что понял, что исходный код не работает. Обновил, но оказалось, что работает только в 1/4 случаях. Я использую стандартный System.Numerics.BigInteger

Ruslan Gilmutdinov 18.03.2022 14:26

Я все еще получаю исключения, вероятно, из-за моего переноса вашего кода в мою систему. Я использую старый BigInteger из CodeProject с другой семантикой. Сейчас я буду перебирать блок try-catch, пока .Net не даст мне ключ, который работает с показателем степени 3. Удар по скорости пока не проблема.

Larry Martin 18.03.2022 15:25

ну теперь я вижу проблему. Дело в том, что публичный показатель e должен быть выбран таким образом, чтобы выполнялись следующие условия: 1 < e < ϕ и gcd(e, ϕ) = 1, где ϕ = (p-1)(q-1). Поэтому, когда вы меняете e, вам также нужно настроить p и q. Подробнее см. в di-mgt.com.au/rsa_alg.html#keygen.

Ruslan Gilmutdinov 18.03.2022 15:44

Я отредактировал код. Теперь он пересчитывает все ключевые компоненты.

Ruslan Gilmutdinov 18.03.2022 19:59

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