Trauer
Trauer

Reputation: 2091

How to encrypt and decrypt a string with manually set RSA parameters? Why RSACryptoServiceProvider is throwing?

I'm trying to manually set the properties of a RSAParameters, but I'm getting all sorts of errors when I try to encrypt, decrypt or even instantiate the RSACryptoServiceProvider.

If I set only the Exponent and the Modulus, I'm able to encrypt a message; but I when I try to decrypt it I get a Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'Key does not exist

If I set all the properties, I get a System.Security.Cryptography.CryptographicException: 'The specified RSA parameters are not valid; both Exponent and Modulus are required fields.'

My question is why? What's wrong with my parameters?

I'm obviously not using random values for the fields; I copy-pasted their string representations from another project.

Below is the code I wrote, it's targeting .Net Core 2.1. I updated it to include this length restrictions

namespace PlayingWithCryptography {
    using System;
    using System.Linq;
    using System.Numerics;
    using System.Security.Cryptography;
    using System.Text;

    public static class Program {
        private static void Main(string[] args) {
            var msg = "lol";
            var encodedMessage = Encoding.ASCII.GetBytes(msg);

            var parameters = new RSAParameters();

            parameters.P = BigInteger.Parse("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113").ToByteArray();

            parameters.Q = BigInteger.Parse("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101").ToByteArray();

            parameters.D = BigInteger.Parse("46730330223584118622160180015036832148732986808519344675210555262940258739805766860224610646919605860206328024326703361630109888417839241959507572247284807035235569619173792292786907845791904955103601652822519121908367187885509270025388641700821735345222087940578381210879116823013776808975766851829020659073").ToByteArray();

            parameters.Modulus = BigInteger.Parse("109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413").ToByteArray();

            parameters.Exponent = BigInteger.Parse("65537").ToByteArray();

            parameters.DP = BigInteger.Parse("11141736698610418925078406669215087697114858422461871124661098818361832856659225315773346115219673296375487744032858798960485665997181641221483584094519937").ToByteArray();

            parameters.DQ = BigInteger.Parse("4886309137722172729208909250386672706991365415741885286554321031904881408516947737562153523770981322408725111241551398797744838697461929408240938369297973").ToByteArray();

            parameters.InverseQ = BigInteger.Parse("5610960212328996596431206032772162188356793727360507633581722789998709372832546447914318965787194031968482458122348411654607397146261039733584248408719418").ToByteArray();

            Console.WriteLine($"M[0]={parameters.Modulus[0]}");
            Console.WriteLine($"D.Length={parameters.D.Length}");
            Console.WriteLine($"M.Length={parameters.Modulus.Length}");
            Console.WriteLine($"E={parameters.Exponent[0]}");
            Console.WriteLine($"P.Length={ parameters.P.Length}");
            Console.WriteLine($"Q.Length={ parameters.Q.Length}");
            Console.WriteLine($"DP.Length={ parameters.DP.Length}");
            Console.WriteLine($"DQ.Length={ parameters.DQ.Length}");
            Console.WriteLine($"InverseQ.Length={ parameters.InverseQ.Length}");

            // Adding zeros coz https://stackoverflow.com/questions/42098493/decrypting-with-rsa-encryption-in-vb-net/42117655#42117655
            parameters.D = new byte[] { 0 }.Concat(parameters.D).ToArray();
            parameters.DQ = new byte[] { 0 }.Concat(parameters.DQ).ToArray();
            parameters.InverseQ = new byte[] { 0 }.Concat(parameters.InverseQ).ToArray();
            Console.WriteLine();

            Console.WriteLine($"M[0]={parameters.Modulus[0]}");
            Console.WriteLine($"D.Length={parameters.D.Length}");
            Console.WriteLine($"M.Length={parameters.Modulus.Length}");
            Console.WriteLine($"E[0]={parameters.Exponent[0]}");
            Console.WriteLine($"P.Length={ parameters.P.Length}");
            Console.WriteLine($"Q.Length={ parameters.Q.Length}");
            Console.WriteLine($"DP.Length={ parameters.DP.Length}");
            Console.WriteLine($"DQ.Length={ parameters.DQ.Length}");
            Console.WriteLine($"InverseQ.Length={ parameters.InverseQ.Length}");

            var csp = RSACryptoServiceProvider.Create();

            csp.ImportParameters(parameters);

            var encrypted = csp.Encrypt(
                  data: encodedMessage,
                  padding: RSAEncryptionPadding.OaepSHA256);
            var decrypted = csp.Decrypt(
                  data: encrypted,
                  padding: RSAEncryptionPadding.OaepSHA256);

            var decoded = Encoding.ASCII.GetString(decrypted);
            Console.WriteLine(decoded);
            Console.WriteLine("Done!");
        }

    }
}

