How to decrypt RSA content in .NET? - c#

I am generating RSA private & public keys using OpenSSL, sending public key into HTML page to encrypt username & password (with JSEncrypt).The encrypted content sends to server to decrypt using private key.
This is my decrypt function:
public string RsaDecrypt(string xmlPrivateKey, string mStrDecryptString)
{
string str2;
try
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(xmlPrivateKey);
byte[] rgb = Convert.FromBase64String(mStrDecryptString);
byte[] buffer2 = provider.Decrypt(rgb, false);
str2 = new UnicodeEncoding().GetString(buffer2);
}
catch (Exception exception)
{
throw exception;
}
return str2;
}
The xmlPrivateKey value is:
MIICWwIBAAKBgQCt8y+vx9Y3Iik9l/8r6x+wjcrgPskbjVpt7fSJqtpCA/XaYl/3O2uvrRUPzyqr1wA+ejsdhdm285nYSbSaHTPem1+N/JHynp+cLQiBV6a8PayOvtrSBaHLZDDhgvntk/BLeplU406kiMltnVDko33H+Y3yaNuY2TNDEMe5Z8OlUQIDAQABAoGAdYIChMyKeVQqZ+F2D0UWcz5V/oZrdKFYpUpKF3XDWzUxsAUkru8FH/fccoGQYeUr1QjdRmRVXrHRC7s+tZ1km68oiUFD6sbCYyPQy0Se95050FncM3lEndGUJTiTelVqAYh+DPVnRURcfgA+HSvWek1/YnOZ8UNZJ36jiogSKcECQQDbRfn/UODXud7MKO7zfYOLvPhtFMgtA0Ac5w6tTJ/llZs0QtjMKCNHF9bGRxKdFvKTMA1DGBNN0chdWAc7UET/AkEAyxXUJAk1+46fRhzTH4uXRX7SEMCwEjY79DHqE23pPx8Q8VC3j2aPETQerT4EHNzaMBg6hneJE2p7xB5Rm/SFrwJAIWasaT7psRLIJHNLyt1gr2WOthcHUwv+tShhLPbSGIfMh45zNc4baZXxCm0DIdjABLm6G3FMZ3tAOS/Ski9tAwJAMYWQJn1sgXwk0KcEwIN8jsC/HsCt7rL06bYmOzipEPBVZFLnf/tlVa+c72fY/uTH+8RcuR96+JYVuhwekGYPFwJAQXbsOkyVTvZGcqRk9+SF7AUsGcHYPrImH6iafYEBsVCOrMJfjEai0zmd/9A1j+NHFq31KPAQGV0zHmV2NXscDg==
The mStrDecryptString is:
fW9H+/Nz/yp6my/EwY0I+KP1CX/QPY8TL3bFDvfJYJDJ50LHEPfiR/RGhHl9rvViXOgD4IiXYF2/KbNPQNmno+Bioi3r8Xc5+PVNyFDJy+X4/YjX4O830g9vAhyRJ1RKbJOmJYWT4sdP0jfxwaRL2+FAl6yIsrcsH/7bRZvjDTU=
When decrypting at server side, the error is:
Invalid grammar in line 1.
How could I do to make it right?

