Processing BigInteger issues - c#

Assume the following Diffie-Hellman info which can also be found on this page
1)P
string givenp = "00e655cc9e04f3bebae76ecca77143ef5c4451876615a9f8b4f712b8f3bdf47ee7f717c09bb5b2b66450831367d9dcf85f9f0528bcd5318fb1dab2f23ce77c48b6b7381eed13e80a14cca6b30b5e37ffe53db15e2d6b727a2efcee51893678d50e9a89166a359e574c4c3ca5e59fae79924fe6f186b36a2ebde9bf09fe4de50453";
BigInteger p = new BigInteger(HexToBytesv2(givenp));
2)G
BigInteger g = new BigInteger(2);
3)Merchant private key
string merchantPrivateKeyHEX = "48887dfd090d175e33beea29e7b38334299289069f9ab492b67807905faa98d96d22d79205bef03f14af093f1797b904734132c34a388fdc79e20497bfa1465fec2aac4fabdf3bb0c9be8685d20f7bfe0346a9abdf7fa89838c3fa9ca6abdb70bea66795ab6699cc154db59490e4159f142f7bddff603c1d3d6c4fff8177e11d";
BigInteger a = new BigInteger(HexToBytesv2(merchantPrivateKeyHEX));
Using the formula publickey = g ^ a mod p I should get the public key provided in the initial link, yet when executing
BigInteger A = BigInteger.ModPow(g, a, p);
ToHex(A.ToByteArray())
the result I get is
00f85c41e84446ecfe43c9911df31d3cf60d83642afd496b741363290139badf75f8b8c5c010dda2446dd483dc553b6c2698c16c9d082391677785f81d54bc9c7c45f8b6d5bdb3e49fec7f5522b880c8c753fb7d3ff2c81e47dcb27d52842def40a812dc95cc679575baf237a955ee9944bd0797326f2a0a58c6c087f9b0b9e82c
instead of
00d9abd78c93dfddeb920d57d6513126d8f1118c9237a45101408dbffe6cfd95b011a016e4e0ab8aef0601e836a452b8bb88be7ca71e4f22f97aa65f8358ee69348d1227d65db6e53641d1a6542aa4be4b4adc75fac816af79a8e3f5097f8313e7b725df37eadc8c774e2033dfa99c95ccef333bf402b066198c30481e2a83875c
Any ideas? I must be missing pretty obvious but I am not sure what that might be.
P.S. Adding the function being used:
public static byte[] HexToBytesv2(this string hex)
{
if (hex.Length % 2 == 1)
hex = '0' + hex;
byte[] ret = new byte[hex.Length / 2];
for (int i = 0; i < ret.Length; i++)
ret[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
return ret;
}
public static string ToHex(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}

It's an endian problem.
I've adjusted your encoding and decoding and now get the answer you're looking for:
public static byte[] HexToBytesv2(string hex)
{
if (hex.Length % 2 == 1)
hex = '0' + hex;
byte[] ret = new byte[hex.Length / 2];
for (int i = 0; i < ret.Length; i++)
ret[i] = Convert.ToByte(hex.Substring(hex.Length - (i+1) * 2, 2), 16);
return ret;
}
public static string ToHex( byte[] bytes)
{
var sb = new StringBuilder();
foreach (var b in bytes.Reverse())
{
sb.AppendFormat("{0:x2}", b);
}
return sb.ToString();
}
FYI I used LinqPad and the main method is your code from the question (as adjusted) with checks that the data has not lost anything on the way:
void Main()
{
string givenp = "00e655cc9e04f3bebae76ecca77143ef5c4451876615a9f8b4f712b8f3bdf47ee7f717c09bb5b2b66450831367d9dcf85f9f0528bcd5318fb1dab2f23ce77c48b6b7381eed13e80a14cca6b30b5e37ffe53db15e2d6b727a2efcee51893678d50e9a89166a359e574c4c3ca5e59fae79924fe6f186b36a2ebde9bf09fe4de50453";
BigInteger p = new BigInteger(HexToBytesv2(givenp));
(ToHex(p.ToByteArray()) == "00e655cc9e04f3bebae76ecca77143ef5c4451876615a9f8b4f712b8f3bdf47ee7f717c09bb5b2b66450831367d9dcf85f9f0528bcd5318fb1dab2f23ce77c48b6b7381eed13e80a14cca6b30b5e37ffe53db15e2d6b727a2efcee51893678d50e9a89166a359e574c4c3ca5e59fae79924fe6f186b36a2ebde9bf09fe4de50453").Dump();
BigInteger g = new BigInteger(2);
string merchantPrivateKeyHEX = "48887dfd090d175e33beea29e7b38334299289069f9ab492b67807905faa98d96d22d79205bef03f14af093f1797b904734132c34a388fdc79e20497bfa1465fec2aac4fabdf3bb0c9be8685d20f7bfe0346a9abdf7fa89838c3fa9ca6abdb70bea66795ab6699cc154db59490e4159f142f7bddff603c1d3d6c4fff8177e11d";
BigInteger a = new BigInteger(HexToBytesv2(merchantPrivateKeyHEX));
(ToHex(a.ToByteArray()) == "48887dfd090d175e33beea29e7b38334299289069f9ab492b67807905faa98d96d22d79205bef03f14af093f1797b904734132c34a388fdc79e20497bfa1465fec2aac4fabdf3bb0c9be8685d20f7bfe0346a9abdf7fa89838c3fa9ca6abdb70bea66795ab6699cc154db59490e4159f142f7bddff603c1d3d6c4fff8177e11d").Dump();
BigInteger A = BigInteger.ModPow(g, a, p);
(ToHex(A.ToByteArray()) == "00f85c41e84446ecfe43c9911df31d3cf60d83642afd496b741363290139badf75f8b8c5c010dda2446dd483dc553b6c2698c16c9d082391677785f81d54bc9c7c45f8b6d5bdb3e49fec7f5522b880c8c753fb7d3ff2c81e47dcb27d52842def40a812dc95cc679575baf237a955ee9944bd0797326f2a0a58c6c087f9b0b9e82c").Dump();
(ToHex(A.ToByteArray()) == "00d9abd78c93dfddeb920d57d6513126d8f1118c9237a45101408dbffe6cfd95b011a016e4e0ab8aef0601e836a452b8bb88be7ca71e4f22f97aa65f8358ee69348d1227d65db6e53641d1a6542aa4be4b4adc75fac816af79a8e3f5097f8313e7b725df37eadc8c774e2033dfa99c95ccef333bf402b066198c30481e2a83875c").Dump();
}
Before I swapped the ordering, and included the .Concat(new byte[] { 0 }).ToArray() from your original question, the output was:
True
True
True
False
And now it's:
True
True
False
True
The other issue you're seeing is BigInteger.Parse and the Byte[] constructor always expect the top bit of the first nibble or last byte respectively to be the sign bit. So you need to include the extra 0 character or byte respectively to avoid that.

You're doing a number of unnecessary conversions and they're introducing an error somewhere.
If you remove the broken string-byte[]-BigInteger-byte[]-string steps and let BigInteger itself do the work for you then you'll generate the expected result:
string givenp = "00e655cc9e04f3bebae76ecca77143ef5c4451876615a9f8b4f712b8f3bdf47ee7f717c09bb5b2b66450831367d9dcf85f9f0528bcd5318fb1dab2f23ce77c48b6b7381eed13e80a14cca6b30b5e37ffe53db15e2d6b727a2efcee51893678d50e9a89166a359e574c4c3ca5e59fae79924fe6f186b36a2ebde9bf09fe4de50453";
var p = BigInteger.Parse(givenp, NumberStyles.HexNumber);
var g = new BigInteger(2);
var merchantPrivateKeyHEX = "48887dfd090d175e33beea29e7b38334299289069f9ab492b67807905faa98d96d22d79205bef03f14af093f1797b904734132c34a388fdc79e20497bfa1465fec2aac4fabdf3bb0c9be8685d20f7bfe0346a9abdf7fa89838c3fa9ca6abdb70bea66795ab6699cc154db59490e4159f142f7bddff603c1d3d6c4fff8177e11d";
var a = BigInteger.Parse(merchantPrivateKeyHEX, NumberStyles.HexNumber);
var publicKey = BigInteger.ModPow(g, a, p);
Console.WriteLine(publicKey.ToString("x")); // displays 0d9abd7...

Related

Convert HEX to UTF-16 (GUID partition name) - WPF App (.NET Framework)

I have a string of hex that I want to convert to UTF-16L, as specified at https://en.wikipedia.org/wiki/GUID_Partition_Table under "Partition entries (LBA 2–33)". The string with hex has a fixed length of 72 bytes. I'm not sure what to do to convert it. I was thinking converting it to byte first then use Encoding.BigEndianUnicode Property.
Also when I tried to use Encoding.UTF8.GetChars then I got a lot of spaces in my result.
static void Main(string[] args)
{
string hexString = "4200610073006900630020006400610074006100200070006100720074006900740069006F006E000000000000000000000000000000000000000000000000000000000000000000";
int length = hexString.Length;
byte[] bytes = new byte[length / 2];
for (int i = 0; i < length; i += 2){
bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
}
char[] chars = Encoding.UTF8.GetChars(bytes);
string s = new string(chars);
Console.WriteLine(s);
}
Prints this:
B a s i c d a t a p a r t i t i o n
(B\0a\0s\0i\0c\0 \0d\0a\0t\0a\0 \0p\0a\0r\0t\0i\0t\0i\0o\0n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0)

Conversion of Hexadecimal to text [duplicate]

I need to check for a string located inside a packet that I receive as byte array. If I use BitConverter.ToString(), I get the bytes as string with dashes (f.e.: 00-50-25-40-A5-FF).
I tried most functions I found after a quick googling, but most of them have input parameter type string and if I call them with the string with dashes, It throws an exception.
I need a function that turns hex(as string or as byte) into the string that represents the hexadecimal value(f.e.: 0x31 = 1). If the input parameter is string, the function should recognize dashes(example "47-61-74-65-77-61-79-53-65-72-76-65-72"), because BitConverter doesn't convert correctly.
Like so?
static void Main()
{
byte[] data = FromHex("47-61-74-65-77-61-79-53-65-72-76-65-72");
string s = Encoding.ASCII.GetString(data); // GatewayServer
}
public static byte[] FromHex(string hex)
{
hex = hex.Replace("-", "");
byte[] raw = new byte[hex.Length / 2];
for (int i = 0; i < raw.Length; i++)
{
raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return raw;
}
For Unicode support:
public class HexadecimalEncoding
{
public static string ToHexString(string str)
{
var sb = new StringBuilder();
var bytes = Encoding.Unicode.GetBytes(str);
foreach (var t in bytes)
{
sb.Append(t.ToString("X2"));
}
return sb.ToString(); // returns: "48656C6C6F20776F726C64" for "Hello world"
}
public static string FromHexString(string hexString)
{
var bytes = new byte[hexString.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
return Encoding.Unicode.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64"
}
}
string str = "47-61-74-65-77-61-79-53-65-72-76-65-72";
string[] parts = str.Split('-');
foreach (string val in parts)
{
int x;
if (int.TryParse(val, out x))
{
Console.Write(string.Format("{0:x2} ", x);
}
}
Console.WriteLine();
You can split the string at the -
Convert the text to ints (int.TryParse)
Output the int as a hex string {0:x2}
string hexString = "8E2";
int num = Int32.Parse(hexString, System.Globalization.NumberStyles.HexNumber);
Console.WriteLine(num);
//Output: 2274
From https://msdn.microsoft.com/en-us/library/bb311038.aspx
Your reference to "0x31 = 1" makes me think you're actually trying to convert ASCII values to strings - in which case you should be using something like Encoding.ASCII.GetString(Byte[])
If you need the result as byte array, you should pass it directly without changing it to a string, then change it back to bytes.
In your example the (f.e.: 0x31 = 1) is the ASCII codes. In that case to convert a string (of hex values) to ASCII values use:
Encoding.ASCII.GetString(byte[])
byte[] data = new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30 };
string ascii=Encoding.ASCII.GetString(data);
Console.WriteLine(ascii);
The console will display: 1234567890
My Net 5 solution that also handles null characters at the end:
hex = ConvertFromHex( hex.AsSpan(), Encoding.Default );
static string ConvertFromHex( ReadOnlySpan<char> hexString, Encoding encoding )
{
int realLength = 0;
for ( int i = hexString.Length - 2; i >= 0; i -= 2 )
{
byte b = byte.Parse( hexString.Slice( i, 2 ), NumberStyles.HexNumber, CultureInfo.InvariantCulture );
if ( b != 0 ) //not NULL character
{
realLength = i + 2;
break;
}
}
var bytes = new byte[realLength / 2];
for ( var i = 0; i < bytes.Length; i++ )
{
bytes[i] = byte.Parse( hexString.Slice( i * 2, 2 ), NumberStyles.HexNumber, CultureInfo.InvariantCulture );
}
return encoding.GetString( bytes );
}
One-liners:
var input = "Hallo Hélène and Mr. Hörst";
var ConvertStringToHexString = (string input) => String.Join("", Encoding.UTF8.GetBytes(input).Select(b => $"{b:X2}"));
var ConvertHexToString = (string hexInput) => Encoding.UTF8.GetString(Enumerable.Range(0, hexInput.Length / 2).Select(_ => Convert.ToByte(hexInput.Substring(_ * 2, 2), 16)).ToArray());
Assert.AreEqual(input, ConvertHexToString(ConvertStringToHexString(input)));

RSAParameters calculating private parameters

I am trying to calculate the private parameters of the RSAParameters struct on .NET Standard. I made a unit test to test my calculations:
[TestMethod]
public void DQDPTest()
{
RSA rsa = RSA.Create();
RSAParameters rsaParams = rsa.ExportParameters(true);
BigInteger p = new BigInteger(rsaParams.P.Reverse().ToArray());
BigInteger q = new BigInteger(rsaParams.Q.Reverse().ToArray());
BigInteger d = new BigInteger(rsaParams.D.Reverse().ToArray());
BigInteger dq = new BigInteger(rsaParams.DQ.Reverse().ToArray());
BigInteger dp = new BigInteger(rsaParams.DP.Reverse().ToArray());
Assert.AreEqual(dq, d % (q - 1));
Assert.AreEqual(dp, d % (p - 1));
}
However, the assertions consistently fail and I cannot figure out why, since DQ and DP are supposed to contain those values. Why is this happening?
I have a similar method for calculating InverseQ, and this does not work either:
[TestMethod]
public void ModInverseTest()
{
RSA rsa = RSA.Create();
RSAParameters rsaParams = rsa.ExportParameters(true);
BigInteger p = new BigInteger(rsaParams.P.Reverse().ToArray());
BigInteger q = new BigInteger(rsaParams.Q.Reverse().ToArray());
BigInteger iq = new BigInteger(rsaParams.InverseQ.Reverse().ToArray());
BigInteger ciq = Extensions.ModInverse(q, p);
Assert.AreEqual(1, (iq * q) % p);
Assert.AreEqual(1, (ciq * q) % p);
Assert.AreEqual(iq, ciq);
}
public static BigInteger ModInverse(BigInteger a, BigInteger n)
{
BigInteger t = 0, nt = 1, r = n, nr = a;
if (n < 0)
{
n = -n;
}
if (a < 0)
{
a = n - (-a % n);
}
while (nr != 0)
{
var quot = r / nr;
var tmp = nt; nt = t - quot * nt; t = tmp;
tmp = nr; nr = r - quot * nr; r = tmp;
}
if (r > 1) throw new ArgumentException(nameof(a) + " is not convertible.");
if (t < 0) t = t + n;
return t;
}
The assertions in ModInverseTest() also consistently fail, which means either I am doing something incorrectly, or these values are simply not what I think they are. Again, why is this happening?
Your values for P and Q are almost certainly negative, which is likely throwing off everything else. This is because the C# BigInteger constructor doesn't allow you to specify positive/negative, and so a most significant byte with the most sigificant bit set means it's a negative number. The solution is to add a padding byte (0x00) which keeps the sign bit clear.
private static System.Numerics.BigInteger GetBigInteger(byte[] parameter)
{
byte[] signPadded = new byte[parameter.Length + 1];
Buffer.BlockCopy(parameter, 0, signPadded, 1, parameter.Length);
Array.Reverse(signPadded);
return new System.Numerics.BigInteger(signPadded);
}

Simple Byte Encryption Not Working

When the message is decrypted, the characters are one less than the original. Example: H Will be G
I have tried to debug the code by printing out values and all goes well until trying to divide by 100000 and multiplying by the date
Here is the code I used:
I didn't include the Main Method Here
public static string encrypt(string input)
{
string final;
string date = DateTime.Now.Date.ToShortDateString().ToString();
var datetime = int.Parse(date.Replace("/", ""));
List<int> semi = new List<int>();
var bytes = Encoding.UTF8.GetBytes(input.ToCharArray());
for (int i = 0; i < bytes.Length; i++)
{
int y = bytes[i] * datetime / 100000;
semi.Add(y);
Console.WriteLine(y);
}
Console.WriteLine(string.Join("", bytes));
final = string.Join(":", semi.ToArray()) + ":" + date;
return final;
}
public static string decrypt(string input)
{
string final;
string[] raw = input.Split(':');
int date = int.Parse(raw[raw.Length - 1].Replace("/",""));
var dump = new List<string>(raw);
dump.RemoveAt(raw.Length - 1);
string[] stringbytes = dump.ToArray();
List<byte> bytes = new List<byte>();
for (int i = 0; i < stringbytes.Length; i++)
{
int x = int.Parse(stringbytes[i]);
Console.WriteLine(x);
x = x * 100000 / date;
byte finalbytes = Convert.ToByte(x);
bytes.Add(finalbytes);
}
Console.WriteLine(string.Join("", bytes.ToArray()));
Console.WriteLine(date);
var bytearray = bytes.ToArray();
final = Encoding.UTF8.GetString(bytearray);
return final;
}
It's likely a rounding error from integer division. when doing integer math it is very possible that ((x * date / 100000) * 100000 / date) != x, in fact the only time it will be == x is when date % 100000 == 0.
Fix the rounding errors introduced by your int division and it should fix your problem.
P.S. I would also be very hesitant to call this "encryption", there is no secret key, all the information required to decrpt the message is in the message itself. You are only relying on the fact that the algorithm is secret which is practically impossible to do with C#. I would rather call what you are doing "Encoding", because to decode something that is encoding all you need to know is the algorithm.
You are using the low precision datatype int to store the result of the division. I have changed the type to double and it works
public static string encrypt(string input)
{
string final;
string date = DateTime.Now.Date.ToString("MMddyyyy");
var datetime = int.Parse(date);
List<double> semi = new List<double>();
var bytes = Encoding.UTF8.GetBytes(input);
for (int i = 0; i < bytes.Length; i++)
{
double y = bytes[i] * datetime / 100000;
semi.Add(y);
Console.WriteLine(y);
}
Console.WriteLine(string.Join("", bytes));
final = string.Join(":", semi.ToArray()) + ":" + date;
return final;
}
public static string decrypt(string input)
{
string final;
string[] raw = input.Split(':');
int date = int.Parse(raw[raw.Length - 1].Replace("/", ""));
var dump = new List<string>(raw);
dump.RemoveAt(raw.Length - 1);
string[] stringbytes = dump.ToArray();
List<byte> bytes = new List<byte>();
for (int i = 0; i < stringbytes.Length; i++)
{
var x = double.Parse(stringbytes[i]);
Console.WriteLine(x);
x = x * 100000 / date;
byte finalbytes = Convert.ToByte(x);
bytes.Add(finalbytes);
}
Console.WriteLine(string.Join("", bytes.ToArray()));
Console.WriteLine(date);
var bytearray = bytes.ToArray();
final = Encoding.UTF8.GetString(bytearray);
return final;
}
Here is a fully working console app http://ideone.com/Rjc13A
I believe this is a number truncation issue. In your decrypt method, the division will actually create a double instead of an int - If you do the math it turns out to have decimal places. Since x is an integer it will be cutt-off.
The following should work:
for (int i = 0; i < stringbytes.Length; i++)
{
var x = double.Parse(stringbytes[i]);
Console.WriteLine(x);
x = Math.Round((x * 100000) / date,0);
byte finalbytes = Convert.ToByte(x);
bytes.Add(finalbytes);
}
Also, as a side note why are you creating your own encryption algorithm? Could you not use one that already exists?

How to create private RSA key using modulus, D, exponent in C#?

I have 3 byte arrays of length 128, 128, 3 bytes respectively. I don't know what it is, but I expect them to be Modulus, D, Exponent.
Now how can I use these arrays in C# to decrypt a byte array using RSA?
When I create an RSAParameters and assign the 3 byte arrays to Modulus, D, Exponent and try to use that RSAParameters in RSACryptoServiceProvider.ImportParameters, decryption fails stating corrupt keys. I guess the other entries also need to be filled DQ,DP,...etc...
How do I do that in C#? I don't have that values, is there an easy way to decrypt a byte array using only Modulus, D, Exponent in C#, as in other languages?
The Windows implementations seem to only be willing to do RSA via the CRT parameters, leaving D as a potentially ignored value. At the very least, the CRT parameters are required inputs.
First, we need to turn your arrays into BigInteger values. I'm assuming here that you have Big-Endian encoded values. If they're Little-Endian, don't call Array.Reverse() and change the copy-to index from 1 to 0.
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);
}
Adding the extra byte prevents numbers from being treated as negative. (One could avoid the allocation and memory copy by testing for the sign bit in the last byte, if one wanted).
So now you have three BigInteger values, n, e, d. Not sure which of n and d is which?
// Unless someone tried really hard to make this break it'll work.
if (n < d)
{
BigInteger tmp = n;
n = d;
d = tmp;
}
Now, using the algorithm from NIST Special Publication 800-56B Recommendation for Pair-Wise August 2009 Key Establishment Schemes Using Integer Factorization Cryptography, Appendix C (as shared in https://stackoverflow.com/a/28299742/6535399) we can calculate the BigInteger values. There's a tricky subtlety, though. RSAParameters values have to have a correct amount of padding, and RSACryptoServiceProvider doesn't do it for you.
private static RSAParameters RecoverRSAParameters(BigInteger n, BigInteger e, BigInteger d)
{
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
BigInteger k = d * e - 1;
if (!k.IsEven)
{
throw new InvalidOperationException("d*e - 1 is odd");
}
BigInteger two = 2;
BigInteger t = BigInteger.One;
BigInteger r = k / two;
while (r.IsEven)
{
t++;
r /= two;
}
byte[] rndBuf = n.ToByteArray();
if (rndBuf[rndBuf.Length - 1] == 0)
{
rndBuf = new byte[rndBuf.Length - 1];
}
BigInteger nMinusOne = n - BigInteger.One;
bool cracked = false;
BigInteger y = BigInteger.Zero;
for (int i = 0; i < 100 && !cracked; i++)
{
BigInteger g;
do
{
rng.GetBytes(rndBuf);
g = GetBigInteger(rndBuf);
}
while (g >= n);
y = BigInteger.ModPow(g, r, n);
if (y.IsOne || y == nMinusOne)
{
i--;
continue;
}
for (BigInteger j = BigInteger.One; j < t; j++)
{
BigInteger x = BigInteger.ModPow(y, two, n);
if (x.IsOne)
{
cracked = true;
break;
}
if (x == nMinusOne)
{
break;
}
y = x;
}
}
if (!cracked)
{
throw new InvalidOperationException("Prime factors not found");
}
BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n);
BigInteger q = n / p;
BigInteger dp = d % (p - BigInteger.One);
BigInteger dq = d % (q - BigInteger.One);
BigInteger inverseQ = ModInverse(q, p);
int modLen = rndBuf.Length;
int halfModLen = (modLen + 1) / 2;
return new RSAParameters
{
Modulus = GetBytes(n, modLen),
Exponent = GetBytes(e, -1),
D = GetBytes(d, modLen),
P = GetBytes(p, halfModLen),
Q = GetBytes(q, halfModLen),
DP = GetBytes(dp, halfModLen),
DQ = GetBytes(dq, halfModLen),
InverseQ = GetBytes(inverseQ, halfModLen),
};
}
}
With the "tricky" BigInteger-to-suitable-for-RSAParameters-byte[] method:
private static byte[] GetBytes(BigInteger value, int size)
{
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;
}
And for computing InverseQ you need ModInverse:
private static BigInteger ModInverse(BigInteger e, BigInteger n)
{
BigInteger r = n;
BigInteger newR = e;
BigInteger t = 0;
BigInteger newT = 1;
while (newR != 0)
{
BigInteger quotient = r / newR;
BigInteger temp;
temp = t;
t = newT;
newT = temp - quotient * newT;
temp = r;
r = newR;
newR = temp - quotient * newR;
}
if (t < 0)
{
t = t + n;
}
return t;
}
On my computer I'm recovering P and Q from (n, e, d) in ~50ms for a 1024-bit key. ~2-4 seconds for a 4096-bit key.
Note to implementers who like unit tests: There's not really a defined order for P and Q (like a convention that P always be the larger), so your P and Q values may be backwards from an RSAParameters structure that you started with. DP and DQ will thus also be reversed.
You don't have enough when you just have Mod, D, and the exponent. (Well you might have enough) P and Q are VERY hard to calculate from the mod. I wouldn't know how to do that and there are almost certainly more primes than the right ones that multiplied end up with the same mod.
You need atleast P, Q and the public exponent.
P, Q and D are the building blocks
DP = D mod (p - 1)
DQ = D mod (q - 1)
InverseQ = Q^-1 mod p
Modulus = P * Q
so now we have
P Q and D.
and we can calulate DP, DQ, InverseQ and Modulus and Exponent (see below)
long gcd(long a, long b)
{
long temp;
while (b != 0)
{
temp = b;
b = a % b;
a = temp;
}
return a;
}
Exponent = gcd(1, (P - 1)*(Q - 1));

Categories

Resources