Home > Software design >  c# What is the right way to get an RSA key with public exponent 3 from System.Security.Cryptography?
c# What is the right way to get an RSA key with public exponent 3 from System.Security.Cryptography?

Time:03-19

I have a c# program using System.Security.Cryptography (standard provider) that needs to generate RSA keys of a particular bit size and exponent to interface with another long standing system. This code seems reasonable to me:

            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;
                    }
                }
            }

Most of the time but not always, I get a Bad Parameter exception on rsa2.ImportParameters with the 3 exponent. Sometimes it works, and I have had runs where rsa2.ToXmlString shows an Exponent of 3:

<Exponent>Aw==</Exponent>

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

The test loop sometimes fails with nonzero trix, so it works a little. See the screenshot and screenshot of exception

CodePudding user response:

After changing the public exponent, the remaining dependent components (namely D, DP, DQ) of the key must also be recalculated. You can archive this with the following code:

public static void Main()
{
    using (var rsa = new RSACryptoServiceProvider(1024))
    {
        RSAParameters key = rsa.ExportParameters(true);
        UpdatePublicExponent(ref key, 3);
        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));
    }
}

Here is UpdatePublicExponent with the other helper functions:

private static void UpdatePublicExponent(ref RSAParameters key, BigInteger e)
{
    BigInteger p = GetBigInteger(key.P);
    BigInteger q = GetBigInteger(key.Q);

    BigInteger gcd = BigInteger.GreatestCommonDivisor(p - 1, q - 1);
    BigInteger lcm = (p - 1) /  gcd * (q - 1);

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

    // calculate the CRT factors
    BigInteger dP = d % (p - 1);
    BigInteger dQ = d % (q - 1);
    
    int len = key.Modulus.Length;
    int halfLen = (len   1) / 2;

    // update key components
    key.Exponent = GetBytes(e, -1);
    key.D = GetBytes(d, len);
    key.DP = GetBytes(dP, halfLen);
    key.DQ = GetBytes(dQ, halfLen);
}

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 GetBigInteger(byte[] bytes)
{
    byte[] signPadded = new byte[bytes.Length   1];
    Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length);
    Array.Reverse(signPadded);
    return new BigInteger(signPadded);
}

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;
}
  • Related