The RSA format generated by OpenSSL is different with .NET, you should convert OpenSSL RSA xmlPrivateKey to XML format so it could be recognized by RSACryptoServiceProvider.
private static RSACryptoServiceProvider DecodeRsaPrivateKey(string priKey)
{
var privkey = Convert.FromBase64String(priKey);
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
var mem = new MemoryStream(privkey);
var binr = new BinaryReader(mem); // wrap Memory Stream with BinaryReader for easy reading
try
{
var twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) // data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); // advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); // advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) // version number
return null;
var bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
var elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
var rsa = new RSACryptoServiceProvider();
var rsAparams = new RSAParameters
{
Modulus = MODULUS,
Exponent = E,
D = D,
P = P,
Q = Q,
DP = DP,
DQ = DQ,
InverseQ = IQ
};
rsa.ImportParameters(rsAparams);
return rsa;
}
catch (Exception e)
{
LogHelper.Logger.Error("DecodeRsaPrivateKey failed", e);
return null;
}
finally
{
binr.Close();
}
}
private static int GetIntegerSize(BinaryReader binary)
{
byte binaryReadByte = 0;
var count = 0;
binaryReadByte = binary.ReadByte();
if (binaryReadByte != 0x02) // expect integer
return 0;
binaryReadByte = binary.ReadByte();
if (binaryReadByte == 0x81)
{
count = binary.ReadByte(); // data size in next byte
}
else
{
if (binaryReadByte == 0x82)
{
var highbyte = binary.ReadByte();
var lowbyte = binary.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = binaryReadByte; // we already have the data size
}
}
while (binary.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binary.BaseStream.Seek(-1, SeekOrigin.Current); // last ReadByte wasn't a removed zero, so back up a byte
return count;
}
So the RSACryptoServiceProvider can decrypt the original context.

Related

C# Extract public key from RSA PEM private key

I have a private key in PEM format. How can I extract the public key from it. I need to be able to do this in code, pragmatically, without executing a process (using openssl).
Sample unused private key:
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
I've seen a bunch of other SO articles but they are not exactly what I need.
c# RSA extract public key from private key
Use RSA private key to generate public key?
C# RSA Public Key Output Not Correct
In order to do this I had to combine a couple answers here:
https://stackoverflow.com/a/28407693
https://stackoverflow.com/a/32243171/645283
I combined both of the above answers and refactored out GetRSAProviderFromPemString so I didn't need to read the private key from a file:
//Adapted from https://stackoverflow.com/a/32243171/645283
public class PemKeyUtils
{
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
const String pempubfooter = "-----END PUBLIC KEY-----";
const String pemp8header = "-----BEGIN PRIVATE KEY-----";
const String pemp8footer = "-----END PRIVATE KEY-----";
const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----";
static bool verbose = false;
public static RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile)
{
string pemstring = File.ReadAllText(pemfile).Trim();
return GetRSAProviderFromPemString(pemstring);
}
public static RSACryptoServiceProvider GetRSAProviderFromPemString(String pemstr)
{
bool isPrivateKeyFile = true;
if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
isPrivateKeyFile = false;
byte[] pemkey;
if (isPrivateKeyFile)
pemkey = DecodeOpenSSLPrivateKey(pemstr);
else
pemkey = DecodeOpenSSLPublicKey(pemstr);
if (pemkey == null)
return null;
if (isPrivateKeyFile)
return DecodeRSAPrivateKey(pemkey);
else
return DecodeX509PublicKey(pemkey);
}
//-------- Get the binary RSA PUBLIC key --------
static byte[] DecodeOpenSSLPublicKey(String instr)
{
const String pempubheader = "-----BEGIN PUBLIC KEY-----";
const String pempubfooter = "-----END PUBLIC KEY-----";
String pemstr = instr.Trim();
byte[] binkey;
if (!pemstr.StartsWith(pempubheader) || !pemstr.EndsWith(pempubfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr);
sb.Replace(pempubheader, ""); //remove headers/footers, if present
sb.Replace(pempubfooter, "");
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try
{
binkey = Convert.FromBase64String(pubstr);
}
catch (System.FormatException)
{ //if can't b64 decode, data is not valid
return null;
}
return binkey;
}
static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509Key)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
using (var mem = new MemoryStream(x509Key))
{
using (var binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
{
try
{
var twobytes = binr.ReadUInt16();
switch (twobytes)
{
case 0x8130:
binr.ReadByte(); //advance 1 byte
break;
case 0x8230:
binr.ReadInt16(); //advance 2 bytes
break;
default:
return null;
}
var seq = binr.ReadBytes(15);
if (!CompareBytearrays(seq, seqOid)) //make sure Sequence for OID is correct
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8203)
binr.ReadInt16(); //advance 2 bytes
else
return null;
var bt = binr.ReadByte();
if (bt != 0x00) //expect null byte next
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;
if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
else if (twobytes == 0x8202)
{
highbyte = binr.ReadByte(); //advance 2 bytes
lowbyte = binr.ReadByte();
}
else
return null;
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
int modsize = BitConverter.ToInt32(modint, 0);
byte firstbyte = binr.ReadByte();
binr.BaseStream.Seek(-1, SeekOrigin.Current);
if (firstbyte == 0x00)
{ //if first byte (highest order) of modulus is zero, don't include it
binr.ReadByte(); //skip this null byte
modsize -= 1; //reduce modulus buffer size by 1
}
byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
return null;
int expbytes = binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
byte[] exponent = binr.ReadBytes(expbytes);
// We don't really need to print anything but if we insist to...
//showBytes("\nExponent", exponent);
//showBytes("\nModulus", modulus);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaKeyInfo = new RSAParameters
{
Modulus = modulus,
Exponent = exponent
};
rsa.ImportParameters(rsaKeyInfo);
return rsa;
}
catch (Exception)
{
return null;
}
}
}
}
//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider ---
static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
Console.WriteLine("showing components ..");
if (verbose)
{
showBytes("\nModulus", MODULUS);
showBytes("\nExponent", E);
showBytes("\nD", D);
showBytes("\nP", P);
showBytes("\nQ", Q);
showBytes("\nDP", DP);
showBytes("\nDQ", DQ);
showBytes("\nIQ", IQ);
}
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
//----- Get the binary RSA PRIVATE key, decrypting if necessary ----
static byte[] DecodeOpenSSLPrivateKey(String instr)
{
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
String pemstr = instr.Trim();
byte[] binkey;
if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr);
sb.Replace(pemprivheader, ""); //remove headers/footers, if present
sb.Replace(pemprivfooter, "");
String pvkstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try
{ // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
binkey = Convert.FromBase64String(pvkstr);
return binkey;
}
catch (System.FormatException)
{ //if can't b64 decode, it must be an encrypted private key
//Console.WriteLine("Not an unencrypted OpenSSL PEM private key");
}
StringReader str = new StringReader(pvkstr);
//-------- read PEM encryption info. lines and extract salt -----
if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
return null;
String saltline = str.ReadLine();
if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
return null;
String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
byte[] salt = new byte[saltstr.Length / 2];
for (int i = 0; i < salt.Length; i++)
salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
if (!(str.ReadLine() == ""))
return null;
//------ remaining b64 data is encrypted RSA key ----
String encryptedstr = str.ReadToEnd();
try
{ //should have b64 encrypted RSA key now
binkey = Convert.FromBase64String(encryptedstr);
}
catch (System.FormatException)
{ // bad b64 data.
return null;
}
//------ Get the 3DES 24 byte key using PDK used by OpenSSL ----
SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>");
//Console.Write("\nEnter password to derive 3DES key: ");
//String pswd = Console.ReadLine();
byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
if (deskey == null)
return null;
//showBytes("3DES key", deskey) ;
//------ Decrypt the encrypted 3des-encrypted RSA private key ------
byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
if (rsakey != null)
return rsakey; //we have a decrypted RSA private key
else
{
Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
return null;
}
}
// ----- Decrypt the 3DES encrypted RSA private key ----------
static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
{
MemoryStream memst = new MemoryStream();
TripleDES alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = IV;
try
{
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
return null;
}
byte[] decryptedData = memst.ToArray();
return decryptedData;
}
//----- OpenSSL PBKD uses only one hash cycle (count); miter is number of iterations required to build sufficient bytes ---
static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter)
{
IntPtr unmanagedPswd = IntPtr.Zero;
int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
//UTF8Encoding utf8 = new UTF8Encoding();
//byte[] psbytes = utf8.GetBytes(pswd);
// --- contatenate salt and pswd bytes into fixed data array ---
byte[] data00 = new byte[psbytes.Length + salt.Length];
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = null;
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
for (int j = 0; j < miter; j++)
{
// ---- Now hash consecutively for count times ------
if (j == 0)
result = data00; //initialize
else
{
Array.Copy(result, hashtarget, result.Length);
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
result = hashtarget;
//Console.WriteLine("Updated new initial hash target:") ;
//showBytes(result) ;
}
for (int i = 0; i < count; i++)
result = md5.ComputeHash(result);
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
}
//showBytes("Final key material", keymaterial);
byte[] deskey = new byte[24];
Array.Copy(keymaterial, deskey, deskey.Length);
Array.Clear(psbytes, 0, psbytes.Length);
Array.Clear(data00, 0, data00.Length);
Array.Clear(result, 0, result.Length);
Array.Clear(hashtarget, 0, hashtarget.Length);
Array.Clear(keymaterial, 0, keymaterial.Length);
return deskey;
}
static SecureString GetSecPswd(String prompt)
{
SecureString password = new SecureString();
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write(prompt);
Console.ForegroundColor = ConsoleColor.Magenta;
while (true)
{
ConsoleKeyInfo cki = Console.ReadKey(true);
if (cki.Key == ConsoleKey.Enter)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (cki.Key == ConsoleKey.Backspace)
{
// remove the last asterisk from the screen...
if (password.Length > 0)
{
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
Console.Write(" ");
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
password.RemoveAt(password.Length - 1);
}
}
else if (cki.Key == ConsoleKey.Escape)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
{
if (password.Length < 20)
{
password.AppendChar(cki.KeyChar);
Console.Write("*");
}
else
{
Console.Beep();
}
}
else
{
Console.Beep();
}
}
}
static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
static void showBytes(String info, byte[] data)
{
Console.WriteLine("{0} [{1} bytes]", info, data.Length);
for (int i = 1; i <= data.Length; i++)
{
Console.Write("{0:X2} ", data[i - 1]);
if (i % 16 == 0)
Console.WriteLine();
}
Console.WriteLine("\n\n");
}
/// <summary>
/// Export public key from MS RSACryptoServiceProvider into OpenSSH PEM string
/// slightly modified from https://stackoverflow.com/a/28407693
/// </summary>
/// <param name="csp"></param>
/// <returns></returns>
public static string ExportPublicKey(RSACryptoServiceProvider csp)
{
StringWriter outputStream = new StringWriter();
var parameters = csp.ExportParameters(false);
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
innerWriter.Write((byte)0x30); // SEQUENCE
EncodeLength(innerWriter, 13);
innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
EncodeLength(innerWriter, rsaEncryptionOid.Length);
innerWriter.Write(rsaEncryptionOid);
innerWriter.Write((byte)0x05); // NULL
EncodeLength(innerWriter, 0);
innerWriter.Write((byte)0x03); // BIT STRING
using (var bitStringStream = new MemoryStream())
{
var bitStringWriter = new BinaryWriter(bitStringStream);
bitStringWriter.Write((byte)0x00); // # of unused bits
bitStringWriter.Write((byte)0x30); // SEQUENCE
using (var paramsStream = new MemoryStream())
{
var paramsWriter = new BinaryWriter(paramsStream);
EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus
EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent
var paramsLength = (int)paramsStream.Length;
EncodeLength(bitStringWriter, paramsLength);
bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
}
var bitStringLength = (int)bitStringStream.Length;
EncodeLength(innerWriter, bitStringLength);
innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
}
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
// WriteLine terminates with \r\n, we want only \n
outputStream.Write("-----BEGIN PUBLIC KEY-----\n");
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.Write(base64, i, Math.Min(64, base64.Length - i));
outputStream.Write("\n");
}
outputStream.Write("-----END PUBLIC KEY-----");
}
return outputStream.ToString();
}
// https://stackoverflow.com/a/23739932/2860309
private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}
//https://stackoverflow.com/a/23739932/2860309
private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}
}
Then, I was able to export (more like piece together) the public key like by calling PemKeyUtils.ExportPublicKey:
using (RSACryptoServiceProvider rsaCsp = PemKeyUtils.GetRSAProviderFromPemString(privateKeyInPemFormat))
{
return PemKeyUtils.ExportPublicKey(rsaCsp);
}
You aren't very clear on what you want. You can use the Bouncycastle library to parse the PEM data and return the RSA keypair, from which you can extract the public key. Here is some sample code:
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
namespace ImportRSAPrivateKeyPEM
{
class MainClass
{
readonly static string PEM_PRIV_KEY = #"-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----";
public static void Main(string[] args)
{
var rdr = new StringReader(PEM_PRIV_KEY);
var pemReader = new PemReader(rdr);
AsymmetricCipherKeyPair pemObject = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)pemReader.ReadObject();
}
}
}
If you need to convert from Bouncycastle types to .NET types then the methods of Org.BouncyCastle.Security.DotNetUtilities can be used. The Org.BouncyCastle.Asn1.Pkcs.RsaPrivateKeyStructure may also be of use depending on what you want to do.
Unfortunately documentation on the Bouncycastle C# library seems to be pretty thin. There always the source code itself, which is what I use.

