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;
}
Related
I have a .net framework 4.5.2 project in which AES 256/GCM/NO padding implementation has to be performed. I found out that framework doesn't support GCM directly hence I tried using BouncyCastle. While passing the params it looks like something is wrong being passed to initialization. Also I wanted to make the Keysize as 256 bit . I took this page as reference
Below is the code which I'm trying :
public class BouncyCastleCustom:ICipherParameters
{
private const string ALGORITHM = "AES";
private const byte AesIvSize = 16;
private const byte GcmTagSize = 16; // in bytes
private readonly CipherMode _cipherMode = CipherMode.GCM;
//private const string _cipherMode = "GCM";
private readonly string _algorithm = ALGORITHM;
public string Encrypt(string plainText, byte[] key)
{
var random = new SecureRandom();
var iv = random.GenerateSeed(AesIvSize);
var keyParameters = CreateKeyParameters(key, iv, GcmTagSize * 8);
var cipher = CipherUtilities.GetCipher(_algorithm);
cipher.Init(true, keyParameters);/*System.ArgumentException: 'invalid parameter passed to AES init - Org.BouncyCastle.Crypto.Parameters.AeadParameters'*/
var plainTextData = Encoding.UTF8.GetBytes(plainText);
var cipherText = cipher.DoFinal(plainTextData);
return PackCipherData(cipherText, iv);
}
public string Decrypt(string cipherText, byte[] key)
{
var (encryptedBytes, iv, tagSize) = UnpackCipherData(cipherText);
var keyParameters = CreateKeyParameters(key, iv, tagSize * 8);
var cipher = CipherUtilities.GetCipher(_algorithm);
cipher.Init(false, keyParameters);
var decryptedData = cipher.DoFinal(encryptedBytes);
return Encoding.UTF8.GetString(decryptedData);
}
private ICipherParameters CreateKeyParameters(byte[] key, byte[] iv, int macSize)
{
var keyParameter = new KeyParameter(key);
if (_cipherMode == CipherMode.CBC)
{
return new ParametersWithIV(keyParameter, iv);
}
else if (_cipherMode == CipherMode.GCM)
{
return new AeadParameters(keyParameter, macSize, iv);
}
throw new Exception("Unsupported cipher mode");
}
private string PackCipherData(byte[] encryptedBytes, byte[] iv)
{
var dataSize = encryptedBytes.Length + iv.Length + 1;
if (_cipherMode == CipherMode.GCM)
dataSize += 1;
var index = 0;
var data = new byte[dataSize];
data[index] = AesIvSize;
index += 1;
if (_cipherMode == CipherMode.GCM)
{
data[index] = GcmTagSize;
index += 1;
}
Array.Copy(iv, 0, data, index, iv.Length);
index += iv.Length;
Array.Copy(encryptedBytes, 0, data, index, encryptedBytes.Length);
return Convert.ToBase64String(data);
}
private (byte[], byte[], byte) UnpackCipherData(string cipherText)
{
var index = 0;
var cipherData = Convert.FromBase64String(cipherText);
byte ivSize = cipherData[index];
index += 1;
byte tagSize = 0;
if (_cipherMode == CipherMode.GCM)
{
tagSize = cipherData[index];
index += 1;
}
byte[] iv = new byte[ivSize];
Array.Copy(cipherData, index, iv, 0, ivSize);
index += ivSize;
byte[] encryptedBytes = new byte[cipherData.Length - index];
Array.Copy(cipherData, index, encryptedBytes, 0, encryptedBytes.Length);
return (encryptedBytes, iv, tagSize);
}
public enum CipherMode //cannot access BouncyCastle inbuilt CipherMode
{
CBC,
GCM
}
}
static void Main(string[] args)
{
BouncyCastleCustom aes = new BouncyCastleCustom();
var encrypted = aes.Encrypt("testDemo", Encoding.UTF8.GetBytes("mysmallkey1234551298765134567890"));
Console.WriteLine("Encrypted testDemo: " + encrypted);
string decrypted = aes.Decrypt(encrypted, Encoding.UTF8.GetBytes("mysmallkey1234551298765134567890"));
Console.WriteLine("Decrypted: " + decrypted);
}
I'm trying to simulate a Meet-in-the-Middle attack on Double DES which works as follows:
So I'm performing the MitM attack by first encrypting a known plaintext m and with all possible values of k1, and then subsequently I decrypt a known ciphertext c with all possible values of k2. Then there should be a match inbetween which gives me k1, and k2. I'm using a cut-down key size of 20 bits instead of 56bit (or 64bit which is what the DES implementation actually wants as input). I just pad with zeroes after 20bits.
I've implemented what I think is a right solution but not getting any matches.
My two hashtables:
Dictionary<string, string> hashTable = new Dictionary<string, string>();
Dictionary<string, string> matches = new Dictionary<string, string>();
Encrypting:
Since I'm using a reduced 20bit key, there can be 220 different combinations of 20bits. So for each iteration, I take the counter i, convert it to a binary string representation, then I pad 0s until it makes a 64bit binary value. Then I convert this string to a byte array and encrypt the plaintext with the key. I'm storing the output (intermediary cipher) as the key in the hashTable and the actual key used to get that cipher, as the value.
//First generate all possible intermediary values
for(int i=0; i< Math.Pow(2,20); i++)
{
string key1 = ToBin(i, 20);
string paddedKey1 = ToBin(i, 20).PadRight(64, '0');
//First encryption of plaintext1 using key1
string intermediaryCipher =
DESWrapper.DES_Encrypt(plaintext1, ConvertBinaryStringToByteArray(paddedKey1));
hashTable.Add(intermediaryCipher, key1);
//Show the current iteration in binary
Console.WriteLine(ToBin(i, 20));
}
DESWrapper.DES_Encrypt method:
public static string DES_Encrypt(string input, byte[] key)
{
DESCryptoServiceProvider desCryptoService = new DESCryptoServiceProvider();
//Reflection necessary otherwise it complains about a weak key, so bypassing that check
MethodInfo mi = desCryptoService.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance);
object[] Par = { key, desCryptoService.Mode, key, desCryptoService.FeedbackSize, 0 };
ICryptoTransform trans = mi.Invoke(desCryptoService, Par) as ICryptoTransform;
byte[] resultArray = trans.TransformFinalBlock(Encoding.Default.GetBytes(input), 0, Encoding.Default.GetBytes(input).Length);
desCryptoService.Clear();
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
After the encryption I have a hastable with 220 entries. The next thing I do is to make the decryption:
Decrypting:
When I'm decrypting ciphertext1 with the current padded key, the result, if it is the correct key, will be an intermediary cipher which already exists in the hashTable as a Key. So I perform this lookup. If it exists I save the two keys to another hashtable matches. If it doesn't exist I move on.
for (int i = 0; i < Math.Pow(2, 20); i++)
{
string key2 = ToBin(i, 20);
string paddedKey2 = ToBin(i, 20).PadRight(64, '0');
//Decrypting ciphertext1 with key2 (64bit padded)
string intermediaryCipher =
DESWrapper.DES_Decrypt(ciphertext1, ConvertBinaryStringToByteArray(paddedKey2));
var temp = hashTable.FirstOrDefault(x => x.Key == intermediaryCipher);
if(temp.Key != null)
{
matches.Add(temp.Value, key2);
Console.WriteLine("Found match!");
Console.ReadKey();
}
//Show the current iteration in binary
Console.WriteLine(ToBin(i, 20));
}
DESWrapper.DES_Decrypt:
public static string DES_Decrypt(string input, byte[] key)
{
DESCryptoServiceProvider desCryptoService = new DESCryptoServiceProvider();
//Again have to use reflection..
MethodInfo mi = desCryptoService.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance);
object[] Par = { key, desCryptoService.Mode, key, desCryptoService.FeedbackSize, 0 };
ICryptoTransform trans = mi.Invoke(desCryptoService, Par) as ICryptoTransform;
byte[] resultArray = trans.TransformFinalBlock(Encoding.Default.GetBytes(input), 0, Encoding.Default.GetBytes(input).Length);
desCryptoService.Clear();
return Convert.ToBase64String(resultArray);
}
The problem:
I never get a match, so the hashtable lookup always returns an empty key-value pair. I don't understand why. Eventually it should match but it doesn't.
Could the problem be in the way I'm trying to look up the values in the hashTable?
Other information:
To encrypt the intial plaintext and ciphertext I use a fabricated key which the 17th bit set to 1 and all other 63 bits set to 0. This is done for both keys.
In this way I should get the match quite fast when I'm doing the decryption, but I'm not sure if the problem is here. Including the code anyway:
private static void GeneratePlaintextCiphertextPairs(out string plainText1, out string plainText2, out string cipherText1, out string cipherText2)
{
Random rnd = new Random(Guid.NewGuid().GetHashCode());
//Generate two random keys of 20 bits padded with 0s to reach 64 bits
//We need 64 bits because the implementation of DES requires it. Internally,
//it will only use 56 bits
byte[] key1 = GenerateRandom64BitPaddedKey(rnd);
byte[] key2 = GenerateRandom64BitPaddedKey(rnd);
plainText1 = "Hello Dear World";
//Perform double DES encryption
cipherText1 = DESWrapper.DES_Encrypt(
DESWrapper.DES_Encrypt(plainText1, key1),
key2);
plainText2 = "Hello Evil World";
//Perform double DES encryption
cipherText2 = DESWrapper.DES_Encrypt(
DESWrapper.DES_Encrypt(plainText2, key1),
key2);
}
private static byte[] GenerateRandom64BitPaddedKey(Random rnd)
{
short keySize = 64;
//The first 20bits are of interest, the rest is padded with 0s
BitArray bitArray = new BitArray(keySize);
for (int i=0; i<keySize; i++)
{
//if(i < 20) { bitArray[i] = rnd.NextDouble() > 0.5; }
//else { bitArray[i] = false; }
if (i == 17) { bitArray[i] = true; }
else { bitArray[i] = false; }
}
//Console.WriteLine("In Binary: " + ToDigitString(bitArray));
byte[] key = new byte[8];
ReverseBitArray(ref bitArray);
bitArray.CopyTo(key, 0);
return key;
}
I am currently trying to generate and send a public RSA key using C#. It should be a 2048 bit long key in PEM format. I have successfully done so using OpenSSL command with the following (some output are shortened):
$ openssl genrsa 2048
Generating RSA private key, 2048 bit long modulus
............................................................+++
............................................................+++
e is 65537 (0x10001)
$ openssl rsa -pubout
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy1MoBtENHBhYLgwP5Hw/xRGaBPHonApChBPBYD6fiq/QoLXA
RmyMoOjXHsKrrwysYIujXADM2LZ0MlFvPbBulvciWnZwp9CUQPwsZ8xnmBWlHyru
xTxNSvV+E/6+2gMOn3I4bmOSIaLx2Y7nCuaenREvD7Mn0vgFnP7yaN8/9va4q8Lo
...
...
y5jiKQKBgGAe9DlkYvR6Edr/gzd6HaF4btQZf6idGdmsYRYc2EMHdRM2NVqlvyLc
MR6rYEuViqLN5XWK6ITOlTPrgAuU6Rl4ZpRlS1ZrfjiUS6dzD/jtJJvsYByC7ZoU
NxIzB0r1hj0TIoedu6NqfRyJ6Fx09U5W81xx77T1EBSg4OCH7eyl
-----END RSA PRIVATE KEY-----
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy1MoBtENHBhYLgwP5Hw/
xRGaBPHonApChBPBYD6fiq/QoLXARmyMoOjXHsKrrwysYIujXADM2LZ0MlFvPbBu
lvciWnZwp9CUQPwsZ8xnmBWlHyruxTxNSvV+E/6+2gMOn3I4bmOSIaLx2Y7nCuae
nREvD7Mn0vgFnP7yaN8/9va4q8LoMKlceE5fSYl2QIfC5ZxUtkblbycEWZHLVOkv
+4Iz0ibD8KGo0PaiZl0jmn9yYXFy747xmwVun+Z4czO8Nu+OOVxsQF4hu1pKvTUx
9yHH/vk5Wr0I09VFyt3BT/RkecJbAAWB9/e572T+hhmmJ08wCs29oFa2Cdik9yyE
2QIDAQAB
-----END PUBLIC KEY-----
The following code is what I use to generate a public key using C#:
// Variables
CspParameters cspParams = null;
RSACryptoServiceProvider rsaProvider = null;
StreamWriter publicKeyFile = null;
string publicKey = "";
try
{
// Create a new key pair on target CSP
cspParams = new CspParameters();
cspParams.ProviderType = 1; // PROV_RSA_FULL
cspParams.Flags = CspProviderFlags.CreateEphemeralKey;
rsaProvider = new RSACryptoServiceProvider(2048, cspParams);
// Export public key
result = ExportPublicKeyToPEMFormat(rsaProvider);
}
catch (Exception ex)
{
}
The ExportPublicKeyToPEMFormat can be found from this thread:
https://stackoverflow.com/a/25591659/2383179
My output in C# looks like this:
-----BEGIN PUBLIC KEY-----
MIIBKwIBAAKCAQEAzMoaInPQ7nAXGWUY2EEtBcPY/Zvfcqf3Uxr7mFrQaxMjdXYi
DVSPh9XBWJlEhQ9ZGyBMpkWwtkrlDw11g/7pj+u7KTa5nH1ZB8vCrY3TC+YnFXPQ
Nv5dCzW0Lz+HD04rir2+K++XQCroy7G68uE9dtkbqa1U7IEWOvejbX+sgzo5ISHA
vCz2DFBInqYNJWfkM8OvLnRYYQ4f8MbmvDEMyaEYPGfQybXAs5eFksqm9pwR0xh4
Oxg/DkDas93lNIf+g00IesHvHuavRm2GX8jAXhrAoZY7nWQZpqS5kwx1kjSwtYEg
Vq4mHcaKIalMAoILSV9ttgqiJ5KVuKIvQJ7wRwIDAQABAgMBAAECAwEAAQIDAQAB
AgMBAAECAwEAAQIDAQAB
-----END PUBLIC KEY-----
The correct output using OpenSSL looks like this:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy1MoBtENHBhYLgwP5Hw/
xRGaBPHonApChBPBYD6fiq/QoLXARmyMoOjXHsKrrwysYIujXADM2LZ0MlFvPbBu
lvciWnZwp9CUQPwsZ8xnmBWlHyruxTxNSvV+E/6+2gMOn3I4bmOSIaLx2Y7nCuae
nREvD7Mn0vgFnP7yaN8/9va4q8LoMKlceE5fSYl2QIfC5ZxUtkblbycEWZHLVOkv
+4Iz0ibD8KGo0PaiZl0jmn9yYXFy747xmwVun+Z4czO8Nu+OOVxsQF4hu1pKvTUx
9yHH/vk5Wr0I09VFyt3BT/RkecJbAAWB9/e572T+hhmmJ08wCs29oFa2Cdik9yyE
2QIDAQAB
-----END PUBLIC KEY-----
Obviously there is something different with the formats between the two public key.
The OpenSSL key always starst with
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA"
My key starts with
"MIIBKwIBAAKCAQEA"
Unfortunately, the code in the answer you referenced isn't really correct - it exports a private key PEM format, but with only the public key fields correctly set, this is not the same as exporting an RSA public key in standard format.
I actually wrote the code in the other answer to that question, and at the time wrote a mode for exporting the public key in the standard format, but didn't include it in that answer as it wasn't required. Here it is:
private static void ExportPublicKey(RSACryptoServiceProvider csp, TextWriter outputStream)
{
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();
outputStream.WriteLine("-----BEGIN PUBLIC KEY-----");
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
}
outputStream.WriteLine("-----END PUBLIC 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]);
}
}
}
On .Net Framework, one simple way to extract the public key from an RSA object is to create a temporary X509Certificate2 object from it and then call the GetPublicKey() method on that, as shown below:
var tempCertRequest = new CertificateRequest("CN=DummyCN", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var tempCert = tempCertRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(3));
return tempCert.GetPublicKey();
GetPublicKey() will return the public key as a byte[]. You can convert it to Base64 using Convert.ToBase64String() if required.
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);
};
I'm developing a new version in c# of an existing java application.
The existing application uses RSA encryption with java.security.spec.* and boncycastle api.
I'm looking for equivalent code in c# for the java below code:
public static java.security.PublicKey getKey
(
org.bouncycastle.asn1.x509.RSAPublicKeyStructure rsaPublicKey
)
{
java.security.KeyFactory keyFactory = KeyFactory.getInstance("RSA");
java.security.spec.RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
rsaPublicKey.getModulus(),
rsaPublicKey.getPublicExponent());
java.security.PublicKey pkey = keyFactory.generatePublic(keySpec);
return pkey;
}
I "googled" a lot but don't found solution.
Thanks in advance for your help.
Although you may be already aware of this, there is a .NET version of Bouncy Castle, so you can use it in your C# project.
Regarding your question, here is an example of implementing signing in pure Bouncy Castle, an it deals with key generation in the MakeKey method, so you may want to take a look at it.
By the way, if this key is in a certificate, you may want to look at the .NET X509Certificate2 class.
Edit
I tried to convert your method into a c# equivalent, and this it the closer I got:
public static byte[] getKey(Org.BouncyCastle.Asn1.x509.RSAPublicKeyStructure rsaPublicKey)
{
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters bcKeySpec = new RsaKeyParameters();
bcKeySpec.RsaKeyParameters(false, rsaPublicKey.getModulus(), rsaPublicKey.getPublicExponent());
RSAParameters keySpec = Org.BouncyCastle.Security.DotNetUtilities.ToRSAParameters(bcKeySpec);
RSACryptoServiceProvider keyFactory = new RSACryptoServiceProvider();
keyFactory.ImportParameters(keySpec);
byte[] pKey = keyFactory.ExportCspBlob(false);
return pKey;
}
Note that the key is exported into a byte array, which depending of what you want to do with your key later, may or may not be helpful to you, also, the RSACryptoServiceProvider object let you encrypt, decrypt, sign and verify, so if you are going to get the key for any of these purposes, then you may want to return the keyFactory object instead of the exported public key.
If you want more information about RSACryptoServiceProvider you can read here: http://msdn.microsoft.com/en-us/library/s575f7e2.aspx
public static string EncryptRsa(string stringPublicKey, string stringDataToEncrypt)
{
byte[] publicKey = Convert.FromBase64String(stringPublicKey);
using (RSACryptoServiceProvider rsa = DecodeX509PublicKey(publicKey))
{
byte[] dataToEncrypt = Encoding.UTF8.GetBytes(stringDataToEncrypt);
byte[] encryptedData = rsa.Encrypt(dataToEncrypt, false);
return Convert.ToBase64String(encryptedData);
}
}
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
{
byte[] SeqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
MemoryStream ms = new MemoryStream(x509key);
BinaryReader reader = new BinaryReader(ms);
if (reader.ReadByte() == 0x30)
ReadASNLength(reader); //skip the size
else
return null;
int identifierSize = 0; //total length of Object Identifier section
if (reader.ReadByte() == 0x30)
identifierSize = ReadASNLength(reader);
else
return null;
if (reader.ReadByte() == 0x06) //is the next element an object identifier?
{
int oidLength = ReadASNLength(reader);
byte[] oidBytes = new byte[oidLength];
reader.Read(oidBytes, 0, oidBytes.Length);
if (oidBytes.SequenceEqual(SeqOID) == false) //is the object identifier rsaEncryption PKCS#1?
return null;
int remainingBytes = identifierSize - 2 - oidBytes.Length;
reader.ReadBytes(remainingBytes);
}
if (reader.ReadByte() == 0x03) //is the next element a bit string?
{
ReadASNLength(reader); //skip the size
reader.ReadByte(); //skip unused bits indicator
if (reader.ReadByte() == 0x30)
{
ReadASNLength(reader); //skip the size
if (reader.ReadByte() == 0x02) //is it an integer?
{
int modulusSize = ReadASNLength(reader);
byte[] modulus = new byte[modulusSize];
reader.Read(modulus, 0, modulus.Length);
if (modulus[0] == 0x00) //strip off the first byte if it's 0
{
byte[] tempModulus = new byte[modulus.Length - 1];
Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
modulus = tempModulus;
}
if (reader.ReadByte() == 0x02) //is it an integer?
{
int exponentSize = ReadASNLength(reader);
byte[] exponent = new byte[exponentSize];
reader.Read(exponent, 0, exponent.Length);
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024);
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
}
}
}
return null;
}
public static int ReadASNLength(BinaryReader reader)
{
//Note: this method only reads lengths up to 4 bytes long as
//this is satisfactory for the majority of situations.
int length = reader.ReadByte();
if ((length & 0x00000080) == 0x00000080) //is the length greater than 1 byte
{
int count = length & 0x0000000f;
byte[] lengthBytes = new byte[4];
reader.Read(lengthBytes, 4 - count, count);
Array.Reverse(lengthBytes); //
length = BitConverter.ToInt32(lengthBytes, 0);
}
return length;
}