Как использовать RSACryptoServiceProvider для расшифровки, если у меня есть только p, q, d и u?

Я создаю простое клиентское приложение для экспериментов с Mega и не могу понять, как используется RSA. . Возьмем, к примеру, расшифровку идентификатора сеанса — это одно из первых действий, которое необходимо сделать, чтобы войти в систему.

API предоставляет мне следующие данные RSA:

  • р (1024 бита)
  • д (1024 бита)
  • д (2044 бита)
  • u (1024 бита)

Начнем с того, что я не знаю, что означает "у". Я вижу из кода, что он вычисляется modinverse(p, q) - это то, что обычно называют qInverse?

Это значительно меньше данных RSA для закрытого ключа, чем я использовал ранее, поэтому я не совсем уверен, что с этим делать. Однако мне дали понять, что некоторые данные RSA, используемые RSACryptoServiceProvider, являются просто предварительно рассчитанными данными для целей оптимизации, поэтому, возможно, остальные не нужны?

Используя эти данные, JavaScript сайта расшифровывает идентификатор сеанса с помощью следующей функции:

// Compute m**d mod p*q for RSA private key operations.

function RSAdecrypt(m, d, p, q, u)
{
 var xp = bmodexp(bmod(m,p), bmod(d,bsub(p,[1])), p);
 var xq = bmodexp(bmod(m,q), bmod(d,bsub(q,[1])), q);

 var t=bsub(xq,xp);
 if(t.length==0)
 {
  t=bsub(xp,xq);
  t=bmod(bmul(t, u), q);
  t=bsub(q,t);
 }
 else
 {
  t=bmod(bmul(t, u), q);
 } 
 return badd(bmul(t,p), xp);
}

Я хотел бы сделать это в .NET, используя RSACryptoServiceProvider, но если я передам ему 4 части данных, которые у меня есть (при условии, что u == qInverse), ключ будет отклонен во время импорта с исключением «Неверные данные».

Должен ли я делать что-то еще с данными? Можно ли вообще использовать RSACryptoServiceProvider в этой ситуации?

Пример параметров и зашифрованных данных, которые я тестирую, приведен ниже.

 var p = Convert.FromBase64String("1AkMwy3SPbJtL/k2RUPNztBQKow0NX9LVr5/73+zR3cuwgUToYkVefKdzlTgeri9CAVUq/+jU6o+P7sUpPUN+V97quZa00m3GSIdonRMdaMrDDH5aHnkQgOsCjLJDWXU6+TQBqLumR3XMSat3VO09Dps+6NcMc+uMi5atC3tb+0=");
 var q = Convert.FromBase64String("qtnlmPbATJajNdihw1K6cwSormySATp7g75vYfilYx6RXN3xpNCZR/i8zFbx/lDh+n1a2rdHy1nWyuaD3UmE26d1xUkmsPDfBc72WXt88UqWE/gF7NJjtgTxS2Ui+2GGKUCloi5UA/pOI7R5TBvGI8zna00SH78bctyE0dcAcwM=");
 var d = Convert.FromBase64String("CFL4QPQ8zLzrf2bUzCVX8S2/eALzo/P2cvQsW9lft7uelHYfC1CvHP+z4RvQgXABpgT8YTdU+sgdMHrhHT1vxeUaDRkcQv9lV0IP6YtAcD+gk5jDQkXk4ruYztTUF3v4u8rlMuZ8kAKKWKw+JH6grLWD/vXjMv2RybxPqq3fKI6VJaj/Y/ZnDjD5HrQmJopnCbOrZrPysNb/rGrN3ad9ysaZwBvQtIE0/tQvmL+lsI+PfF9oGKeHkciIo0D4N2abOKT2fiazNm1U9LnrQih687ge0aeAlP2OO8c0h/nbEkMbNg83n1GGEt3DNojIWbT5uHaj12M6G81leS77mfLvSQ==");
 var u = Convert.FromBase64String("CNlUzgCf6Ymd/qeWiv3ScCIXYCwjP3SNLHxRgozIbNg2JEKpJn2M3vO72qLI+FT34xckaAGIcKWMkmpoaKy6PYF4jsAz2atLEClLimbMEPvpWxK7b/I5yvXMT7i2r5hr0OjjplL0wFQYL1IS2M8DTrL99rd9zXCoCWg5Tax6zQM=");

 var encryptedData = Convert.FromBase64String("CABt/Qp7ZODvweEk5RY9JNMXoyFfUwMnc53zbP5jB4jnwWXibLLvjc+Dv5CwQAtUYRme+vRd80++178BiWl0YSOKKhQaDQKoeOUONn3KbZVWyCtyWyQZNtASPoQfizay/Dw3yP5BKsJmDpEv47awdEZzh8IqTcTKeQbpHFL+3uL5EjIENpxMh15rJUsY9w+jq6Yax+379tq67EPMUON0aYkRQ3k1Rsp9fOL6qrgoqOPmOc0cIQgx76t6SFB9LmDySkyBhtK+vcEkdn9GwzZqc6n/Jqt9K8a+mbBv3K7eO3Pa37SDncsaxEzlyLwQ2om1+bK2QwauSQl+7QwQS1a9Ejb9");

 var rsa = new RSACryptoServiceProvider();

 // Throws exception saying "Bad data"
 rsa.ImportParameters(new RSAParameters
 {
      D = d,
      P = p,
      Q = q,
      InverseQ = u
  });