Answered To anyone in a similar situation, please read (and vote) both James K Polk's answers and then the answer for this question

Upvotes: 0

Views: 4224

Answers (2)

President James K. Polk
President James K. Polk

Reputation: 41967

I'm going to leave my other answer up for now, but I have reason to doubt the restrictions quoted therein. For the parameters in the question, my other answer suggests that RSAParameters.P must be exactly 64 bytes. However that can't possibly work, as the P in the question requires at least 65 bytes to represent.

Instead, I'll suggest you use the RSA.FromXMLString() method to import RSA public and private keys. That seems to be based on an external standard called XML Signature Syntax and Processing Version 1.1, and in particular this section with further details here. The basic idea is to encode the integers as big-endian byte arrays of minimal length -- no leading zeros -- and then base64-encode that.

Here is some lightly tested code to accomplish it. Note that C# and .NET are not my strong suit, so feel free to improve this. Note that, on my platform, OAEPSha1 was the only supported OAEP padding.

using System;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;

namespace PlayingWithCryptography
{
    public static class ConvertToRSAParameters
    {

        public static string ConvertXML(BigInteger e, BigInteger n)
        {
            var xml = new StringBuilder();
            xml.AppendLine(WrapTags(BigToBase64(n), "Modulus"));
            xml.AppendLine(WrapTags(BigToBase64(e), "Exponent"));
            WrapTags(xml, "RSAKeyValue");
            return xml.ToString();
        }

        public static string ConvertXML(BigInteger e, BigInteger n, BigInteger p, BigInteger q,
                                            BigInteger d, BigInteger dp, BigInteger dq, BigInteger inverseQ)
        {
            var xml = new StringBuilder();
            xml.AppendLine(WrapTags(BigToBase64(n), "Modulus"));
            xml.AppendLine(WrapTags(BigToBase64(e), "Exponent"));
            xml.AppendLine(WrapTags(BigToBase64(p), "P"));
            xml.AppendLine(WrapTags(BigToBase64(q), "Q"));
            xml.AppendLine(WrapTags(BigToBase64(d), "D"));
            xml.AppendLine(WrapTags(BigToBase64(dp), "DP"));
            xml.AppendLine(WrapTags(BigToBase64(dq), "DQ"));
            xml.AppendLine(WrapTags(BigToBase64(inverseQ), "InverseQ"));
            WrapTags(xml, "RSAKeyValue");
            return xml.ToString();

        }

        private static string BigToBase64(BigInteger val)
        {
            var valBytes = val.ToByteArray();
            int len = valBytes.Length;
            while (valBytes[len - 1] == 0)
            {
                --len;
                if (len == 0)
                {
                    break;
                }
            }
            Array.Resize(ref valBytes, len);
            Array.Reverse(valBytes);
            return System.Convert.ToBase64String(valBytes);
        }

        private static string WrapTags(string target, string tag)
        {
            return String.Format("<{0}>{1}</{0}>", tag, target);
        }

        private static StringBuilder WrapTags(StringBuilder target, string tag)
        {
            return target.Insert(0, String.Format("<{0}>", tag)).AppendFormat("</{0}>", tag);
        }