How to parse(Convert to RSAParameters) X.509 private key in C#?

I'm working on an encryption channel to encrypt the communication between two devices. So I'm creating a helper class to do the encryption and decryption. I've googled a lot and found a piece of code that can parse RSA Public Key Into RSACryptoServiceProvider.
This is the code:
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
{
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[15];
MemoryStream mem = new MemoryStream(x509key);
BinaryReader binr = new BinaryReader(mem);
byte bt = 0;
ushort twobytes = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
else
return null;
seq = binr.ReadBytes(15);
if (!CompareBytearrays(seq, SeqOID))
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8103)
binr.ReadByte();
else if (twobytes == 0x8203)
binr.ReadInt16();
else
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
else
return null;
twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;
if (twobytes == 0x8102)
lowbyte = binr.ReadByte();
else if (twobytes == 0x8202)
{
highbyte = binr.ReadByte();
lowbyte = binr.ReadByte();
}
else
return null;
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
int modsize = BitConverter.ToInt32(modint, 0);
byte firstbyte = binr.ReadByte();
binr.BaseStream.Seek(-1, SeekOrigin.Current);
if (firstbyte == 0x00)
{
binr.ReadByte();
modsize -= 1;
}
byte[] modulus = binr.ReadBytes(modsize);
if (binr.ReadByte() != 0x02)
return null;
int expbytes = (int)binr.ReadByte();
byte[] exponent = binr.ReadBytes(expbytes);
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
It works awesome with a public key. But my question is how can I parse a X.509 private key? I'm not really familiar with structure of the RSA key.
The keys are generated from node-rsa in node.js
Thanks in advance. <3
You currently have a functioning reader for the RSAPublicKey structure. That structure, and the RSAPrivateKey structure, can be found in (RFC 3447 Appendix A.1)[https://www.rfc-editor.org/rfc/rfc3447#appendix-A].
.NET does not support "multi-prime" (more than 2) RSA (but neither does anyone else), so you can stick with the version 0 format. The field names should be clear from the alternative names in the comments, but if it is unclear:
modulus -> Modulus
publicExponent -> Exponent
privateExponent -> D
prime1 -> P
prime2 -> Q
exponent1 -> DP
exponent2 -> DQ
coefficient -> InverseQ
You will also need to add (or remove) padding zeros (on the left) to the values such that
D.Length == Modulus.Length
hm = (Modulus.Length + 1) / 2 // half round up
P, Q, DP, DQ, InverseQ each have Length == hm.

I have a RSA public key exponent and modulus. How can I encrypt a string using C#?

My Public Key
setMaxDigits(131);
var publicKey = new RSAKeyPair('010001', '', '009e57c825fd80fec265fab0aab5ef2af47fb7b82b7b1344f5fd439363e78eff968ec4bfd3d5881b2871c4508c35db0bb307d3115170b5ab555d1d4ac194804673b21b86576ab17dd6d38f8629d1219adfbc71b4663c76369c4802854a13e24946dcb001eabf8d2c2a114acae7ca3fa49d04335983552c74cc1042bd15b3ec1945');
Javascript encrypt function
function encryptedString(l, o) {
var h = new Array();
var b = o.length;
var f = 0;
while (f < b) {
h[f] = o.charCodeAt(f);
f++;
}
while (h.length % l.chunkSize != 0) {
h[f++] = 0;
}
var g = h.length;
var p = "";
var e, d, c;
for (f = 0; f < g; f += l.chunkSize) {
c = new BigInt();
e = 0;
for (d = f; d < f + l.chunkSize; ++e) {
c.digits[e] = h[d++];
c.digits[e] += h[d++] << 8;
}
var n = l.barrett.powMod(c, l.e);
var m = l.radix == 16 ? biToHex(n) : biToString(n, l.radix);
p += m + " ";
}
return p.substring(0, p.length - 1);
}
My C# code:
RSAParameters rsaParams = new RSAParameters();
var hexValue = FromHex("010001");
var plainTextBytes = System.Text.Encoding.Default.GetBytes(hexValue);
var exp = System.Convert.ToBase64String(plainTextBytes);
rsaParams.Exponent = plainTextBytes;
var hexValue1 = FromHex("009e57c825fd80fec265fab0aab5ef2af47fb7b82b7b1344f5fd439363e78eff968ec4bfd3d5881b2871c4508c35db0bb307d3115170b5ab555d1d4ac194804673b21b86576ab17dd6d38f8629d1219adfbc71b4663c76369c4802854a13e24946dcb001eabf8d2c2a114acae7ca3fa49d04335983552c74cc1042bd15b3ec1945");
var modulus = System.Text.Encoding.Default.GetBytes(hexValue1);
var exp11 = System.Convert.ToBase64String(modulus);
rsaParams.Modulus = modulus;
Modulus = new BigInteger(rsaParams.Modulus);
Exponent = new BigInteger(rsaParams.Exponent);
BigInteger bnData = new BigInteger(newData);
// (bnData ^ Exponent) % Modulus - This Encrypt the data using the public Exponent
BigInteger encData = bnData.modPow(Exponent, Modulus);
var hexEncryptedData = encData.ToHexString();
See html to view my Modulus and Exponent
http://103.203.49.220:1234/TestRSA.html
This example return encrypted string by Javascript
I also write it in C#, but It don't return the same value as JS
Link to download c# code. http://www.mediafire.com/file/xzqtxbibqk83h48/RSAEncryptionTestApplication.zip
Please help me to fix it. Ensure C# return the same data as js
RSAParameters rsaParams = new RSAParameters();
var hexValue = FromHex("010001");
This was a good start, things went pretty downhill from there.
RSAParameters rsaParams = new RSAParameters
{
Modulus = FromHex(yourBigLongModulusHex),
Exponent = FromHex("010001"),
}
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParams);
return rsa.EncryptData(data, RSAEncryptionPadding.OaepSHA1);
}
If you insist on using BigInteger to do your own raw RSA (which no one will be able to read, since no one does raw RSA due to its bad security... you need to do padding) remember that
BigInteger takes in byte[] in Little-Endian ordering
BigInteger says that a number is negative if bytes.Last() >= 0x80.
The following isn't GC-optimal, but it'll do.
private static BigInteger BigIntegerFromBigEndian(byte[] data)
{
byte[] tmp = new byte[data + 1];
Buffer.BlockCopy(data, 0, tmp, 1, data.Length);
Array.Reverse(tmp);
return new BigInteger(tmp);
}
Now, please don't do custom implementations of RSA outside of homework/self-discovery. I'll feel bad if you use this knowledge to write an implementation so full of timing and lack-of-padding holes that your key and data are recovered in less than a day.

