Hello Stackoverflow users, I am working on assignment regarding RSA encryption and decryption and have hit a sticky spot.
Using Visual Studio I have created a simple form with 2 textboxes (one for the plain text message and the other to display the encrypted message) and 4 buttons (2 clear buttons to clear corresponding textboxes and an encryption button and decryption button).
public partial class Form1 : Form
{
//Strings to hold public & private keys
String publicKey, privateKey;
UnicodeEncoding encoder = new UnicodeEncoding();
public Form1()
{
RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider();
InitializeComponent();
privateKey = myRSA.ToXmlString(true);
publicKey = myRSA.ToXmlString(false);
}
private void btnClr1_Click(object sender, EventArgs e)
{
txtPlain.Text = "";
txtPlain.Refresh();
}
private void btnClr2_Click(object sender, EventArgs e)
{
txtCypher.Text = "";
txtCypher.Refresh();
}
private void btnEncrypt_Click(object sender, EventArgs e)
{
var myRSA = new RSACryptoServiceProvider();
//Set cryptoserviceprovider with the proper key
myRSA.FromXmlString(publicKey);
//Encode the data to encrypt as a byte array
var dataToEncrypt = encoder.GetBytes(txtPlain.Text);
//Encrypt the byte array
var encryptedByteArray = myRSA.Encrypt(dataToEncrypt, false).ToArray();
var length = encryptedByteArray.Count();
var item = 0;
var sb = new StringBuilder();
//Change each byte in the encrypted byte array to text
foreach(var x in encryptedByteArray)
{
item++;
sb.Append(x);
if (item < length) sb.Append(",");
}
txtCypher.Text = sb.ToString();
}
private void btnDecrypt_Click(object sender, EventArgs e)
{
var myRSA = new RSACryptoServiceProvider();
//Split data into an array
var dataArray = txtCypher.Text.Split(new char[] { ',' });
//Convert chars to bytes
byte[] dataByte = new byte[dataArray.Length];
for(int i = 0; i < dataArray.Length; i++) dataByte[i] = Convert.ToByte(dataArray[i]);
//Decrypt the byte array
myRSA.FromXmlString(privateKey);
var decryptedBytes = myRSA.Decrypt(dataByte, false);
//place into cypher text box
txtPlain.Text = encoder.GetString(decryptedBytes);
}
}
I have no issues with encrypting and decrypting messages (as long as they don't exceed the RSA key size, obviously).
So I am working to modify my program to allow for any size messages to be encrypted and decrypted.
This is the code I have come up with to allow for any size to be encrypted (which seems to work)
private void btnEncrypt_Click(object sender, EventArgs e)
{
var myRSA = new RSACryptoServiceProvider();
//Set cryptoserviceprovider with the proper key
myRSA.FromXmlString(publicKey);
//Encode the data to encrypt as a byte array
var dataToEncrypt = encoder.GetBytes(txtPlain.Text);
//store dataLength
int dataLength = dataToEncrypt.Length;
//Check if dataLength > 128
if (dataLength > 128)
{
//Divide dataLength by 128 to determine how many cycles will be needed
double numOfCycles = (dataLength / 117);
//round up to nearest whole number
cycles = (int)Math.Ceiling(numOfCycles);
//for however many cycles
for (int i = 0; i < cycles; i++)
{
var myByteArray = new byte[117];
for (int j = 0; j < 117; j++)
{
int currentByte = i * 117 + j;
myByteArray[j] = dataToEncrypt[currentByte];
}
var encryptedByteArray = myRSA.Encrypt(myByteArray, false).ToArray();
var length = encryptedByteArray.Count();
var item = 0;
//Change each byte in the encrypted byte array to text
foreach (var x in encryptedByteArray)
{
item++;
sb.Append(x);
if (item < length) sb.Append(",");
}
txtCypher.Text = sb.ToString();
}
}
else
{
var encryptedByteArray = myRSA.Encrypt(dataToEncrypt, false).ToArray();
var length = encryptedByteArray.Count();
var item = 0;
var sb = new StringBuilder();
//Change each byte in the encrypted byte array to text
foreach(var x in encryptedByteArray)
{
item++;
sb.Append(x);
if (item < length) sb.Append(",");
}
txtCypher.Text = sb.ToString();
}
}
And this is the code that I have come up with to handle decryption of any size message (which is what is giving me errors)
private void btnDecrypt_Click(object sender, EventArgs e)
{
var myRSA = new RSACryptoServiceProvider();
//Split data into an array
var dataArray = txtCypher.Text.Split(new char[] { ',' });
int length = dataArray.Count();
float numOfCycles = (length / 117);
int cycles = (int)Math.Ceiling(numOfCycles);
for (int i = 0; i < cycles; i++)
{
byte[] dataByte = new byte[117];
for(int j = 0; j < 117; j++)
{
//Convert chars to bytes
dataByte[j] = Convert.ToByte(dataArray[ i * 117 + j ]);
}
//Decrypt the byte array
myRSA.FromXmlString(privateKey);
var decryptedBytes = myRSA.Decrypt(dataByte, false);
txtPlain.Text += encoder.GetString(decryptedBytes);
}
}
The errors that it being thrown is:
`System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll. Additional information: Bad Data.`
from line 133: var decryptedBytes = myRSA.Decrypt(dataByte, false);
Any help/advice would be greatly appreciated! Thanks all!
As Luke Park said, it's generally considered bad practice to try to make RSA a chained algorithm. It's purpose is for secure "key exchange", meaning the encrypted contents are 'supposed' to be symmetric encryption information.
However, if you're determined to go down this road; the problem is that RSA-Encrypt always produces an output whose length is the key size. Your code is assuming 117 bytes in => 117 bytes out; and that's just not the case.
Since RSA-Decrypt expects that you have prepared the data with RSA-Encrypt, it reports "Bad Data" because you haven't given it a valid datagram (a valid one would have length equal to the keysize).
Related
Currently I am trying to implement a save function for my RSA key with the help of bouncycastle. I am running into problems if I try to save my public or private key encrypted and load it afterwards.
As a little example here the original public key:
305C300D06092A864886F70D0101010500034B00304802410096B4751049165D1E046063EA22E8FFA0F90AE1DD997A3876DA5F79C7DE97951F009AC9ACA3EB91114F8A32C04F48293B6665CD6DD5C406C81CD13270A2AB61130203010001
What I get after loading it (it adds 4 zeroes, bigger key means more zeroes added):
305C300D06092A864886F70D0101010500034B00304802410096B4751049165D1E046063EA22E8FFA0F90AE1DD997A3876DA5F79C7DE97951F009AC9ACA3EB91114F8A32C04F48293B6665CD6DD5C406C81CD13270A2AB611302030100010000
I found out it has something to do with my implementation of the symmetric encryption and the padding used there. Normal text no matter how long it is just works fine without extra data getting added.
This is the code I am using for my AES encryption:
Encryption
byte[] outputBytes = new byte[0];
AesEngine aesengine = new AesEngine();
CbcBlockCipher aesblockCipher = new CbcBlockCipher(aesengine);
PaddedBufferedBlockCipher aescipher = new PaddedBufferedBlockCipher(aesblockCipher);
KeyParameter aeskeyParameter = new KeyParameter(Hash.HashDataBlock(password, Hash.HashAlgorithm.SHA3).Bytes);
aescipher.Init(true, aeskeyParameter);
outputBytes = new byte[aescipher.GetOutputSize(inputBytes.Bytes.Length)];
int aeslength = aescipher.ProcessBytes(inputBytes.Bytes, outputBytes, 0);
aescipher.DoFinal(outputBytes, aeslength);
Decryption
byte[] inputBytes = input.Bytes;
byte[] outputBytes = new byte[0];
AesEngine aesengine = new AesEngine();
CbcBlockCipher aesblockCipher = new CbcBlockCipher(aesengine);
PaddedBufferedBlockCipher aescipher = new PaddedBufferedBlockCipher(aesblockCipher);
KeyParameter aeskeyParameter = new KeyParameter(Hash.HashDataBlock(password, Hash.HashAlgorithm.SHA3).Bytes);
aescipher.Init(false, aeskeyParameter);
outputBytes = new byte[aescipher.GetOutputSize(inputBytes.Length)];
int aeslength = aescipher.ProcessBytes(inputBytes, outputBytes, 0);
aescipher.DoFinal(outputBytes, aeslength);
My Functions to save and load the keys. The DataBlock class just converts data to needed formats like UTF8, Base64 or just byte arrays:
public static void SaveKeyEncrypted(DataBlock key, string path, DataBlock password)
{
StreamWriter sw = new StreamWriter(path);
DataBlock encrypted = SymmetricEncryption.Encrypt(key, password, SymmetricEncryption.SymmetricAlgorithms.AES);
sw.Write(encrypted.Base64);
sw.Close();
}
public static DataBlock ReadKeyEncrypted(string path, DataBlock password)
{
StreamReader sr = new StreamReader(path);
DataBlock readData = new DataBlock(sr.ReadLine(), DataBlock.DataType.Base64);
sr.Close();
return SymmetricEncryption.Decrypt(readData, password, SymmetricEncryption.SymmetricAlgorithms.AES);
}
For reproduction my other code that has to do with this problem:
public class DataBlock
{
private byte[] _data;
public DataBlock()
{
this._data = new byte[0];
}
public enum DataType
{
UTF8,
UTF7,
UTF32,
ASCII,
Unicode,
Hex,
Base64,
Base32
}
public DataBlock(string data, DataType dataType) : this()
{
switch (dataType)
{
case DataType.UTF8:
this._data = Encoding.UTF8.GetBytes(data);
break;
case DataType.UTF7:
this._data = Encoding.UTF7.GetBytes(data);
break;
case DataType.UTF32:
this._data = Encoding.UTF32.GetBytes(data);
break;
case DataType.ASCII:
this._data = Encoding.ASCII.GetBytes(data);
break;
case DataType.Unicode:
this._data = Encoding.Unicode.GetBytes(data);
break;
case DataType.Hex:
this._data = new byte[data.Length / 2];
for (int i = 0; i < data.Length; i += 2)
{
this._data[i / 2] = Convert.ToByte(data.Substring(i, 2), 16);
}
break;
case DataType.Base64:
this._data = Convert.FromBase64String(data);
break;
case DataType.Base32:
this._data = this.FromBase32String(data);
break;
}
}
public DataBlock(byte[] data)
{
this._data = data;
}
public string UTF8
{
get
{
return Encoding.UTF8.GetString(this._data);
}
}
public string UTF7
{
get
{
return Encoding.UTF7.GetString(this._data);
}
}
public string UTF32
{
get
{
return Encoding.UTF32.GetString(this._data);
}
}
public string ASCII
{
get
{
return Encoding.ASCII.GetString(this._data);
}
}
public string Unicode
{
get
{
return Encoding.Unicode.GetString(this._data);
}
}
public string Hex
{
get
{
return BitConverter.ToString(this._data).Replace("-", "");
}
}
public string Base64
{
get
{
return Convert.ToBase64String(this._data);
}
}
public string Base32
{
get
{
return this.ToBase32String(this._data);
}
}
public byte[] Bytes
{
get
{
return this._data;
}
}
private string ValidChars = "QAZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP";
private string ToBase32String(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
byte index;
int hi = 5;
int currentByte = 0;
while (currentByte < bytes.Length)
{
if (hi > 8)
{
index = (byte)(bytes[currentByte++] >> (hi - 5));
if (currentByte != bytes.Length)
{
index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index);
}
hi -= 3;
}
else if (hi == 8)
{
index = (byte)(bytes[currentByte++] >> 3);
hi -= 3;
}
else
{
index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3);
hi += 5;
}
sb.Append(ValidChars[index]);
}
return sb.ToString();
}
public byte[] FromBase32String(string str)
{
int numBytes = str.Length * 5 / 8;
byte[] bytes = new Byte[numBytes];
str = str.ToUpper();
int bit_buffer;
int currentCharIndex;
int bits_in_buffer;
if (str.Length < 3)
{
bytes[0] = (byte)(ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
return bytes;
}
bit_buffer = (ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
bits_in_buffer = 10;
currentCharIndex = 2;
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)bit_buffer;
bit_buffer >>= 8;
bits_in_buffer -= 8;
while (bits_in_buffer < 8 && currentCharIndex < str.Length)
{
bit_buffer |= ValidChars.IndexOf(str[currentCharIndex++]) << bits_in_buffer;
bits_in_buffer += 5;
}
}
return bytes;
}
}
Function to generate a keypair
public static DataBlock[] GenerateKeyPair(KeyPairSize keyPairSize)
{
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), (int) keyPairSize));
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
PrivateKeyInfo pkInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
DataBlock[] keyPairData = new DataBlock[2];
keyPairData[0] = new DataBlock(pkInfo.GetDerEncoded());
keyPairData[1] = new DataBlock(info.GetDerEncoded());
return keyPairData;
}
Code to reproduce the error:
DataBlock[] keyPair = AsymmetricEncryption.GenerateKeyPair(AsymmetricEncryption.KeyPairSize.Bits512);
DataBlock pass = new DataBlock("1234", DataBlock.DataType.UTF8);
DataBlock orig = new DataBlock("Hello World", DataBlock.DataType.UTF8);
DataBlock encrypted = AsymmetricEncryption.Encrypt(orig, keyPair[1]);
AsymmetricEncryption.SaveKeyEncrypted(keyPair[0], "D:\\privateenc", pass);
AsymmetricEncryption.SaveKeyEncrypted(keyPair[1], "D:\\publicenc", pass);
DataBlock privateKey = AsymmetricEncryption.ReadKeyEncrypted("D:\\privateenc", pass);
DataBlock publicKey = AsymmetricEncryption.ReadKeyEncrypted("D:\\publicenc", pass);
DataBlock decrypted = AsymmetricEncryption.Decrypt(encrypted, privateKey);
Console.WriteLine(decrypted.UTF8);
The encryption/decryption method is not needed because the error already happens after reading the encrypted key on my harddrive.
Why/where is the extra data added and how can I fix it?
I was able to fix it by adding the initial byte array length of the key to the encrypted text and read it later on. In the read function I cut everything after the original size of the key.
The main problem is still present and this is just a workaround.
I have a problem turning a RandomAccessStream into a float array. The float array contains values that are NaN. I can't tell if they come from the stream, the byte array or the float array. Performance & quality are important in this so if there is a better way to do this let me know.
Last count I was getting 122 NaN's.
thanks
private async void button_Click(object sender, RoutedEventArgs e)
{
string text = "this is text";
SpeechSynthesizer synthesizer = new SpeechSynthesizer();
SpeechSynthesisStream synthesisStream = await synthesizer.SynthesizeTextToStreamAsync(text);
Stopwatch watch = new Stopwatch();
watch.Start();
ProcessStream(synthesisStream.CloneStream());
watch.Stop();
// Performance is important
Debug.WriteLine(watch.Elapsed);
}
private async void ProcessStream(IRandomAccessStream stream)
{
// Create a buffer (somewhere to put the stream)
byte[] bytes = new byte[stream.Size];
// Add stream data to buffer (Following or After that) same result
// IBuffer x = await stream.ReadAsync(bytes.AsBuffer(), (uint)stream.Size, InputStreamOptions.None);
using (DataReader reader = new DataReader(stream))
{
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(bytes);
}
// Change buffer(in bytes) to a float array
float[] floatArray = MainPage.ConvertByteToFloat(bytes.ToArray());
int nanCount = 0;
for (var index = 0; index < floatArray.Length; index++)
{
float value = floatArray[index];
if (float.IsNaN(value))
{
nanCount++;
}
}
Debug.WriteLine("Nan count: " + nanCount);
}
public static float[] ConvertByteToFloat(byte[] array)
{
float[] floatArr = new float[array.Length / 4];
for (int i = 0; i < floatArr.Length; i++)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(array, i * 4, 4);
}
floatArr[i] = BitConverter.ToSingle(array, i * 4);
}
return floatArr;
}
Found the answer At this SO post
Basically I did not know that the 32 bit wav format stored its data in a 16 bit format.
I want to convert text into binary and after that, to try convert binary into string text.
How can i convert tobin back into text if it is already a string?
private void iTalk_Button_12_Click(object sender, EventArgs e)
{
ambiance_RichTextBox2.Text = tobin(ambiance_RichTextBox1.Text);
}
public string tobin(string inp)
{
StringBuilder sb = new StringBuilder();
foreach (char L in inp.ToCharArray())
{
sb.Append(Convert.ToString(L, 2).PadLeft(8, '0'));
}
return sb.ToString();
}
private void iTalk_Button_12_Click(object sender, EventArgs e)
{
ambiance_RichTextBox2.Text = BinaryToString(ambiance_RichTextBox1.Text);
//use what u need: BinaryToString or StringToBinary.
}
Convert String to Binary:
public static string StringToBinary(string data)
{
StringBuilder sb = new StringBuilder();
foreach (char c in data.ToCharArray())
{
sb.Append(Convert.ToString(c, 2).PadLeft(8, '0'));
}
return sb.ToString();
}
Convert Binary to String:
public static string BinaryToString(string data)
{
List<Byte> byteList = new List<Byte>();
for (int i = 0; i < data.Length; i += 8)
{
byteList.Add(Convert.ToByte(data.Substring(i, 8), 2));
}
return Encoding.ASCII.GetString(byteList.ToArray());
}
Good luck!
You can use like this,
public static string BinaryToString(string data)
{
List<Byte> byteList = new List<Byte>();
for (int i = 0; i < data.Length; i += 8)
{
byteList.Add(Convert.ToByte(data.Substring(i, 8), 2));
}
return Encoding.ASCII.GetString(byteList.ToArray());
}
Currently you are converting a char (which can be represented as a number) to its binary representation (this number is the ASCII number). But if you want to convert a string to binary, you should use an encoding. The encoding determines how the text is converted to binary.
For example:
static void Main(string[] args)
{
string input = "This is an example text.";
Console.WriteLine(input);
string asBin = ToBinary(input);
Console.WriteLine(asBin);
string asText = ToText(asBin);
Console.WriteLine(asText);
}
static string ToBinary(string input, System.Text.Encoding encoding = null)
{
if (encoding == null)
encoding = System.Text.Encoding.UTF8;
var builder = new System.Text.StringBuilder();
var bytes = encoding.GetBytes(input); // Convert the text to bytes using the encoding
foreach (var b in bytes)
builder.Append(Convert.ToString(b, 2).PadLeft(8, '0')); //Convert the byte to its binary representation
return builder.ToString();
}
static string ToText(string bytes, System.Text.Encoding encoding = null)
{
if (encoding == null)
encoding = System.Text.Encoding.UTF8;
var byteCount = 8;
var byteArray = new byte[bytes.Length / 8]; // An array for the bytes
for (int i = 0; i < bytes.Length / byteCount; i++)
{
var subBytes = bytes.Substring(i * byteCount, byteCount); // Get a subpart of 8 bits
var b = Convert.ToByte(subBytes.TrimStart('0'), 2); // Convert the subpart to a byte
byteArray[i] = b; // Add the byte to the array
}
return encoding.GetString(byteArray); // Convert the array to text using the right encoding.
}
Now if you want to use ASCII encoding, you can call the functions as follows:
Console.WriteLine(input);
string asBin = ToBinary(input, System.Text.Encoding.ASCII);
Console.WriteLine(asBin);
string asText = ToText(asBin, System.Text.Encoding.ASCII);
Console.WriteLine(asText);
To convert a string to binary
string s = "hai";
byte []arr = System.Text.Encoding.ASCII.GetBytes(s);
To convert binary to string
byte[] arr ;
string s = Encoding.ASCII.GetString(arr);
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 try to coding HMAC-SHA256 Algorithm as Function
HMAC (K,m) = H((K ⊕ opad) ∥ H((K ⊕ ipad) ∥ m))
where
H is a cryptographic hash function,
K is a secret key padded to the right with extra zeros to the input block size of the hash function, or the hash of the original key if it's longer than that block size,
m is the message to be authenticated,
∥ denotes concatenation,
⊕ denotes exclusive or (XOR),
opad is the outer padding (0x5c5c5c…5c5c, one-block-long hexadecimal constant),
ipad is the inner padding(0x363636…3636, one-block-long hexadecimal constant).
and this my code
public static string MyHMACHash(string key , string message)
{
Encoding encoding = Encoding.UTF8;
//var md = System.Security.Cryptography.MD5CryptoServiceProvider.Create();
SHA256 hash = SHA256Managed.Create();
byte[] trans_5C = new byte[32];
byte[] trans_36 = new byte[32];
byte[] b_key = encoding.GetBytes(key);
// TODO: also check if key is to short
if (b_key.Length > 32)
b_key = hash.ComputeHash(b_key);
for (int i = 0; i < 32; i++)
{
trans_5C[i] = 92;
trans_36[i] = 54;
if (i < key.Length)
{
trans_5C[i] ^= b_key[i];
trans_36[i] ^= b_key[i];
}
}
byte[] inner = hash.ComputeHash(trans_36.Concat(encoding.GetBytes(message)).ToArray());
var Fhash = hash.ComputeHash(trans_5C.Concat(inner).ToArray());
StringBuilder sb = new StringBuilder();
foreach (byte b in Fhash)
sb.Append(b.ToString("x2"));
string result = sb.ToString(); // = 9036a1a3f654aefeab426e9f7e17288e
return result;
}
but when i try to test this code the result Non-conforming to standard HMAC-SHA256 hashing on the standard internet web sites
Here is the modified version with custom HMAC generation. Main thing to consider is that Input Block Size referred in the K, is the hash algorithm block size; not returned hashed byte length. For SHA256, block size is 64 bytes. I believe you were using 32byte block size. You can find different block size references here: http://en.wikipedia.org/wiki/Secure_Hash_Algorithm.
public static string MyHMACHash(string key, string message)
{
Encoding encoding = Encoding.UTF8;
//Reference http://en.wikipedia.org/wiki/Secure_Hash_Algorithm
//SHA256 block size is 512 bits => 64 bytes.
const int HashBlockSize = 64;
var keyBytes = encoding.GetBytes(key);
var opadKeySet = new byte[HashBlockSize];
var ipadKeySet = new byte[HashBlockSize];
if (keyBytes.Length > HashBlockSize)
{
keyBytes = GetHash(keyBytes);
}
// This condition is independent of previous
// condition. If previous was true
// we still need to execute this to make keyBytes same length
// as blocksize with 0 padded if its less than block size
if (keyBytes.Length < HashBlockSize)
{
var newKeyBytes = new byte[HashBlockSize];
keyBytes.CopyTo(newKeyBytes, 0);
keyBytes = newKeyBytes;
}
for (int i = 0; i < keyBytes.Length; i++)
{
opadKeySet[i] = (byte)(keyBytes[i] ^ 0x5C);
ipadKeySet[i] = (byte)(keyBytes[i] ^ 0x36);
}
var hash = GetHash(ByteConcat(opadKeySet,
GetHash(ByteConcat(ipadKeySet, encoding.GetBytes(message)))));
// Convert to standard hex string
return hash.Select<byte, string>(a => a.ToString("x2"))
.Aggregate<string>((a, b) => string.Format("{0}{1}", a, b));
}
public static byte[] GetHash(byte[] bytes)
{
using (var hash = new SHA256Managed())
{
return hash.ComputeHash(bytes);
}
}
public static byte[] ByteConcat(byte[] left, byte[] right)
{
if (null == left)
{
return right;
}
if (null == right)
{
return left;
}
byte[] newBytes = new byte[left.Length + right.Length];
left.CopyTo(newBytes, 0);
right.CopyTo(newBytes, left.Length);
return newBytes;
}