Дополнение от 2 февраля

Я копался в связанных ответах StackOverflow и достиг точки, когда я думаю, что определил, как сгенерировать отсутствующий компонент. Однако теперь я получаю исключение «Плохой ключ», которое ставит меня в тупик.

Я напишу код, который использую для генерации недостающих компонентов — возможно, вы где-нибудь заметите ошибку?

Я также рассчитал InverseQ и D вручную, и значения совпадают со значениями в моих входных данных. Ниже моя функция для генерации необходимых данных только на основе q, p и e.

private static RSAParameters CalculateRsaParameters(BigInteger p, BigInteger q, BigInteger e)
{
    var modulus = BigInteger.Multiply(p, q);

    var phi = BigInteger.Multiply(BigInteger.Subtract(p, BigInteger.One), BigInteger.Subtract(q, BigInteger.One));

    BigInteger x, y;
    // From http://www.codeproject.com/Articles/60108/BigInteger-Library
    // Returns 1 with my test data.
    ExtendedEuclidGcd(e, phi, out x, out y);

    var d = BigInteger.Remainder(x, phi);

    var dp = BigInteger.Remainder(d, BigInteger.Subtract(p, BigInteger.One));
    var dq = BigInteger.Remainder(d, BigInteger.Subtract(q, BigInteger.One));

    BigInteger x2, y2;
    // Returns 1 with my test data.
    ExtendedEuclidGcd(q, p, out x2, out y2);

    // y2 since it matched the pre-generated inverseQ data I had and x2 was some negative value, so it did not seem to fit. I have no idea what the logic behind which to pick really is.
    var qInverse = BigInteger.Remainder(y2, p);

    return new RSAParameters
    {
        D = ToBigEndianByteArray(d, 256),
        DP = ToBigEndianByteArray(dp, 128),
        DQ = ToBigEndianByteArray(dq, 128),
        InverseQ = ToBigEndianByteArray(qInverse, 128),
        Exponent = ToBigEndianByteArray(e, 1),
        Modulus = ToBigEndianByteArray(modulus, 256),
        P = ToBigEndianByteArray(p, 128),
        Q = ToBigEndianByteArray(q, 128)
    };
}

Мои входные данные:

e = 17
p = 148896287039501678969147386479458178246000691707699594019852371996225136011987881033904404601666619814302065310828663028471342954821076961960815187788626496609581811628527023262215778397482476920164511192915070597893567835708908996890192512834283979142025668876250608381744928577381330716218105191496818716653
q = 119975764355551220778509708561576785383941026741388506773912560292606151764383332427604710071170171329268379604135341015979284377183953677973647259809025842247294479469402755370769383988530082830904396657573472653613365794770434467132057189606171325505138499276437937752474437953713231209677228298628994462467

И вот как я использую сгенерированную структуру:

var rsa = new RSACryptoServiceProvider(2048);
rsa.ImportParameters(CalculateRsaParameters(p, q, e));

Вызов ImportParameters выдает исключение с сообщением «Плохой ключ». Что я делаю неправильно?

Что произойдет, если я поменяю местами Q и P?

Судя по всему, это заставляет RSACryptoServiceProvider принимать данные! Но что это означает?

Я получил эту идею от того, как мне пришлось использовать ExtendedEuclidGcd в моем коде генерации. Необходимость использовать разные выходные данные для двух экземпляров меня очень беспокоила, поэтому я провел этот эксперимент.

Одно дело, что u != qInverse - это правильно? Я не понимаю математику в исходной функции JavaScript, поэтому я не уверен, каковы последствия. Правильно ли я предполагаю, что значение u в оригинале на самом деле является каким-то внутренним ярлыком, а не QInverse?

Предстоит дальнейшее тестирование (т. е. фактическая расшифровка данных). Я отредактирую вопрос с любыми новыми разработками после того, как они будут сделаны.

С этим набором параметров расшифровка не удалась

У меня есть зашифрованные тестовые данные (в кодировке base64):