C# Export Private/Public RSA key from RSACryptoServiceProvider to PEM string

I have an instance of System.Security.Cryptography.RSACryptoServiceProvider, i need to export it's key to a PEM string - like this:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDUNPB6Lvx+tlP5QhSikADl71AjZf9KN31qrDpXNDNHEI0OTVJ1
OaP2l56bSKNo8trFne1NK/B4JzCuNP8x6oGCAG+7bFgkbTMzV2PCoDCRjNH957Q4
Gxgx1VoS6PjD3OigZnx5b9Hebbp3OrTuqNZaK/oLPGr5swxHILFVeHKupQIDAQAB
AoGAQk3MOZEGyZy0fjQ8eFKgRTfSBU1wR8Mwx6zKicbAotq0CBz2v7Pj3D+higlX
LYp7+rUOmUc6WoB8QGJEvlb0YZVxUg1yDLMWYPE7ddsHsOkBIs7zIyS6cqhn0yZD
VTRFjVST/EduvpUOL5hbyLSwuq+rbv0iPwGW5hkCHNEhx2ECQQDfLS5549wjiFXF
gcio8g715eMT+20we3YmgMJDcviMGwN/mArvnBgBQsFtCTsMoOxm68SfIrBYlKYy
BsFxn+19AkEA82q83pmcbGJRJ3ZMC/Pv+/+/XNFOvMkfT9qbuA6Lv69Z1yk7I1ie
FTH6tOmPUu4WsIOFtDuYbfV2pvpqx7GuSQJAK3SnvRIyNjUAxoF76fGgGh9WNPjb
DPqtSdf+e5Wycc18w+Z+EqPpRK2T7kBC4DWhcnTsBzSA8+6V4d3Q4ugKHQJATRhw
a3xxm65kD8CbA2omh0UQQgCVFJwKy8rsaRZKUtLh/JC1h1No9kOXKTeUSmrYSt3N
OjFp7OHCy84ihc8T6QJBANe+9xkN9hJYNK1pL1kSwXNuebzcgk3AMwHh7ThvjLgO
jruxbM2NyMM5tl9NZCgh1vKc2v5VaonqM1NBQPDeTTw=
-----END RSA PRIVATE KEY-----
But there is no such option according to the MSDN documentation, there is only some kind of XML export. I can't use any third party libraries like BouncyCastle.
Is there any way to generate this string?
Please note: The code below is for exporting a private key. If you are looking to export the public key, please refer to my answer given here.
The PEM format is simply the ASN.1 DER encoding of the key (per PKCS#1) converted to Base64. Given the limited number of fields needed to represent the key, it's pretty straightforward to create quick-and-dirty DER encoder to output the appropriate format then Base64 encode it. As such, the code that follows is not particularly elegant, but does the job:
private static void ExportPrivateKey(RSACryptoServiceProvider csp, TextWriter outputStream)
{
if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp");
var parameters = csp.ExportParameters(true);
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
EncodeIntegerBigEndian(innerWriter, parameters.D);
EncodeIntegerBigEndian(innerWriter, parameters.P);
EncodeIntegerBigEndian(innerWriter, parameters.Q);
EncodeIntegerBigEndian(innerWriter, parameters.DP);
EncodeIntegerBigEndian(innerWriter, parameters.DQ);
EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
outputStream.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
// Output as Base64 with lines chopped at 64 characters
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
}
outputStream.WriteLine("-----END RSA PRIVATE KEY-----");
}
}
private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}
private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}
With the current version of .NET, this can be done in a simple way.
RSA rsa = RSA.Create();
rsa.KeySize = 4096;
// Private key export.
string hdrPrv = "-----BEGIN RSA PRIVATE KEY-----";
string ftrPrv = "-----END RSA PRIVATE KEY-----";
string keyPrv = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey());
string PEMPrv = "${hdrPrv}\n{keyPrv}\n{ftrPrv}";
// Public key export (with a correction for more accuracy from RobSiklos's comment to have the key in PKCS#1 RSAPublicKey format)
string hdrPub = "-----BEGIN RSA PUBLIC KEY-----";
string ftrPub = "-----END RSA PUBLIC KEY-----";
string keyPub = Convert.ToBase64String(rsa.ExportRSAPublicKey());
string PEMPub = "${hdrPub}\n{keyPub}\n{ftrPub}";
// Distribute PEMs.
Note: To have the nicely formatted file with new lines, you can write a little function to do it for you.
With the solution given above, you will have a file with only three lines.
If you're using .NET Core 3.0 this is already implemented out of the box
public string ExportPrivateKey(RSA rsa)
{
var privateKeyBytes = rsa.ExportRSAPrivateKey();
var builder = new StringBuilder("-----BEGIN RSA PRIVATE KEY");
builder.AppendLine("-----");
var base64PrivateKeyString = Convert.ToBase64String(privateKeyBytes);
var offset = 0;
const int LINE_LENGTH = 64;
while (offset < base64PrivateKeyString.Length)
{
var lineEnd = Math.Min(offset + LINE_LENGTH, base64PrivateKeyString.Length);
builder.AppendLine(base64PrivateKeyString.Substring(offset, lineEnd - offset));
offset = lineEnd;
}
builder.Append("-----END RSA PRIVATE KEY");
builder.AppendLine("-----");
return builder.ToString();
}
For anyone else who balked at the original answer's apparent complexity (which is very helpful, don't get me wrong), I thought I'd post my solution which is a little more straightforward IMO (but still based on the original answer):
public class RsaCsp2DerConverter {
private const int MaximumLineLength = 64;
// Based roughly on: http://stackoverflow.com/a/23739932/1254575
public RsaCsp2DerConverter() {
}
public byte[] ExportPrivateKey(String cspBase64Blob) {
if (String.IsNullOrEmpty(cspBase64Blob) == true)
throw new ArgumentNullException(nameof(cspBase64Blob));
var csp = new RSACryptoServiceProvider();
csp.ImportCspBlob(Convert.FromBase64String(cspBase64Blob));
if (csp.PublicOnly)
throw new ArgumentException("CSP does not contain a private key!", nameof(csp));
var parameters = csp.ExportParameters(true);
var list = new List<byte[]> {
new byte[] {0x00},
parameters.Modulus,
parameters.Exponent,
parameters.D,
parameters.P,
parameters.Q,
parameters.DP,
parameters.DQ,
parameters.InverseQ
};
return SerializeList(list);
}
private byte[] Encode(byte[] inBytes, bool useTypeOctet = true) {
int length = inBytes.Length;
var bytes = new List<byte>();
if (useTypeOctet == true)
bytes.Add(0x02); // INTEGER
bytes.Add(0x84); // Long format, 4 bytes
bytes.AddRange(BitConverter.GetBytes(length).Reverse());
bytes.AddRange(inBytes);
return bytes.ToArray();
}
public String PemEncode(byte[] bytes) {
if (bytes == null)
throw new ArgumentNullException(nameof(bytes));
var base64 = Convert.ToBase64String(bytes);
StringBuilder b = new StringBuilder();
b.Append("-----BEGIN RSA PRIVATE KEY-----\n");
for (int i = 0; i < base64.Length; i += MaximumLineLength)
b.Append($"{ base64.Substring(i, Math.Min(MaximumLineLength, base64.Length - i)) }\n");
b.Append("-----END RSA PRIVATE KEY-----\n");
return b.ToString();
}
private byte[] SerializeList(List<byte[]> list) {
if (list == null)
throw new ArgumentNullException(nameof(list));
var keyBytes = list.Select(e => Encode(e)).SelectMany(e => e).ToArray();
var binaryWriter = new BinaryWriter(new MemoryStream());
binaryWriter.Write((byte) 0x30); // SEQUENCE
binaryWriter.Write(Encode(keyBytes, false));
binaryWriter.Flush();
var result = ((MemoryStream) binaryWriter.BaseStream).ToArray();
binaryWriter.BaseStream.Dispose();
binaryWriter.Dispose();
return result;
}
}
Here is a GREAT NEWS, with .NET 7, Microsoft has added a new method to export RSA keys directly to PEM format.
Refer RSA.ExportRSAPrivateKeyPem Method
Below is how you can use it.
using (var rsa = new RSACryptoServiceProvider(2048)) // Generate a new 2048 bit RSA key
{
// RSA keys in PKCS#1 format, PEM encoded
string publicPrivateKeyPEM = rsa.ExportRSAPrivateKeyPem();
string publicOnlyKeyPEM = rsa.ExportRSAPublicKeyPem();
// RSA keys in XML format
string publicPrivateKeyXML = rsa.ToXmlString(true);
string publicOnlyKeyXML = rsa.ToXmlString(false);
// RSA keys in byte array
byte[] publicPrivateKey = rsa.ExportRSAPrivateKey();
byte[] publicOnlyKey = rsa.ExportRSAPublicKey();
}
public static Func<string, string> ToBase64PemFromKeyXMLString= (xmlPrivateKey) =>
{
if (string.IsNullOrEmpty(xmlPrivateKey))
throw new ArgumentNullException("RSA key must contains value!");
var keyContent = new PemReader(new StringReader(xmlPrivateKey));
if (keyContent == null)
throw new ArgumentNullException("private key is not valid!");
var ciphrPrivateKey = (AsymmetricCipherKeyPair)keyContent.ReadObject();
var asymmetricKey = new AsymmetricKeyEntry(ciphrPrivateKey.Private);
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(asymmetricKey.Key);
var serializedPrivateKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();
return Convert.ToBase64String(serializedPrivateKey);
};

