Parallel + Stream + Encryption ... c# supposed to work - c#

Something very weird occur in my code.
If i create a simple for without parallel my code working fine.
But when I add Parallel.For it's no more working.
The problem is when it's rebuild the binary are not the same with the Parallel For but it's the same with the normal For ...
I create a sample for the example.
Essentially, the code only read a file, save it in chunck.
After "Simulate upload", just rebuild the file with the same Key and IV.
Like i said in the normal For everything working fine.
// Decryption
public static byte[] DecryptBinaryFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream())
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
{
csDecrypt.Write(cipherText, 0, cipherText.Length);
csDecrypt.Close();
}
return msDecrypt.ToArray();
}
}
}
// Encryption
public static byte[] EncryptBinary(byte[] binary, byte[] Key, byte[] IV)
{
// Check arguments.
if (binary == null || binary.Length <= 0)
throw new ArgumentNullException("Binary");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (BinaryWriter swEncrypt = new BinaryWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(binary);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
// Rebuild the file
static void RebuildFile(string directory, string file, int totalParts)
{
string finalName = string.Concat(directory, file);
using (FileStream fs = new FileStream(finalName, FileMode.Append))
{
for (int i = 0; i < totalParts; i++)
{
string fileName = $"{i.ToString("00000")}-{file}";
string path = Path.Combine(directory, fileName);
byte[] binary = DecryptBinaryFromBytes(File.ReadAllBytes(path), key, iv);
fs.Write(binary, 0, binary.Length);
}
fs.Close();
}
}
// Simulate upload
static void SimulateUpload(string filePath, byte[] binary)
{
binary = EncryptBinary(binary,key, iv);
using (FileStream fs = new FileStream(filePath, FileMode.Append))
{
fs.Write(binary, 0, binary.Length);
}
}
// variable declaration
static byte[] key;
static byte[] iv;
// The main method
static void Main(string[] args)
{
key = Guid.NewGuid().ToByteArray().Concat(Guid.NewGuid().ToByteArray()).ToArray();
iv = Guid.NewGuid().ToByteArray();
string inputFile = #"C:\temp\Fonctionnement-FR.docx";
string directory = #"C:\temp\uploads\";
const long BUFFER_SIZE = 524288;
byte[] buffer = new byte[BUFFER_SIZE];
FileInfo fInfo = new FileInfo(inputFile);
long totalBytes = fInfo.Length;
double estimateTotalChuck = Math.Ceiling((totalBytes / BUFFER_SIZE) + 0.5);
int totalParts = (int)estimateTotalChuck;
string fileName = Path.GetFileName(inputFile);
string fileExtension = Path.GetExtension(inputFile);
// This one is Working
for (int idx=0;idx<totalParts; idx++)
{
using (Stream input = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
{
using (MemoryStream output = new MemoryStream())
{
long startPosition = (idx * BUFFER_SIZE);
long maxPosition = startPosition + BUFFER_SIZE;
int maxBufferRead = (int)BUFFER_SIZE;
input.Seek(startPosition, SeekOrigin.Begin);
if (maxPosition > totalBytes)
{
maxBufferRead = (int)(totalBytes - startPosition);
}
input.Read(buffer, 0, maxBufferRead);
output.Write(buffer, 0, maxBufferRead);
SimulateUpload(string.Concat(directory, $"{idx.ToString("00000")}-{fileName}"), output.ToArray());
// upFile = UploadSingleFile(endPoint, new FileArgs(output.ToArray(), fileName, fileExtension, idx, totalParts), handShake, chunkSize, idx, totalParts, fileIdentifier);
}
}
}
// This one is not working
Parallel.For(0, (int)estimateTotalChuck, new ParallelOptions { MaxDegreeOfParallelism = 5 }, idx =>
{
using (Stream input = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
{
using (MemoryStream output = new MemoryStream())
{
long startPosition = (idx * BUFFER_SIZE);
long maxPosition = startPosition + BUFFER_SIZE;
int maxBufferRead = (int)BUFFER_SIZE;
input.Seek(startPosition, SeekOrigin.Begin);
if (maxPosition > totalBytes)
{
maxBufferRead = (int)(totalBytes - startPosition);
}
input.Read(buffer, 0, maxBufferRead);
output.Write(buffer, 0, maxBufferRead);
SimulateUpload(string.Concat(directory, $"{idx.ToString("00000")}-{fileName}"), output.ToArray());
// upFile = UploadSingleFile(endPoint, new FileArgs(output.ToArray(), fileName, fileExtension, idx, totalParts), handShake, chunkSize, idx, totalParts, fileIdentifier);
}
}
});
RebuildFile(directory, fileName, totalParts);
}

When you switch to parallel code, you need to be acutely aware of where all locals are declared; anything declared outside the parallel region will be shared by all the parallel threads. In this case, it looks like buffer is a key contender, and since that is the primary data exchange mechanism, it would be entirely expected that if multiple threads are reading and writing to buffer concurrently, that they're all virtually guaranteed to trip over each-other.
But: check all the other locals too! If in doubt: move the code that runs in parallel to a separate method. It is hard to trip over shared locals in that scenario. Note that for ref-type parameters (such as byte[]), you'd also need to ensure that the objects are separate - separate locals to the same object has the same problem.

Related

The input data is not a complete block when decrypting AES with specific offset?

I'm trying to write an encryption/decryption method for practice and after getting an initial run working, I decided to step it up and make it less vulnerable by encrypting the IV into the data. I got that working, and decided to step it up again by introducing an offset for the IV's location in the data, by adding some random data to the left hand side of the IV. Up until this point, everything was working fine, but now I'm receiving an error on decryption that states:
The input data is not a complete block.
Which with my limited knowledge of encryption and decryption is quite useless to me in debugging the issue. I've searched high and low, and none of the answers to this problem, that I've found seem to fix my issue. The answers are typically along the lines of the developer isn't decrypting a byte[] but instead something like a base 64 string.
private static Guid TestGuid = Guid.NewGuid();
private static DateTime Timestamp = DateTime.Now;
private static string key = "PPPQmyuzqKtjzYlWM3mP0aDxaxCzlsACajIkTVN4IjI=";
public static void Main()
{
string data = TestGuid + "|" + Timestamp;
Console.WriteLine("Request Parameter: " + data);
string encryptedData = AESEncrypt(key, data, 1);
Console.WriteLine("Encrypted: " + encryptedData);
string decryptedData = AESDecrypt(key, encryptedData, 1);
Console.WriteLine("Decrypted: " + decryptedData);
}
public static string AESEncrypt(string key, string data, int offset)
{
if (string.IsNullOrWhiteSpace(data))
throw new ArgumentException("Data");
byte[] encryptedData;
byte[] keyData = Convert.FromBase64String(key);
using (Aes algo = Aes.Create())
{
algo.Key = keyData;
algo.GenerateIV();
algo.Padding = PaddingMode.PKCS7;
byte[] iv = new byte[offset + 16];
Random r = new Random();
using (MemoryStream ms = new MemoryStream())
{
for (int i = 0; i < offset; i++)
iv[i] = (byte)r.Next(1, 200);
for (int i = 0; i < algo.IV.Length; i++)
iv[offset + i - 1] = algo.IV[i];
ICryptoTransform encryptor = algo.CreateEncryptor(algo.Key, algo.IV);
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (BinaryWriter bw = new BinaryWriter(cs))
{
bw.Write(iv, 0, offset);
ms.Write(iv, offset, algo.IV.Length);
bw.Write(data);
cs.FlushFinalBlock();
}
encryptedData = ms.ToArray();
}
}
}
if (encryptedData != null)
return Convert.ToBase64String(encryptedData);
throw new Exception("An unxpected error occurred and the provided data was not encrypted.");
}
public static string AESDecrypt(string key, string data, int offset)
{
if (string.IsNullOrWhiteSpace(data))
throw new ArgumentException("Data");
string decryptedData;
byte[] keyData = Convert.FromBase64String(key);
using (Aes algo = Aes.Create())
{
algo.Key = keyData;
algo.Padding = PaddingMode.PKCS7;
byte[] decodedData = Convert.FromBase64String(data);
using (MemoryStream ms = new MemoryStream(decodedData))
{
byte[] ivData = new byte[offset + 16];
ms.Read(ivData, 0, offset + 16);
List<byte> iv = new List<byte>();
for (int i = offset - 1; i < ivData.Length - 1; i++)
iv.Add(ivData[i]);
algo.IV = iv.ToArray();
ICryptoTransform decryptor = algo.CreateDecryptor(algo.Key, algo.IV);
List<byte> dataToDecrypt = new List<byte>();
for (int i = 0; i + offset < decodedData.Length; i++)
dataToDecrypt.Add(decodedData[i + offset]);
using (MemoryStream ds = new MemoryStream(dataToDecrypt.ToArray()))
{
using (CryptoStream cs = new CryptoStream(ds, decryptor, CryptoStreamMode.Read))
{
using (StreamReader sr = new StreamReader(cs))
{
decryptedData = sr.ReadToEnd();
}
}
}
}
}
if (!string.IsNullOrWhiteSpace(decryptedData))
return decryptedData;
throw new Exception("An unxpected error occurred and the provided data was not decrypted.");
}
What is causing this error, why is it causing the error, how do I resolve the error, and why does that resolution work?
Messing with the final encryption stream during the encryption process (feeding into it raw bytes) is a fatal mistake and should be avoided
The problem in question occurs because
bw.Write(iv, 0, offset);
ms.Write(iv, offset, algo.IV.Length);
Feeding the first random bytes of the modified IV to the encryption stream and the rest of it to the raw output stream, the first bytes of the modified iv will not be written to the memory stream immediately and instead will be part of the first encryption block, thus, the size of the cipher during decryption will lack some bytes, for example it lacks 1 byte where offset = 1
But you expect them to be written immediately as independent bytes, because in the decryption part you read offset + 16 bytes from the stream and thus you read into the encrypted block and cause it to be less than the block size for AES. You can see this if you debug the code. The final size of the encrypted bytes is 0x50 while the size of bytes for decryption is 0x50 - offset = 0x4f (offset = 1)
For solution,
You can derive the IV from the Key (which is not secure if you are reusing the key) and will not have to include it in your cipher.
You can prepend the IV (and your random bytes) to your encrypted buffer and read it first then use it for decryption,
like:
public static string AESEncrypt(string key, string data, int offset)
{
if (string.IsNullOrWhiteSpace(data))
throw new ArgumentException("Data");
byte[] encryptedData;
byte[] keyData = Convert.FromBase64String(key);
using (Aes algo = Aes.Create())
{
algo.Key = keyData;
algo.GenerateIV();
algo.Padding = PaddingMode.PKCS7;
Random r = new Random();
using (MemoryStream ms = new MemoryStream())
{
for (int i = 0; i < offset; i++)
{
ms.WriteByte((byte)r.Next(0, 200));
}
ms.Write(algo.IV, 0, 16);
ICryptoTransform encryptor = algo.CreateEncryptor(algo.Key, algo.IV);
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (BinaryWriter bw = new BinaryWriter(cs))
{
bw.Write(data);
cs.FlushFinalBlock();
}
encryptedData = ms.ToArray();
}
}
}
if (encryptedData != null)
return Convert.ToBase64String(encryptedData);
throw new Exception("An unxpected error occurred and the provided data was not encrypted.");
}
public static string AESDecrypt(string key, string data, int offset)
{
if (string.IsNullOrWhiteSpace(data))
throw new ArgumentException("Data");
string decryptedData;
byte[] keyData = Convert.FromBase64String(key);
using (Aes algo = Aes.Create())
{
algo.Key = keyData;
algo.Padding = PaddingMode.PKCS7;
byte[] decodedData = Convert.FromBase64String(data);
using (MemoryStream ms = new MemoryStream(decodedData))
{
for (int i = 0; i < offset; i++) ms.ReadByte();
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
algo.IV = iv.ToArray();
ICryptoTransform decryptor = algo.CreateDecryptor(algo.Key, algo.IV);
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (StreamReader sr = new StreamReader(cs))
{
decryptedData = sr.ReadToEnd();
}
}
}
}
if (!string.IsNullOrWhiteSpace(decryptedData))
return decryptedData;
throw new Exception("An unxpected error occurred and the provided data was not decrypted.");
}

Encode CryptoStream to Base64 String in Chunks in C#

There is a method (Version1) that encodes an input stream and there is a function Decrypt() that successfully decodes encoded data. But when the input data is large there could be an error OutOfMemory (on the line "string textEncrypted = Convert.ToBase64String (ms.ToArray())").
Version1
private static Stream EncryptRijndael1(byte[] key, byte[] iv, Stream plainText)
{
if (plainText == null)
return null;
byte[] bytesEncrypted;
RijndaelManaged rjndl = RijndaelManagedWithConfig(key, iv);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, rjndl.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
byte[] buffer = new byte[16 * 1024];
int readed;
while ((readed = (plainText.Read(buffer, 0, buffer.Length))) > 0)
{
cs.Write(buffer, 0, readed);
}
}
string textEncrypted = Convert.ToBase64String(ms.ToArray());
bytesEncrypted = Encoding.ASCII.GetBytes(textEncrypted);
}
return new MemoryStream(bytesEncrypted);
}
So I modified the method to process an array part by part (chunks).
Here is Version2. It causes an error "offset and length must refer to a position in the string" in the line Convert.ToBase64String (ms.ToArray(), offset, read).
Version2
private static Stream EncryptRijndael2(byte[] key, byte[] iv, Stream plainText)
{
if (plainText == null)
return null;
byte[] bytesEncrypted;
RijndaelManaged rjndl = RijndaelManagedWithConfig(key, iv);
string textEncrypted = String.Empty;
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, rjndl.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
byte[] buffer = new byte[16 * 1024];
int readed;
int offset = 0;
while ((readed = (plainText.Read(buffer, 0, buffer.Length))) > 0)
{
cs.Write(buffer, 0, readed);
textEncrypted += Convert.ToBase64String(ms.ToArray(), offset, readed);
offset += readed;
}
}
bytesEncrypted = Encoding.ASCII.GetBytes(textEncrypted);
}
return new MemoryStream(bytesEncrypted);
}
Then I made Version3. It works without errors but the output data length now is bigger than in Version1 having the same input data.
Decryt() function throws an error "The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters."
Version3
private static Stream EncryptRijndael3(byte[] key, byte[] iv, Stream plainText)
{
if (plainText == null)
return null;
byte[] bytesEncrypted;
RijndaelManaged rjndl = RijndaelManagedWithConfig(key, iv);
using (MemoryStream ms = new MemoryStream())
{
string textEncrypted = String.Empty;
using (CryptoStream cs = new CryptoStream(ms, rjndl.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
byte[] buffer = new byte[16*1024];
int readed;
while ((readed = (plainText.Read(buffer, 0, buffer.Length))) > 0)
{
cs.Write(buffer, 0, readed);
}
}
byte[] buffer1 = new byte[16*1024];
int readed1;
using (MemoryStream ms1 = new MemoryStream(ms.ToArray()))
{
while ((readed1 = (ms1.Read(buffer1, 0, buffer1.Length))) > 0)
{
if (readed1 < buffer1.Length)
{
var lastBuf = new List<Byte>();
for (int i = 0; i < readed1; i++)
{
lastBuf.Add(buffer1[i]);
}
textEncrypted += Convert.ToBase64String(lastBuf.ToArray());
continue;
}
textEncrypted += Convert.ToBase64String(buffer1);
}
}
bytesEncrypted = Encoding.ASCII.GetBytes(textEncrypted);
}
return new MemoryStream(bytesEncrypted);
}
My RijndaelManaged
private static RijndaelManaged RijndaelManagedWithConfig(byte[] key, byte[] iv)
{
RijndaelManaged rjndl = new RijndaelManaged();
rjndl.KeySize = 256;
rjndl.BlockSize = 128;
rjndl.Key = key;
rjndl.IV = iv;
rjndl.Mode = CipherMode.CBC;
rjndl.Padding = PaddingMode.PKCS7;
return rjndl;
}
Please help me to get rid of the errors or tell me how to make the Version1 process Convert.ToBase64String data partially.
I have achieved the decision
private static Stream EncryptRijndael(byte[] key, byte[] iv, Stream plainText)
{
if (plainText == null)
return null;
byte[] buffer = new byte[5120 * 1024];
RijndaelManaged rjndl = RijndaelManagedWithConfig(key, iv);
using (var memoryStream = new MemoryStream())
{
int readedBytes;
using (var cs = new CryptoStream(memoryStream, rjndl.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
while ((readedBytes = (plainText.Read(buffer, 0, buffer.Length))) > 0)
{
cs.Write(buffer, 0, readedBytes);
}
}
using (var cryptoMemoryStream = new MemoryStream(memoryStream.ToArray()))
{
using (var base64MemoryStream = new MemoryStream())
{
using (ICryptoTransform transform = new ToBase64Transform())
{
using (var cryptStream = new CryptoStream(base64MemoryStream, transform, CryptoStreamMode.Write))
{
while ((readedBytes = cryptoMemoryStream.Read(buffer, 0, buffer.Length)) > 0)
{
cryptStream.Write(buffer, 0, readedBytes);
}
cryptStream.FlushFinalBlock();
}
return new MemoryStream(base64MemoryStream.ToArray());
}
}
}
}
}

Cant get plain text back from AES decryption c#

I'm trying to create a CTF challenge that requires reverse engineering.
So basically I need to encrypt a string so I know the encrypted value of a plain text. In this case "xor_flag". Once I have that, I will erase the plain text value, and just use the decrypt function. But for now, I just need to get the encrpytion working. The closest thing I have working is this: which displays the plain text in hex value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
namespace XorFlag2
{
class Program
{
// USe de4dot on the .exe and use .net reflector on cleaned one,after de4dot finishes.
// IN reflector go to main method
//num var is what you need . For xoring you can use windows calculator(->scientific mode)
// so: num xor 53129566096 must be = 65535655351
// num is = 65535655351 xor 53129566096 (=13371337255)
// start program type: 13371337255 ,hit enter and you 'll get the flag
private static void Main(string[] args)
{
Console.WriteLine("Greetings challenger! Step right up and try your shot at gaining the flag!");
Console.WriteLine("You'll have to know the pascode to unlock the prize:");
long num = Convert.ToInt64(Console.ReadLine());
if ((num ^ 53129566096L) == 65535655351L)
{
Console.WriteLine("yay");
}
else
{
Console.WriteLine("Incorrect, try again!");
}
byte[] iV = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 };
byte[] array = new byte[16];
BitConverter.GetBytes(num).CopyTo(array, 0);
BitConverter.GetBytes(num).CopyTo(array, 8);
string s = "ls/5RTxwflDrqr5G8pO9cQ1NlgQcFjcJj9x4z7oIhlfY4w42GAFqKbyzwqHAZQBZa5ctysKKWIbTgU2VxoR​YohxCbPyV6sEU/tn+sIxNg6A/r5OJnIMqTs0seMrzWh5J";
string t = "flag_xor";
byte[] encrypted = EncryptStringToBytes(t, array, iV);
Console.WriteLine(BitConverter.ToString(encrypted));
// str = DecryptStringFromBytes(Convert.FromBase64String(t), array, iV);
// Console.WriteLine(str);
// catch (Exception exception)
//{
// Console.WriteLine("ERROR!!! darn. huh? how did I get here? Hmm, something must have gone wrong. What am I doing?", exception);
//}
Console.WriteLine("press key to continue");
Console.ReadKey();
}
static string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
rijAlg.Padding = PaddingMode.None;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
}
}

AES256 Encryption/Decryption in both NodeJS and C#

I've taken some liberties with the results of the following questions:
AES encrypt in .NET and decrypt with Node.js crypto?
Decrypting AES256 encrypted data in .NET from node.js - how to obtain IV and Key from passphrase
C# version of OpenSSL EVP_BytesToKey method?
And created the following class file...
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace T1.CoreUtils.Utilities
{
public static class CryptoUtility
{
public static string Encrypt(string input, string passphrase = null)
{
byte[] key, iv;
DeriveKeyAndIV(Encoding.ASCII.GetBytes(passphrase), null, 1, out key, out iv);
return Convert.ToBase64String(EncryptStringToBytes(input, key, iv));
}
public static string Decrypt(string inputBase64, string passphrase = null)
{
byte[] key, iv;
DeriveKeyAndIV(Encoding.ASCII.GetBytes(passphrase), null, 1, out key, out iv);
return DecryptStringFromBytes(Convert.FromBase64String(inputBase64), key, iv);
}
private static void DeriveKeyAndIV(byte[] data, byte[] salt, int count, out byte[] key, out byte[] iv)
{
List<byte> hashList = new List<byte>();
byte[] currentHash = new byte[0];
int preHashLength = data.Length + ((salt != null) ? salt.Length : 0);
byte[] preHash = new byte[preHashLength];
System.Buffer.BlockCopy(data, 0, preHash, 0, data.Length);
if (salt != null)
System.Buffer.BlockCopy(salt, 0, preHash, data.Length, salt.Length);
MD5 hash = MD5.Create();
currentHash = hash.ComputeHash(preHash);
for (int i = 1; i < count; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
hashList.AddRange(currentHash);
while (hashList.Count < 48) // for 32-byte key and 16-byte iv
{
preHashLength = currentHash.Length + data.Length + ((salt != null) ? salt.Length : 0);
preHash = new byte[preHashLength];
System.Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
System.Buffer.BlockCopy(data, 0, preHash, currentHash.Length, data.Length);
if (salt != null)
System.Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + data.Length, salt.Length);
currentHash = hash.ComputeHash(preHash);
for (int i = 1; i < count; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
hashList.AddRange(currentHash);
}
hash.Clear();
key = new byte[32];
iv = new byte[16];
hashList.CopyTo(0, key, 0, 32);
hashList.CopyTo(32, iv, 0, 16);
}
static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
static string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
}
From here, I generated the following via node:
var crypto = require('crypto');
var input = "This is î╥≤ what it is.";
var passkey= "This is my password.";
var cipher = crypto.createCipher('aes-256-cbc', passkey);
var encrypted = cipher.update(input, 'utf8', 'base64') + cipher.final('base64');
encrypted
// '9rTbNbfJkYVE2m5d8g/8b/qAfeCU9rbk09Na/Pw0bak='
input = "I am the walrus, coo coo cachoo!";
passkey = "I am a ≥ò'ÿ boy baby!";
cipher = crypto.createCipher('aes-256-cbc', passkey);
encrypted = cipher.update(input, 'utf8', 'base64') + cipher.final('base64');
// 'j/e+f5JU5yerSvO7FBJzR1tGro0Ie3L8sWYaupRW1JJhraGqBfQ9z+h85VhSzEjD'
var decipher = crypto.createDecipher('aes-256-cbc', passkey);
var plain = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8');
plain
// 'I am the walrus, coo coo cachoo!'
From this, I create the following test case:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace T1.CoreUtils.Test.Utilities.Tests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void EncryptReturnsExpectedValue1_unicode_in_plaintext()
{
var passkey = "This is my password.";
var plain = "This is î╥≤ what it is.";
var encrypted = "9rTbNbfJkYVE2m5d8g/8b/qAfeCU9rbk09Na/Pw0bak=";
var actual = T1.CoreUtils.Utilities.CryptoUtility.Encrypt(plain, passkey);
Assert.AreEqual(encrypted, actual);
}
[TestMethod]
public void EncryptReturnsExpectedValue2_unicode_in_passkey()
{
var passkey = "I am a ≥ò'ÿ boy baby!";
var plain = "I am the walrus, coo coo cachoo!";
var encrypted = "j/e+f5JU5yerSvO7FBJzR1tGro0Ie3L8sWYaupRW1JJhraGqBfQ9z+h85VhSzEjD";
var actual = T1.CoreUtils.Utilities.CryptoUtility.Encrypt(plain, passkey);
Assert.AreEqual(encrypted, actual);
}
[TestMethod]
public void DecryptReturnsExpectedValue1()
{
var passkey = "This is my password.";
var plain = "This is î╥≤ what it is.";
var encrypted = "9rTbNbfJkYVE2m5d8g/8b/qAfeCU9rbk09Na/Pw0bak=";
var actual = T1.CoreUtils.Utilities.CryptoUtility.Decrypt(encrypted, passkey);
Assert.AreEqual(plain, actual);
}
[TestMethod]
public void DecryptReturnsExpectedValue2()
{
var passkey = "I am a ≥ò'ÿ boy baby!";
var plain = "I am the walrus, coo coo cachoo!";
var encrypted = "j/e+f5JU5yerSvO7FBJzR1tGro0Ie3L8sWYaupRW1JJhraGqBfQ9z+h85VhSzEjD";
var actual = T1.CoreUtils.Utilities.CryptoUtility.Decrypt(encrypted, passkey);
Assert.AreEqual(plain, actual);
}
}
}
Passes:
EncryptReturnsExpectedValue1_unicode_in_plaintext
DecryptReturnsExpectedValue1
Fails:
EncryptReturnsExpectedValue2_unicode_in_passkey
DecryptReturnsExpectedValue2
I can only guess that the issue is in the DeriveKeyAndIV method. Will try a few different approaches and answer if I find it on my own.
Okay, upon inspecting the node.js source for crypto, I determined, that the encoding was using a new Buffer(passkey, 'binary'), which was only using the original value xand 0xFF for the bytes used, so I created a matching method in C#... here's the method in question...
private static byte[] RawBytesFromString(string input)
{
var ret = new List<Byte>();
foreach (char x in input)
{
var c = (byte)((ulong)x & 0xFF);
ret.Add(c);
}
return ret.ToArray();
}
And the updated/working CryptoUtil.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace T1.CoreUtils.Utilities
{
public static class CryptoUtility
{
/* Wanting to stay compatible with NodeJS
* http://stackoverflow.com/questions/18502375/aes256-encryption-decryption-in-both-nodejs-and-c-sharp-net/
* http://stackoverflow.com/questions/12261540/decrypting-aes256-encrypted-data-in-net-from-node-js-how-to-obtain-iv-and-key
* http://stackoverflow.com/questions/8008253/c-sharp-version-of-openssl-evp-bytestokey-method
*
* var cipher = crypto.createCipher('aes-256-cbc', 'passphrase');
* var encrypted = cipher.update("test", 'utf8', 'base64') + cipher.final('base64');
*
* var decipher = crypto.createDecipher('aes-256-cbc', 'passphrase');
* var plain = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8');
*/
public static string Encrypt(string input, string passphrase = null)
{
byte[] key, iv;
DeriveKeyAndIV(RawBytesFromString(passphrase), null, 1, out key, out iv);
return Convert.ToBase64String(EncryptStringToBytes(input, key, iv));
}
public static string Decrypt(string inputBase64, string passphrase = null)
{
byte[] key, iv;
DeriveKeyAndIV(RawBytesFromString(passphrase), null, 1, out key, out iv);
return DecryptStringFromBytes(Convert.FromBase64String(inputBase64), key, iv);
}
private static byte[] RawBytesFromString(string input)
{
var ret = new List<Byte>();
foreach (char x in input)
{
var c = (byte)((ulong)x & 0xFF);
ret.Add(c);
}
return ret.ToArray();
}
private static void DeriveKeyAndIV(byte[] data, byte[] salt, int count, out byte[] key, out byte[] iv)
{
List<byte> hashList = new List<byte>();
byte[] currentHash = new byte[0];
int preHashLength = data.Length + ((salt != null) ? salt.Length : 0);
byte[] preHash = new byte[preHashLength];
System.Buffer.BlockCopy(data, 0, preHash, 0, data.Length);
if (salt != null)
System.Buffer.BlockCopy(salt, 0, preHash, data.Length, salt.Length);
MD5 hash = MD5.Create();
currentHash = hash.ComputeHash(preHash);
for (int i = 1; i < count; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
hashList.AddRange(currentHash);
while (hashList.Count < 48) // for 32-byte key and 16-byte iv
{
preHashLength = currentHash.Length + data.Length + ((salt != null) ? salt.Length : 0);
preHash = new byte[preHashLength];
System.Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
System.Buffer.BlockCopy(data, 0, preHash, currentHash.Length, data.Length);
if (salt != null)
System.Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + data.Length, salt.Length);
currentHash = hash.ComputeHash(preHash);
for (int i = 1; i < count; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
hashList.AddRange(currentHash);
}
hash.Clear();
key = new byte[32];
iv = new byte[16];
hashList.CopyTo(0, key, 0, 32);
hashList.CopyTo(32, iv, 0, 16);
}
static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged cipher = new RijndaelManaged())
{
cipher.Key = Key;
cipher.IV = IV;
//cipher.Mode = CipherMode.CBC;
//cipher.Padding = PaddingMode.PKCS7;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = cipher.CreateEncryptor(cipher.Key, cipher.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
static string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (var cipher = new RijndaelManaged())
{
cipher.Key = Key;
cipher.IV = IV;
//cipher.Mode = CipherMode.CBC;
//cipher.Padding = PaddingMode.PKCS7;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = cipher.CreateDecryptor(cipher.Key, cipher.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
}
NOTE: Some more code related to this...
https://github.com/tracker1/T1.CoreUtils/blob/master/T1.CoreUtils/Utilities/CryptoUtility.cs
https://github.com/tracker1/t1-coreutils-node/blob/master/lib/hashutils.js
These are not in nuget or npm respectively as they really don't belong there... it's mainly for ideas and reference. I do need to flush out the node side a bit better so it matches up.

AES Encryption C#

EDIT: Added my hash code to the bottom of this.
I am having some problems creating a message integrity key for a solution I am creating. In order for this to be correct I need to use the following settings.
Mode: ECB
KeySize: 256
BlockSize: 128
Padding: PKCS7
I am using a 32 byte key which is generated from a file and also a blank IV as I understand ECB does not require one.
My problem I am expecting a 48 byte output from this before the encoding however I am receiving a 64 byte output.
I have shown some code below about how am I am trying to achieve this but I am not having much success.
public static string Encrypt(string hash) {
// Create a new instance of the AesManaged
// class. This generates a new key and initialization
// vector (IV).
using (AesManaged myAes = new AesManaged()) {
myAes.Key = File.ReadAllBytes("keyfile");
myAes.Mode = CipherMode.ECB;
myAes.IV = new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
myAes.KeySize = 256;
myAes.BlockSize = 128;
myAes.Padding = PaddingMode.PKCS7;
// Encrypt the string to an array of bytes.
byte[] encrypted = EncryptStringToBytes_Aes(hash, myAes.Key, myAes.IV);
// Decrypt the bytes to a string.
string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);
//Display the original data and the decrypted data.
Console.WriteLine("Original: {0}", hash);
Console.WriteLine("Round Trip: {0}", roundtrip);
// Encode
string encoded = Convert.ToBase64String(encrypted);
Console.WriteLine("Encoded: {0}", encoded);
return encoded;
}
}
static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV) {
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged()) {
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
public static string getHashSha256(string text) {
byte[] bytes = Encoding.UTF8.GetBytes(text);
SHA256Managed hashstring = new SHA256Managed();
byte[] hash = hashstring.ComputeHash(bytes);
string hashString = string.Empty;
foreach (byte x in hash) {
hashString += String.Format("{0:x2}", x);
}
return hashString;
}
PKCS #7 padding is defined such that padding is added in all cases. When the plaintext is a multiple of the block size, a whole block of padding is added. This is why the ciphertext is 64 bytes long when the plaintext is 48 bytes long.

Categories