/TYSvVZLEAztfglJrgZDtrL5tYnaELzI5UzEGsudg7Tf2nM73q7cb7CZvsYrfasm/6lzajbDRn92JMG9vtKGgUxK8mAufVBIeqvvMQghHM055uOoKLiq+uJ8fcpGNXlDEYlpdONQzEPsutr2++3HGqarow/3GEsla16HTJw2BDIS+eLe/lIc6QZ5ysRNKsKHc0Z0sLbjL5EOZsIqQf7INzz8sjaLH4Q+EtA2GSRbcivIVpVtyn02DuV4qAINGhQqiiNhdGmJAb/Xvk/zXfT6nhlhVAtAsJC/g8+N77Js4mXB54gHY/5s851zJwNTXyGjF9MkPRblJOHB7+Bkewr9bQ==
or
bf0Ke2Tg78HhJOUWPSTTF6MhX1MDJ3Od82z+YweI58Fl4myy743Pg7+QsEALVGEZnvr0XfNPvte/AYlpdGEjiioUGg0CqHjlDjZ9ym2VVsgrclskGTbQEj6EH4s2svw8N8j+QSrCZg6RL+O2sHRGc4fCKk3EynkG6RxS/t7i+RIyBDacTIdeayVLGPcPo6umGsft+/bauuxDzFDjdGmJEUN5NUbKfXzi+qq4KKjj5jnNHCEIMe+rekhQfS5g8kpMgYbSvr3BJHZ/RsM2anOp/yarfSvGvpmwb9yu3jtz2t+0g53LGsRM5ci8ENqJtfmytkMGrkkJfu0MEEtWvRI2/Q==

Даны две альтернативы, поскольку я не уверен в порядке байтов. Это одни и те же данные в обеих строках.

Расшифровка обоих из них завершается сбоем, за исключением сообщения «Неверные данные» в первом случае и «Недостаточно памяти для обработки этой команды». во втором случае (что, по утверждению MSDN, может означать, что ключ не соответствует зашифрованным данным). Я сообщаю RSACryptoServiceProvider, что используется заполнение PKCS, хотя я также экспериментировал с OAEP (который только что выдал ошибку о невозможности декодировать заполнение).

Оригинальный JavaScript без проблем расшифровывает данные, хотя его "p" и "q" поменялись местами с моими.

Прямо сейчас мои вопросы:

  • Является ли переключение P и Q допустимой операцией?
  • Верны ли мои рассуждения или я где-то ошибся?
  • Что мне делать дальше, чтобы успешно расшифровать мои тестовые данные?

comment
Вам необходимо рассчитать остальные параметры. См.: stackoverflow.com/questions/14229040/   -  person CodesInChaos    schedule 29.01.2013
comment
Вам не хватает параметра 'e'. Вроде 17.   -  person President James K. Polk    schedule 30.01.2013
comment
@GregS Я думаю, вы хорошо выразились здесь: stackoverflow.com/questions/14192335/   -  person Maarten Bodewes    schedule 30.01.2013
comment
Спасибо, owlstead и @GregS — ваши комментарии помогли мне перейти к моменту, когда я могу сгенерировать недостающие данные. Однако теперь я получаю сообщение об ошибке Bad key, и я не знаю, что с этим делать. Мои сгенерированные данные, кажется, совпадают с моими входными данными, поэтому я в тупике. Соответствующий код добавлен к вопросу.   -  person Sander    schedule 02.02.2013
comment
Хорошо, я попытался расшифровать свои тестовые данные, поменяв местами Q и P (что заставляет RSACryptoServiceProvider принять мой набор параметров), но это приводит к сбою. Делает ли исходная реализация JavaScript что-то нестандартное, несовместимое с .NET RSACryptoServiceProvider? Или я что-то напутал?   -  person Sander    schedule 02.02.2013
comment
Наконец-то у меня в голове щелкнуло, насколько проста на самом деле RSA и что большинство этих параметров предназначены только для алгоритмической оптимизации. Игнорируя RSACryptoServiceProvider и используя простейший алгоритм расшифровки из Википедии (m**d mod n), мои тестовые данные расшифровываются нормально! Этого достаточно, чтобы сделать меня счастливым. Хотя любые комментарии, которые вы можете дать о возможной причине сбоя RSACryptoServiceProvider, вероятно, будут интересны, @GregS. Спасибо за помощь в любом случае!   -  person Sander    schedule 02.02.2013


Ответы (2)


RsaParameters имеет восемь полей. Я думаю, вам нужно инициализировать их все при создании закрытого ключа.

Взгляните на http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsaparameters.aspx