Public key encryption with RSACryptoServiceProvider

I have been over an article at CodeProject a for a while that explains how to encrypt and decrypt using the RSA provider:
RSA Private Key Encryption
While the old version from 2009 was buggy, the new 2012 version (with System.Numerics.BigInteger support) seems more reliable. What this version lacks though is a way to encrypt with a public key and decrypt using the private key.
So, I tried it myself but get garbage when I decrypt. I'm not familiar with the RSA provider, so I'm in the dark here. It's hard to find more info on how this is supposed to work.
Does anyone see what is wrong with this?
The following is ENcryption with a PUBLIC key:
// Add 4 byte padding to the data, and convert to BigInteger struct
BigInteger numData = GetBig( AddPadding( data ) );
RSAParameters rsaParams = rsa.ExportParameters( false );
//BigInteger D = GetBig( rsaParams.D ); //only for private key
BigInteger Exponent = GetBig( rsaParams.Exponent );
BigInteger Modulus = GetBig( rsaParams.Modulus );
BigInteger encData = BigInteger.ModPow( numData, Exponent, Modulus );
return encData.ToByteArray();
Do I use the big "D" from the provider when I do this? Probably not since it's the public key which doesn't have the "D".
Then the counterpart (DEcrypting using the PRIVATE key):
BigInteger numEncData = new BigInteger( cipherData );
RSAParameters rsaParams = rsa.ExportParameters( true );
BigInteger D = GetBig( rsaParams.D );
//BigInteger Exponent = GetBig( rsaParams.Exponent );
BigInteger Modulus = GetBig( rsaParams.Modulus );
BigInteger decData = BigInteger.ModPow( numEncData, D, Modulus );
byte[] data = decData.ToByteArray();
byte[] result = new byte[ data.Length - 1 ];
Array.Copy( data, result, result.Length );
result = RemovePadding( result );
Array.Reverse( result );
return result;
Do I need the "D" or the Exponent here?
Obviously I need the crypto to work both ways private-public public-private.
Any help is much appreciated!
Take this encode/decode example
byte[] toEncryptData = Encoding.ASCII.GetBytes("hello world");
//Generate keys
RSACryptoServiceProvider rsaGenKeys = new RSACryptoServiceProvider();
string privateXml = rsaGenKeys.ToXmlString(true);
string publicXml = rsaGenKeys.ToXmlString(false);
//Encode with public key
RSACryptoServiceProvider rsaPublic = new RSACryptoServiceProvider();
rsaPublic.FromXmlString(publicXml);
byte[] encryptedRSA = rsaPublic.Encrypt(toEncryptData, false);
string EncryptedResult = Encoding.Default.GetString(encryptedRSA);
//Decode with private key
var rsaPrivate = new RSACryptoServiceProvider();
rsaPrivate.FromXmlString(privateXml);
byte[] decryptedRSA = rsaPrivate.Decrypt(encryptedRSA, false);
string originalResult = Encoding.Default.GetString(decryptedRSA);
here is an example for you:
public static void rsaPlayground()
{
byte[] data = new byte[] { 1, 2, 3, 4, 5 };
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();//make a new csp with a new keypair
var pub_key = csp.ExportParameters(false); // export public key
var priv_key = csp.ExportParameters(true); // export private key
var encData = csp.Encrypt(data, false); // encrypt with PKCS#1_V1.5 Padding
var decBytes = MyRSAImpl.plainDecryptPriv(encData, priv_key); //decrypt with own BigInteger based implementation
var decData = decBytes.SkipWhile(x => x != 0).Skip(1).ToArray();//strip PKCS#1_V1.5 padding
}
public class MyRSAImpl
{
private static byte[] rsaOperation(byte[] data, BigInteger exp, BigInteger mod)
{
BigInteger bData = new BigInteger(
data //our data block
.Reverse() //BigInteger has another byte order
.Concat(new byte[] { 0 }) // append 0 so we are allways handling positive numbers
.ToArray() // constructor wants an array
);
return
BigInteger.ModPow(bData, exp, mod) // the RSA operation itself
.ToByteArray() //make bytes from BigInteger
.Reverse() // back to "normal" byte order
.ToArray(); // return as byte array
/*
*
* A few words on Padding:
*
* you will want to strip padding after decryption or apply before encryption
*
*/
}
public static byte[] plainEncryptPriv(byte[] data, RSAParameters key)
{
MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
return rsaOperation(data, myKey.privExponent, myKey.Modulus);
}
public static byte[] plainEncryptPub(byte[] data, RSAParameters key)
{
MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
return rsaOperation(data, myKey.pubExponent, myKey.Modulus);
}
public static byte[] plainDecryptPriv(byte[] data, RSAParameters key)
{
MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
return rsaOperation(data, myKey.privExponent, myKey.Modulus);
}
public static byte[] plainDecryptPub(byte[] data, RSAParameters key)
{
MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
return rsaOperation(data, myKey.pubExponent, myKey.Modulus);
}
}
public class MyRSAParams
{
public static MyRSAParams fromRSAParameters(RSAParameters key)
{
var ret = new MyRSAParams();
ret.Modulus = new BigInteger(key.Modulus.Reverse().Concat(new byte[] { 0 }).ToArray());
ret.privExponent = new BigInteger(key.D.Reverse().Concat(new byte[] { 0 }).ToArray());
ret.pubExponent = new BigInteger(key.Exponent.Reverse().Concat(new byte[] { 0 }).ToArray());
return ret;
}
public BigInteger Modulus;
public BigInteger privExponent;
public BigInteger pubExponent;
}

Categories

Resources