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'm trying to implement AES 128 CTR encryption in c#. I've found Bouncy Castle is very useful. Here is my code:
public class AESCrypto
{
private byte[] Key = new byte[16];
private byte[] IV = new byte[16];
private const int CHUNK_SIZE = 16;
private IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
// Key and IV I get from client.
public AESCrypto(byte[] key, byte[] iv, bool forEncryption) {
Key = key;
IV = iv;
cipher.Init(forEncryption, new ParametersWithIV(new KeyParameter(Key), IV));
}
public byte[] PerformAES(byte[] incomingBytes)
{
int blockCount = incomingBytes.Length / CHUNK_SIZE; // Number of blocks
int blockRemaining = incomingBytes.Length % CHUNK_SIZE; // Remaining bytes of the last block
byte[] outcomingBytes = new byte[incomingBytes.Length];
for (var i = 0; i < blockCount; i++)
{
// Why do I need to re-init it again?
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
byte[] temp = new byte[CHUNK_SIZE];
Array.Copy(incomingBytes, i * CHUNK_SIZE, temp, 0, CHUNK_SIZE);
byte[] decryptedChunk = cipher.ProcessBytes(temp);
Array.Copy(decryptedChunk, 0, outcomingBytes, i * CHUNK_SIZE, CHUNK_SIZE);
//Increase(IV); Why do I need to increse iv by hand?
}
if (blockRemaining != 0)
{
// Why do I need to re-init it again?
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
byte[] temp = new byte[blockRemaining];
Array.Copy(incomingBytes, incomingBytes.Length - blockRemaining, temp, 0, blockRemaining);
byte[] decryptedChunk = cipher.DoFinal(temp);
Array.Copy(decryptedChunk, 0, outcomingBytes, incomingBytes.Length - blockRemaining, blockRemaining);
//Increase(IV); Why do I need to increse iv by hand?
}
return outcomingBytes;
}
private void Increase(byte[] iv)
{
for (var i = 0; i < iv.Length; i++)
{
iv[i]++;
if (iv[i] != 0)
{
break;
}
}
}
}
At first glance, this code should work fine. But it does not. Pay attention to commented-out-lines:
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
and
//Increase(IV); Why do I need to increase iv by hand?
Only if I uncomment them my code works fine.
I'm wondering why I have to increase the counter manually? Or I made a mistake somewhere in set-up in the constructor? I'm not very familiar with Bouncy Castle.
P.S. I'm using BC 1.8.6.1 version from Nuget.
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-----
MIIJKQIBAAKCAgEA7drrGdj9TY6MZCm3kuCsXKVD2v5kUS38MCtA5PjGp72/A1IO
izG51WjyvCkULjBBQzOr366TcO5HtMzmVlipDzIFJdeQN2Z1gY5oVqA04zGlEAf4
vcgM0ygJLEbtcAYMZz/5Y5RgPqAnv3J3TiB82mbXURVg2PvHp7c+0Rl8vZmC4RIq
+DDkxXfccOFh8eaY4VYfIZPHsZZeX6ih+6JbuReCKU7zVPUMspefOyaMNj+NFeVN
XgKbtcerABpxE1ckWUsNVuw+GHBpJ0MCmBcfNYtMxZv8FZLdNziLsn0moN8RNC29
PRa+Kmt4OPTA+YXBQt8Q4f8K/OHDlKc80XdcrfNG4SMLaLJpdmbVRumDEmHx2dV+
6AHR+SwjkBRdkdWIg0JSmmraEcrKvgXz+ZkVfXbTWAbB5+Mq4ZT+BF0cRRoag13K
U0uaAdhM99zn2Qshdam1Vj++rfTAlaZx+xvx0C92gkC7qC4SmJlnGGisAN8NzYKo
DXX5cV/R5x3w5I98fXBIw0y1NNBlUuEd8OT9wDwvcvA4Y+vpjUPprXScgJcTL72i
x0/BIRDNP3zfBhmKG6XR1MW9dO38pWbn/iS6I2i2jzqnL+mYKQQ35sjvonkkrOpi
amh7V4T1e+H3n8LWBSPSgkKUk936+LouxLzJ4vEeOEg2C/V4T8obrdlCaqMCAwEA
AQKCAgApocrKwGc9vvilw4OFKtwgbzDcUPCgIOtmRvvZ2A11aMnZO/CdvntndjIe
axZEK2AQ8idgRH88IgjdBYw/is80gK3T/NIaUE26+oEawHnhVlws3ShVl4FfKD/K
xzNiCzz6iYEOQ/dAnum2IcPuIdOYqq1/XL2R3SgKHBHbqZli2k7FNFffDzfLtHoa
K+jn3VPfBSL3zpUCaW5lUe/gSn/BevLmZhJDSY0KaW2OfeXGzQLV1Ufgb5Zvj95H
a1llaDhNhMx17W3E+0/8dkcq9ckZpyMt52qNICKmOriA6lTrjX/GYUchPSzV4e+u
EHECe73jBYY/+FMlBiMkjs0fYMQQvAOMF5g/A7pv/dfxiaiMIC84wKRbh8GGfKh/
Ropwa84F9no6fUF9jFhDxzDjxIzsrJOF0C/nPl2vKb3qSkhhQMQSAJhLmyMJ/BHo
CUCWDkE9cRQ+YYrXYycfsSOvr5j4XmztSBm0JgqZm2JMfMR21kw9hTzef99EHEFx
N8fLCz6jcqwycrKqZo7+oiWQrCnARurQT8uauCxpdVnsjZCpgIs9HyybBg4YtUCu
hoQzUzPHU3dUAUhEPVweUfM8qDjCekMLXDAjXKtoWwA3A3m6Jmjy64dJIfso3CLY
KGauIukTGOl8mu4ZDpEiPVE4NLao6S22mITgLnfGnb3zRU4w0QKCAQEA+b8Y8S13
vvMpUiwGGC9bdPTnyVTpt3RSrYBjojStOf1yfnrbUVomEt2qEYCbq6a3eBfSbA6w
whpxPsZvrjQudUKQH9VqCE18q0sUJzOVu/kO+KR2P+8EAFPDoA+KXc1pxWBADoIs
mnVfM8xjI0BR6Dhyk6GXagUFdS7dS8PHEy8pRa1weZQ67IpYpl8etxfCtGY8W0p9
hXXn3adt4da9ga8XAZFdfn0e/NM+b05btHqotC/w+nRDw283ymB43Cfq3IPAVWFe
Jv/2A9eUsyEHhL7SxWSEOyYq+gFGLpnb0S/Sy91wQtiL54vD5wCadec3STs3dnsC
Yo9pTxaPE103ewKCAQEA88+Yoj+h0KMkesN3ozIQq0Gd5IEGvwQs7dgWzCYarTVf
cbBm76IXu0cf+UGZv/rDjSJC56IzN7e9GSfYs5QHteL64ce8nnM3JxLNs5eq9B6c
VcBMwWhQkyIbJwD2hxfs7u8wEvVjNkRjMoAKvo8ne2r5Mjxc5qsFVdYLd64RTK8y
Qn52+ximTq3CNdTI/Z3w1uA9SR6sff5PjPhymgsIcM7NZ7Fmi12cddQdUwtkIaNP
hWblJE2N+CFJUn2wArWOPrlYKcZ3KCSHZVOIWFca9nidDUOaZ4eAsbP/LKQp/LEF
MkAn7Wsy3Rmlj6NuGabNUEzhexW9sBn/BJH8WrIc+QKCAQEAgbQRTBAFBJJcf2SF
tcHCibc3OYRz7ObomVr4Y6Ff5aIO+Ejt5g4ff+THElfsgPUQi7ozehMXEXeSILwF
/D71ccij+SRo8O7tNDjFuqY7uWfbsp4XG5USSuk1y8bGYXjw2aTnH6HTcFRMoSYg
xon8/9FxD+L4NANvljBElbiThw8TLDCrHTkycO5Yo+76kLQyVmZSKkdBcTKOvLrb
glJ4EQXRuOq515s7oKpE3qGfVtftDcdoK2p+Vt1H6D66BfxlKSjzlmP+9dow9kXb
4DvjH7nK1OEjG2TzJOvMex9E3hssKtxSFSVJY3NexnW1wk3WlJ3AbDPuRSmd04kv
vSrISQKCAQEAlZ5+EoNuL/UN+/BcSN/+brozxPiRRUOwtrz3MIzprgWk7sXMRZ55
Zco+Ct6BFdkzjDbMTA2z1KuC9h8H0xwypyIFx+ylCa+21tmpNl8K4Aiw88aw07fK
SqCRfRwQLdM26WILZHcGTVUmcuU0ssBzAEAjcPquIDgva/+Qxf8iSqbw9vFY3rq/
xGTJW/Oa7FiyZYry0R5ryF36P45v9axzn5apYsrxHPFzhLOI01+YMTRhJoKAeAjH
6M+0iVTsYJ0+D6v6OJi8ovvXwwzCDURXHY3jAzLLXGFBTswg+io8Qf/4KmBIoGA6
tIh6m201sbQ1JuQnMzuiTqGFaC6WaKoJMQKCAQBkA5UMKy0Yp3Pec+cWJMWAEdEm
mqScwgrJJnhss5MYUUk4RsypMgdLCn56K1KC3fHuirYZ4xtGt7UlAkNUy2IU1/aP
m+V7w3vRVnDxF0bd/YR6hYlZu8p8jK4noigZJ9DFJO310Ln+jdDq/YZ3m7ntax3e
bQPvPK3Yn7U0vCocT7fQ44Pquxp9qlIApomH5GJ42kcIrCJ+O2YYx6gWgO2GuGCI
Q/g2WdDdXwAfxCMxSkAKi4q3BV6KvkdbbDq/8aMy5o71ePonDm84Ipom9PUDxqwG
dbrB6/PfYXo81POZPbCbeir31AjycePSSBk4sjb7AES+18MvXy3sgn2dHesF
-----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.
I am having problems to extract a string from a memory stream. The memory stream is decorated with a crypto stream. It appears, unless I flush the crypto stream, I cannot read anything from the memory stream.
I am trying to generate multiple strings (which will then be parsed to numerical values) within a for-loop. So far I can't read from a memory stream while the crypto stream is still active.
As one can see, I am trying to measure the runtime but the length of my memory stream always appears to be zero. I also need to find an efficient way to get a string from the encrypted byte array which i extract from the memory stream.
Stopwatch watch = new Stopwatch();
MemoryStream ms = new MemoryStream();
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
UTF8Encoding encoder = new UTF8Encoding();
int counter = (int)numericUpDown2.Value;
byte[] text;
byte[] num;
watch.Start();
for (int k = 0; k < rounds; k++) {
text = encoder.GetBytes(counter.ToString());
cs.Write(text, 0, text.Length);
cs.FlushFinalBlock();
num = new byte[ms.Length];
ms.Read(num, 0, (int)num.Length);
ms.Flush();
counter++;
}
watch.Stop();
A simple example based on what I wrote in the comment:
var lst = new List<string> {
"Foo",
"Bar",
"FooBarFooBarFooBarFooBar",
"FooBar",
};
MemoryStream ms = new MemoryStream();
var aesInstance = Aes.Create();
foreach (var str in lst)
{
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] encrypted = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);
byte[] length = BitConverter.GetBytes(encrypted.Length);
ms.Write(length, 0, length.Length);
ms.Write(encrypted, 0, encrypted.Length);
}
ms.Position = 0;
while (ms.Position < ms.Length)
{
ICryptoTransform decryptor = aesInstance.CreateDecryptor(aesInstance.Key, aesInstance.IV);
byte[] length = new byte[4];
int read = ms.Read(length, 0, length.Length);
if (read < length.Length)
{
throw new Exception();
}
int length2 = BitConverter.ToInt32(length, 0);
byte[] encrypted = new byte[length2];
read = ms.Read(encrypted, 0, encrypted.Length);
if (read < encrypted.Length)
{
throw new Exception();
}
byte[] decrypted = decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length);
string str = Encoding.UTF8.GetString(decrypted);
Console.WriteLine("Encrypted: {0} bytes, value: {1}", encrypted.Length, str);
}
The length of each encrypted "packet" is prepended as an Int32 to the packet. If you watch the output, you'll see that for the strings given the length of the packet is always 16 or 32. For longer strings it will go up by 16 at a time (48, 64, 80, 96...). Note that there is a bug with the CBC mode, so you can't correctly TransformFinalBlock twice or you'll get an error on decryption. To solve this I'm recreating the encryptor/decryptor for each string. This will cause equal strings to be encrypted in the same way. So if you encrypt "Foo" twice, they will be the same XXXXXXXXXYYYYYYYY in the encrypted stream.
CTR Mode
As I wrote in the comments, the best thing would be to have a CTR mode. The CTR mode has the advantage that the encrypted stream has the same length as the non-encrypted stream, and the input stream can be encrypted/decrypted one byte at a time. Using these two "characteristics" we can modify the encryption/decryption sample to encrypt/decrypt even the string length. Note that in the AesCtr class I've added some tests based on the vectors from http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, so the implementation should be correct.
public class AesManagedCtr : Aes
{
private AesManaged Aes;
public AesManagedCtr()
{
Aes = new AesManaged();
Aes.Mode = CipherMode.ECB;
Aes.Padding = PaddingMode.None;
}
public override byte[] IV
{
get
{
return Aes.IV;
}
set
{
Aes.IV = value;
}
}
public override byte[] Key
{
get
{
return Aes.Key;
}
set
{
Aes.Key = value;
}
}
public override int KeySize
{
get
{
return Aes.KeySize;
}
set
{
Aes.KeySize = value;
}
}
public override CipherMode Mode
{
get
{
return Aes.Mode;
}
set
{
if (value != CipherMode.ECB)
{
throw new CryptographicException();
}
}
}
public override PaddingMode Padding
{
get
{
return Aes.Padding;
}
set
{
if (value != PaddingMode.None)
{
throw new CryptographicException();
}
}
}
public override int BlockSize
{
get
{
return 8;
}
set
{
if (value != 8)
{
throw new CryptographicException();
}
}
}
public override KeySizes[] LegalBlockSizes
{
get
{
return new[] { new KeySizes(BlockSize, BlockSize, 0) };
}
}
public override int FeedbackSize
{
get
{
return Aes.FeedbackSize;
}
set
{
if (FeedbackSize != Aes.FeedbackSize)
{
throw new CryptographicException();
}
}
}
public override ICryptoTransform CreateDecryptor()
{
// Note that we always use the Aes.CreateEncryptor, even for
// decrypting, because we only have to "rebuild" the encrypted
// CTR nonce.
return CreateEncryptor();
}
public override ICryptoTransform CreateDecryptor(byte[] key, byte[] iv)
{
// Note that we always use the Aes.CreateEncryptor, even for
// decrypting, because we only have to "rebuild" the encrypted
// CTR nonce.
return CreateEncryptor(key, iv);
}
public override ICryptoTransform CreateEncryptor()
{
return new StreamCipher(Aes.CreateEncryptor(), IV);
}
public override ICryptoTransform CreateEncryptor(byte[] key, byte[] iv)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
if (!ValidKeySize(key.Length * 8))
{
throw new ArgumentException("key");
}
if (iv == null)
{
throw new ArgumentNullException("iv");
}
if (iv.Length * 8 != BlockSizeValue)
{
throw new ArgumentException("iv");
}
return new StreamCipher(Aes.CreateEncryptor(key, iv), iv);
}
public override void GenerateIV()
{
Aes.GenerateIV();
}
public override void GenerateKey()
{
Aes.GenerateKey();
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
Aes.Dispose();
}
}
finally
{
base.Dispose(disposing);
}
}
protected sealed class StreamCipher : ICryptoTransform
{
private ICryptoTransform Transform;
private byte[] IV;
private byte[] EncryptedIV = new byte[16];
private int EncryptedIVOffset = 0;
public StreamCipher(ICryptoTransform transform, byte[] iv)
{
Transform = transform;
// Note that in this implementation the IV/Nonce and the
// Counter described by http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
// are additioned together in a single IV, that then is
// incremented by 1 in a "big-endian" mode.
IV = (byte[])iv.Clone();
Transform.TransformBlock(IV, 0, IV.Length, EncryptedIV, 0);
}
public bool CanReuseTransform
{
get { return true; }
}
public bool CanTransformMultipleBlocks
{
get { return true; }
}
public int InputBlockSize
{
get { return 1; }
}
public int OutputBlockSize
{
get { return 1; }
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
int count = Math.Min(inputCount, outputBuffer.Length - outputOffset);
for (int i = 0; i < count; i++)
{
if (EncryptedIVOffset == EncryptedIV.Length)
{
IncrementNonceAndResetOffset();
}
outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ EncryptedIV[EncryptedIVOffset]);
EncryptedIVOffset++;
}
return count;
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
// This method can be reused. There is no "final block" in
// CTR mode, because characters are encrypted one by one
byte[] outputBuffer = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, 0);
return outputBuffer;
}
public void Dispose()
{
if (Transform != null)
{
Transform.Dispose();
Transform = null;
IV = null;
EncryptedIV = null;
}
GC.SuppressFinalize(this);
}
private void IncrementNonceAndResetOffset()
{
int i = IV.Length - 1;
do
{
unchecked
{
IV[i]++;
}
if (IV[i] != 0 || i == 0)
{
break;
}
i--;
}
while (true);
Transform.TransformBlock(IV, 0, IV.Length, EncryptedIV, 0);
EncryptedIVOffset = 0;
}
}
// A simple string-to-byte[] converter
private static byte[] GetBytes(string str)
{
if (str.Length % 2 != 0)
{
throw new ArgumentException();
}
byte[] bytes = new byte[str.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = byte.Parse(str.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
return bytes;
}
public static void Test()
{
// Taken from http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
// F.5.1 CTR-AES128.Encrypt
// F.5.2 CTR-AES128.Decrypt
// F.5.3 CTR-AES192.Encrypt
// F.5.4 CTR-AES192.Decrypt
// F.5.5 CTR-AES256.Encrypt
// F.5.6 CTR-AES256.Decrypt
string[] keys = new[]
{
// 128 bits
"2b7e151628aed2a6abf7158809cf4f3c",
// 192 bits
"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
// 256 bits
"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
};
string iv = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
string[] plains = new[]
{
"6bc1bee22e409f96e93d7e117393172a",
"ae2d8a571e03ac9c9eb76fac45af8e51",
"30c81c46a35ce411e5fbc1191a0a52ef",
"f69f2445df4f9b17ad2b417be66c3710",
};
string[][] encrypteds = new[]
{
// 128 bits
new[]
{
"874d6191b620e3261bef6864990db6ce",
"9806f66b7970fdff8617187bb9fffdff",
"5ae4df3edbd5d35e5b4f09020db03eab",
"1e031dda2fbe03d1792170a0f3009cee",
},
// 192 bits
new[]
{
"1abc932417521ca24f2b0459fe7e6e0b",
"090339ec0aa6faefd5ccc2c6f4ce8e94",
"1e36b26bd1ebc670d1bd1d665620abf7",
"4f78a7f6d29809585a97daec58c6b050",
},
// 256 bits
new[]
{
"601ec313775789a5b7a7f504bbf3d228",
"f443e3ca4d62b59aca84e990cacaf5c5",
"2b0930daa23de94ce87017ba2d84988d",
"dfc9c58db67aada613c2dd08457941a6",
},
};
for (int i = 0; i < keys.Length; i++)
{
var aes = new AesManagedCtr();
aes.Key = GetBytes(keys[i]);
aes.IV = GetBytes(iv);
Console.WriteLine("{0} bits", aes.KeySize);
{
Console.WriteLine("Encrypt");
ICryptoTransform encryptor = aes.CreateEncryptor();
var cipher = new byte[16];
for (int j = 0; j < plains.Length; j++)
{
byte[] plain = GetBytes(plains[j]);
encryptor.TransformBlock(plain, 0, plain.Length, cipher, 0);
string cipherHex = BitConverter.ToString(cipher).Replace("-", string.Empty).ToLowerInvariant();
if (cipherHex != encrypteds[i][j])
{
throw new Exception("Error encrypting " + j);
}
Console.WriteLine(cipherHex);
}
}
Console.WriteLine();
{
Console.WriteLine("Decrypt");
ICryptoTransform decryptor = aes.CreateDecryptor();
var plain = new byte[16];
for (int j = 0; j < encrypteds[i].Length; j++)
{
byte[] encrypted = GetBytes(encrypteds[i][j]);
decryptor.TransformBlock(encrypted, 0, encrypted.Length, plain, 0);
string plainHex = BitConverter.ToString(plain).Replace("-", string.Empty).ToLowerInvariant();
if (plainHex != plains[j])
{
throw new Exception("Error decrypting " + j);
}
Console.WriteLine(plainHex);
}
}
Console.WriteLine();
}
}
}
And then
var lst = new List<string> {
"Foo",
"Bar",
"FooBarFooBarFooBarFooBar",
"FooBar",
};
MemoryStream ms = new MemoryStream();
var aesInstance = new AesManagedCtr();
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
foreach (var str in lst)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] length = BitConverter.GetBytes(bytes.Length);
byte[] encryptedLength = encryptor.TransformFinalBlock(length, 0, length.Length);
byte[] encryptedBytes = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);
ms.Write(encryptedLength, 0, encryptedLength.Length);
ms.Write(encryptedBytes, 0, encryptedBytes.Length);
}
ms.Position = 0;
ICryptoTransform decryptor = aesInstance.CreateDecryptor(aesInstance.Key, aesInstance.IV);
while (ms.Position < ms.Length)
{
byte[] encryptedLength = new byte[4];
int read = ms.Read(encryptedLength, 0, encryptedLength.Length);
if (read < encryptedLength.Length)
{
throw new Exception();
}
byte[] length = decryptor.TransformFinalBlock(encryptedLength, 0, encryptedLength.Length);
int length2 = BitConverter.ToInt32(length, 0);
byte[] encryptedBytes = new byte[length2];
read = ms.Read(encryptedBytes, 0, encryptedBytes.Length);
if (read < encryptedBytes.Length)
{
throw new Exception();
}
byte[] bytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
string str = Encoding.UTF8.GetString(bytes);
Console.WriteLine("Encrypted: {0} bytes, value: {1}", encryptedBytes.Length, str);
}
Note the differences with the other example: here we reuse the encryptor/decryptor, because in this way every "block" is encrypted in a chain, and even if the same string is repeated twice, the encrypted version will be different.
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);
};