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