        private static void Main(string[] args)
        {
            var msg = "lol";
            var encodedMessage = Encoding.ASCII.GetBytes(msg);

            Console.WriteLine();

            var publicRsa = RSA.Create();

            publicRsa.FromXmlString(
                ConvertToRSAParameters.ConvertXML(
                    BigInteger.Parse("65537"),
                    BigInteger.Parse("109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413")
                )
            );

            var privateRsa = RSA.Create();

            privateRsa.FromXmlString(
                ConvertToRSAParameters.ConvertXML(
                    BigInteger.Parse("65537"),
                    BigInteger.Parse("109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413"),
                    BigInteger.Parse("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113"),
                    BigInteger.Parse("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101"),
                    BigInteger.Parse("46730330223584118622160180015036832148732986808519344675210555262940258739805766860224610646919605860206328024326703361630109888417839241959507572247284807035235569619173792292786907845791904955103601652822519121908367187885509270025388641700821735345222087940578381210879116823013776808975766851829020659073"),
                    BigInteger.Parse("11141736698610418925078406669215087697114858422461871124661098818361832856659225315773346115219673296375487744032858798960485665997181641221483584094519937"),
                    BigInteger.Parse("4886309137722172729208909250386672706991365415741885286554321031904881408516947737562153523770981322408725111241551398797744838697461929408240938369297973"),
                    BigInteger.Parse("5610960212328996596431206032772162188356793727360507633581722789998709372832546447914318965787194031968482458122348411654607397146261039733584248408719418")
                )
            );

            var encrypted = publicRsa.Encrypt(
                  data: encodedMessage,
                  padding: RSAEncryptionPadding.OaepSHA1);
            var decrypted = privateRsa.Decrypt(
                  data: encrypted,
                  padding: RSAEncryptionPadding.OaepSHA1);

            var decoded = Encoding.ASCII.GetString(decrypted);
           Console.WriteLine(decoded);
            Console.WriteLine("Done!");
        }
    }
}

Upvotes: 2

President James K. Polk
President James K. Polk

Reputation: 41967

The details of what the fields of RSAParameters must look like are given at the bottom of the page in the Feedback section by user bartonjs (who is also a stackoverflow user of the same name). Here is a copy of his comment:

The full rules of the arrays for RSAParameters values are as follows:

  • Modulus: The unsigned big-endian representation of the RSA modulus value. Modulus[0] must not have the value 0x00.
  • Exponent: The unsigned big-endian representation of the RSA public exponent value. Exponent[0] must not have the value 0x00.

The other fields must either all be null (public key parameters), or all non-null (private key parameters). When the values are non-null:

  • D: The unsigned big-endian representation of the RSA private exponent value. This array must be equal in length to the length of the Modulus value, inserting 0x00-value bytes at low indexes as necessary.
  • P: The unsigned big-endian representation of the RSA prime p. This array must be equal in length to half of the length of the Modulus value (round up if necessary), inserting 0x00-value bytes at low indexes as necessary.
  • Q: The unsigned big-endian representation of the RSA prime q. This array must be equal in length to half of the length of the Modulus value (round up if necessary), inserting 0x00-value bytes at low indexes as necessary. DP: The unsigned big-endian representation of the RSA CRT parameter dp. This array must be equal in length to half of the length of the Modulus value (round up if necessary), inserting 0x00-value bytes at low indexes as necessary.
  • DQ: The unsigned big-endian representation of the RSA CRT parameter dq. This array must be equal in length to half of the length of the Modulus value (round up if necessary), inserting 0x00-value bytes at low indexes as necessary.
  • InverseQ: The unsigned big-endian representation of the RSA CRT parameter qInverse. This array must be equal in length to half of the length of the Modulus value (round up if necessary), inserting 0x00-value bytes at low indexes as necessary.

Example: For a RSA key with a KeySize value of 2056 and having the standard public exponent value 0x010001, the array lengths are:

Public Key:

  • Modulus: 257 bytes
  • Exponent: 3 bytes
  • D, P, Q, DP, DQ, InverseQ: null values.

Private Key:

  • Modulus: 257 bytes
  • Exponent: 3 bytes
  • D: 257 bytes
  • P: 129 bytes
  • Q: 129 bytes
  • DP: 129 bytes
  • DQ: 129 bytes
  • InverseQ: 129 bytes

Note that BigInteger.ToByteArray() returns the little-endian representation. This must be reversed to get the big-endian representation required by RSAParameters.

Upvotes: 3

Related Questions