person Richard Schneider    schedule 29.01.2013
comment
Звучит разумно, хотя я надеюсь, что это не так, поскольку у меня нет всех этих данных. Существующая реализация JavaScript способна выполнять эту операцию дешифрования RSA только с 4 частями данных, которые я перечислил — это поведение, которое я хочу воспроизвести. - person Sander; 29.01.2013
comment
Смотрите мой комментарий, указывающий на ответ GregS. Скорее всего, вам придется предоставить все данные, но вычисление данных должно быть относительно простым, особенно если у вас уже есть частный показатель степени D. - person Maarten Bodewes; 30.01.2013

Существует ряд возможных ловушек, на которые можно наткнуться при использовании .NET BigIntegers и параметров RSA. Два, которые, вероятно, влияют на вас, — это порядок байтов и ведущее нулевое подавление.

Класс System.Numerics.BigInteger, доступный, начиная с .NET 4.0, использует формат с прямым порядком байтов для его метода ToByteArray() и конструктор new BigInteger(byte []).

К сожалению, структура RSAParameters ожидает массив байтов поля должны быть в обратном порядке. Существует также еще одна несовместимость, которую необходимо учитывать. System.Numerics.BigInteger может быть как положительным, так и отрицательным, и метод ToByteArray() учитывает это, используя вариант представления с дополнением до двух. Фактически это означает, что положительный BigInteger, чье представление массива байтов имеет старший байт >= 128, будет иметь дополнительный нулевой байт, помещенный в позицию самого высокого порядка. Однако все поля RSAParameter считаются положительными, поэтому начальный нуль необоснованно отклоняется с криптографическим исключением «Bad Data». Вы должны удалить эти ведущие нули там, где они встречаются.

Ниже приведен простой пример кода, показывающий эти операции:

    static BigInteger ExtGCD(BigInteger a, BigInteger b, out BigInteger lastx, out BigInteger lasty)
    {
        var x = BigInteger.Zero;
        lastx = BigInteger.One;
        var y = BigInteger.One;
        lasty = BigInteger.Zero;
        while (!b.IsZero)
        {
            BigInteger remainder;
            BigInteger q = BigInteger.DivRem(a, b, out remainder);
            a = b;
            b = remainder;
            var t = x;
            x = lastx - q * x;
            lastx = t;
            t = y;
            y = lasty - q * y;
            lasty = t;
        }

        return a;
    }

    static BigInteger inverse(BigInteger a, BigInteger n)
    {
        BigInteger d, x, y;
        d = ExtGCD(a, n, out x, out y);
        if (d.IsOne)
        {
            // Always return the least positive value
            return (x + n) % n;
        }
        else
        {
            throw new ArgumentException("the arguments must be relatively prime, i.e. their gcd must be 1");
        }
    }

    static byte[] ToByteArrayBE(BigInteger b)
    {
        var x = b.ToByteArray(); // x is little-endian
        Array.Reverse(x);        // now it is big-endian
        if (x[0] == 0)
        {
            var newarray = new byte[x.Length - 1];
            Array.Copy(x, 1, newarray, 0, newarray.Length);
            return newarray;
        } else
        {
            return x;
        }
    }
    static RSAParameters CalculateRsaParameters(BigInteger p, BigInteger q, BigInteger e)
    {
        // Given p, q, and e (the RSA encryption exponent) compute the remaining parameters

        var phi = (p - 1) * (q - 1);

        var d = inverse(e, phi);
        var dp = d % (p - 1);
        var dq = d % (q - 1);
        var qInv = inverse(q, p);

        var RsaParams = new RSAParameters
        {
            Modulus = ToByteArrayBE(p * q),
            Exponent = ToByteArrayBE(e),
            P = ToByteArrayBE(p),
            Q = ToByteArrayBE(q),
            D = ToByteArrayBE(d),
            DP = ToByteArrayBE(dp),
            DQ = ToByteArrayBE(dq),
            InverseQ = ToByteArrayBE(qInv)
        };
        return RsaParams;
    }

    static void Main(string[] args)
    {
        BigInteger p = BigInteger.Parse("148896287039501678969147386479458178246000691707699594019852371996225136011987881033904404601666619814302065310828663028471342954821076961960815187788626496609581811628527023262215778397482476920164511192915070597893567835708908996890192512834283979142025668876250608381744928577381330716218105191496818716653");
        BigInteger q = BigInteger.Parse("119975764355551220778509708561576785383941026741388506773912560292606151764383332427604710071170171329268379604135341015979284377183953677973647259809025842247294479469402755370769383988530082830904396657573472653613365794770434467132057189606171325505138499276437937752474437953713231209677228298628994462467");
        BigInteger e = new BigInteger(17);
        RSAParameters RsaParams = CalculateRsaParameters(p, q, e);
        var Rsa = new RSACryptoServiceProvider();
        Rsa.ImportParameters(RsaParams);
    }
}
person President James K. Polk    schedule 03.02.2013