How to chain-encrypt using AES CBC mode without writing to file - c#

I need to encrypt a large contiguous amount of bytes, not fitting into single byte array.
There are already a few similar questions, but none of the answers works for me:
stackoverflow.com/questions/5090233/how-to-encrpyt-decrypt-data-in-chunks
stackoverflow.com/questions/45735983/encrypting-files-with-aes-c-sharp-in-chunks
stackoverflow.com/questions/27645527/aes-encryption-on-large-files
Following code works if the output length is under limit (Key and IV are hardcoded for testing):
byte[] encrypt(byte[] input, byte[] iv = null)
{
var aes = new AesManaged();
aes.KeySize = 128;
aes.Key = new byte[16] { 0x0F, 0xD4, 0x33, 0x82, 0xF4, 0xDF,
0x62, 0xA5, 0x55, 0x7C, 0x6E, 0x92, 0xC5, 0x64, 0x67, 0xA9 };
aes.IV = (iv != null) ? iv :
new byte[16] { 0xB3, 0x87, 0xDA, 0xA0, 0x47, 0x7C,
0x52, 0x76, 0xCB, 0x3A, 0x69, 0x9B, 0x0F, 0x82, 0xAF, 0xA7 };
using (var stream = new MemoryStream()) // size limit is 2 GB
using (var cryptoStream = new CryptoStream(stream,
aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(input, 0, input.Length);
cryptoStream.FlushFinalBlock();
return stream.ToArray(); // max number of bytes = int.MaxValue
}
}
One alternative is to use FileStream instead of MemoryStream, but I prefer to get the results in memory.
I am trying to implement the chain operation, dividing the input on chunks of [multiples of AES BlockSize] and capture the encrypted output after feeding each chunk.
According to what I read, the last bytes of each result are supposed to be used as an IV for the next encryption, but that does not work. For simplicity, chunk size is equal to BlockSize:
byte[] subArray(byte[] source, int start, int length = -1)
{
if (length == -1) length = source.Length - start;
var target = new byte[length];
Buffer.BlockCopy(source, start, target, 0, length);
return target;
}
var encr0 = encrypt(subArray(input, 0, blockSize * 2));
var encr1 = encrypt(subArray(input, 0, blockSize));
var encr2 = encrypt(subArray(input, blockSize, blockSize),
subArray(encr1, blockSize, 16));
Assert.IsTrue(encr2.SequenceEqual(subArray(encr0, blockSize))); // FAILS
What I am doing wrong?

The encrypt() method uses the CBC mode by default. In this mode, the ciphertext of a block serves as IV of the following block (a random IV is applied for the first block).
In the posted code, decryption fails because an incorrect IV is used for encr2. The correct IV is subArray(encr1, 0, blockSize) (namely the ciphertext of the first block) and not subArray(encr1, blockSize, 16).
When this is fixed, the exception no longer occurs.
Another issue is that encrypt() implicitly performs PKCS#7 padding (the default). PKCS#7 padding pads even if the last plaintext block is completely filled. In this case, a full padding block (consisting of 16 0x10 values for AES) is appended.
The padding in encrypt() causes plaintext blocks that are not at the end to be incorrectly padded with a full padding block as well. This results in a wrong ciphertext block, as can be easily checked when encr0, encr1 and encr2 (e.g. hex encoded) are output.
To avoid these wrong padding blocks, the padding in encrypt() must be disabled for all plaintext blocks except the last one which is regulary padded (if it is incomplete it is filled, if it is complete, a full padding block is appended).
Edit: For arbitrary chunksizes (of the size of a multiple of the blocksize) subArray(encr1, chunkSize - blockSize, blockSize) must be applied as IV for encr1. If the chunksize is equal to the blocksize (as assumed here) this simplifies to subArray(encr1, 0, blockSize).

Related

Why to create a derived password of 256 bytes and later get bytes from result?

I have found an example that uses AES encrypt to encrypt text. The code is this:
public static string Encrypt(string PlainText, string Password,
string Salt = "Kosher", string HashAlgorithm = "SHA1",
int PasswordIterations = 2, string InitialVector = "OFRna73m*aze01xY",
int KeySize = 256)
{
if (string.IsNullOrEmpty(PlainText))
return "";
byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
byte[] SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
byte[] PlainTextBytes = Encoding.UTF8.GetBytes(PlainText);
PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
RijndaelManaged SymmetricKey = new RijndaelManaged();
SymmetricKey.Mode = CipherMode.CBC;
byte[] CipherTextBytes = null;
using (ICryptoTransform Encryptor = SymmetricKey.CreateEncryptor(KeyBytes, InitialVectorBytes))
{
using (MemoryStream MemStream = new MemoryStream())
{
using (CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write))
{
CryptoStream.Write(PlainTextBytes, 0, PlainTextBytes.Length);
CryptoStream.FlushFinalBlock();
CipherTextBytes = MemStream.ToArray();
MemStream.Close();
CryptoStream.Close();
}
}
}
SymmetricKey.Clear();
return Convert.ToBase64String(CipherTextBytes);
}
My question is: how is the key for the AES algorithm generated? These 2 lines:
PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
First, it creates a derived key of 256 bytes, and later, create a key getting pseudo random bytes of this derived key. It has to be divided by 8 because the AES algorithm need 128, 182 or 256 bits, not bytes. In this case, how derived key is 256 bytes, the key for AES will be 256 bits.
But why does it do that? Wouldn't it better create the derived key with the needed length, not 256 bytes but 256 bits (256 bytes / 8)? So It wouldn't be needed to create a new key taken the 1/8 bytes of the derived key.
Also, the getBytes() method, in the description of the method, it says it returns pseudo-random key bytes. So doesn't it do the AES key would be different in each case? How to generate again the AES key from decryption if it is pseudo random key bytes?
Thanks.
First, it creates a derived key of 256 bytes
Where? I don't see any 256-byte key being created.
and later, create a key getting pseudo random bytes of this derived key. It has to be divided by 8 because the AES algorithm need 128, 182 or 256 bits, not bytes
Yes, the function input of KeySize (which should be keySize by normal C# naming conventions) is in bits, but GetBytes wants an input in bytes. x / 8 is one of the three right answers for that conversion ((x + 7) / 8 is another, and x & 7 == 0 ? x / 8 : throw new ArgumentException(nameof(x)) is the third)
But why does it do that? Wouldn't it better create the derived key with the needed length, not 256 bytes but 256 bits (256 bytes / 8)? So It wouldn't be needed to create a new key taken the 1/8 bytes of the derived key.
It would be good to do that. But since it is already doing that, there's no "better" to be had.
Also, the getBytes() method, in the description of the method, it says it returns pseudo-random key bytes. So doesn't it do the AES key would be different in each case? How to generate again the AES key from decryption if it is pseudo random key bytes?
I have to make a pedantic point: There is no getBytes method. C# is a case-sensitive language, and the method name is GetBytes.
pseudorandom: noting or pertaining to random numbers generated by a definite computational process to satisfy a statistical test.
PasswordDeriveBytes is an implementation of PBKDF1 (except it continues beyond the limits of PBKDF1), which is a deterministic algorithm. Given the same inputs (password, seed, iteration count, pseudo-random function (hash algorithm)) the same output is produced. Change any of the inputs slightly, and the output is significantly different.
Rfc2898DeriveBytes (an implementation of PBKDF2) is also a deterministic, but chaotic, algorithm.
So you produce the same answer again in either of them (but not across them) by giving all the same inputs.
When using password-based encryption (PKCS#5) the flow is
Pick a PRF
Pick an iteration count
Generate a random salt
Write down these choices
Apply these three things, plus the password to generate a key
Encrypt the data
Write down to the encrypted data
When decrypting one
Read the PRF
Read the iteration count
Read the salt
Apply these three things, plus the password to generate a key
Read the encrypted data
Decrypt it
Party on
While this code is doing that part right, the IV and Salt should not be ASCII (or UTF8) strings, they should be "just bytes" (byte[]). If they need to be transported as strings then they should be base64, or some other "arbitrary" binary-to-text encoding.

How is an AES key processed when calling CryptoJS.AES.encrypt/decrypt with a non-standard key length?

I am currently having issues with decrypting items in C# that have been encrypted in CryptoJS using keys that are not of length 128, 192, or 256 bits. CryptoJS allows keys of 'odd' lengths to be used during encryption/decryption, but the symmetric algorithm classes in C# (such as RijndaelManaged) do not allow this.
Javascript
var key = CryptoJS.enc.Utf8.parse("This key is 160 bits"); // Not 128, 192, or 256 bits, but is allowed
var iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");
var result = CryptoJS.AES.encrypt("Encrypt Me!", key, { iv: iv });
// result.ciphertext = 2839aff89d889dd29480b038679fbd6e
// or result.ciphertext.toString(CryptoJS.enc.Base64) = KDmv+J2IndKUgLA4Z5+9bg==
C#
byte[] key = Encoding.UTF8.GetBytes("This key is 160 bits");
byte[] iv = { 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f };
byte[] encryptMe = Encoding.UTF8.GetBytes("Encrypt Me!");
using (RijndaelManaged rm = new RijndaelManaged())
{
rm.IV = iv;
rm.Key = key; //This is not allowed due to the key size being 160 bits. Exception thrown here.
using (ICryptoTransform ict = rm.CreateEncryptor())
{
byte[] encrypted = ict.TransformFinalBlock(encryptMe, 0, encryptMe.Length);
}
}
My question is what exactly happens to the key in the javascript code to allow it to be used for encryption/decryption? Padding? Truncation? Is the AES implementation in CryptoJS adjusted to work with 'odd' key lengths?
I have tried adjusting the C# code's key by truncating or padding (both the beginning and end) the byte array to no avail. I'm not terribly familiar with javascript syntax and have looked over the CryptoJS source without understanding much of what's happening.
I took a quick look at the CryptoJS sources, and it seems to be the case that it silently does something nonstandard. If this is true, there is no way to replicate what it does using standard AES.
However, to make sure there is no key derivation or such, try doing an alert(result.key); and see if it's the same as your input.

AES: encrypt using CCCrypt() method and decrypt using C#, the output is wrong

Use CCCrypt() method to encrypt, and then decrypt it in C#. But the output is not same as the original plain text.
The key is 256 bits long, and the IV is the default value.
The main codes is showing as below:
// Encrypt
{
// the key is 32 bytes (256 bits).
Byte iv[] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, // Operation
kCCAlgorithmAES128, // Algorithm
kCCOptionPKCS7Padding, // Option
keyPtr, // key
kCCKeySizeAES256, // key length
iv, /* initialization vector (optional) */
[self bytes], // plain text
dataLength, /* input */
buffer,
bufferSize, /* output */
&numBytesEncrypted); //dataOutMove
if (cryptStatus == kCCSuccess) {
NSData *encryptedData = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
NSString *encryptedString = [encryptedData base64Encoding];
}
// Decrypted
{
byte[] _key1 = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
public static string AESDecrypt(string encryptedString, string key)
{
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.BlockSize = 128;
aes.KeySize = 256;
aes.IV = _key1;
aes.Key = Encoding.UTF8.GetBytes(key);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// Convert Base64 strings to byte array
byte[] src = System.Convert.FromBase64String(encryptedString);
// decryption
using (ICryptoTransform decrypt = aes.CreateDecryptor())
{
byte[] dest = decrypt.TransformFinalBlock(src, 0, src.Length);
return Encoding.UTF8.GetString(dest);
}
}
}
EDIT:
I found the reason is the keyPtr. In the Encrypt process, I handle the key early like this:
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding) [key
getCString:keyPtr maxLength:sizeof(keyPtr)
encoding:NSUTF8StringEncoding];
Now, I modify those codes like this:
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger keyLength = [keyData length];
Byte *keyPtr= (Byte *)malloc(keyLength);
memcpy(keyPtr, [keyData bytes], keyLength);
Then I got the correct output.
Although the problem is gone, I really do not know what's wrong about the previous version.
Crypto is designed to fail badly if there is even a small error. You need to check explicitly that the keys are byte for byte the same (check bytes, not characters). The same for the IVs. You are decoding in CBC mode. Are you certain that the encryption is in CBC mode; it isn't set explicitly in your code. The same with padding. Are you certain that the encryption method is using PKCS7?
In general don't rely on default settings, but set them explicitly in your code.
As a last point, are you using the same byte <-> character conversions on both sides. Again it is better to explicitly state what you are using. For example, UTF-8 text may come with an initial BOM that a UTF-8 conversion will ignore, but a different conversion will include in the bytes.
I had the same problem with some code I was reviewing - where data encrypted by CCCrypt, and decrypted by another tool - did not result in the same plaintext. I tracked it down to a failure to generate the Key properly. It's this line:
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
If the CString version of "key" is longer than kCCKeySizeAES256 bytes, then key:getCString will return "NO" (because it won't fit in the buffer provided) and will not touch the buffer pointed to by keyPtr, resulting in a key that is effectively all NUL bytes (from the bzero function).
Your replacement code, using [key dataUsingEncoding] does not suffer this problem, as it will create a NSData object as big as needed to store the string. However, it WILL suffer the problem of a key not big enough, unless you can guarantee that the incoming key is long enough.
Using plain text keys is not recommended. If you have a plain text "passphrase", then use a Key derivation function to turn that into a binary sequence. The "gold standard" is PBKDF2, but even doing a SHA2-256 is better than feeding in plain text. SHA2-256 will give you 32 bytes. (SHA-1 is insufficient, as it's only 20 bytes).

Get the length of a CryptoStream in .Net

I'm working on software which encrypts/decrypts files.
I would like to be able to guess the length of the data after the encryption but I can't use CryptoStream.Length (It throws a NotSupportedException).
Is there any way to guess it ?
I'm using RijndaelManaged (.Net Framework 4.0)
This says it much better than I can
http://www.obviex.com/Articles/CiphertextSize.aspx
From there:
In the most generic case, the size of the ciphertext can be calculated as:
CipherText = PlainText + Block - (PlainText MOD Block)
where CipherText, PlainText, and Block indicate the sizes of the ciphertext, plaintext, and encryption block respectively. Basically, the resulting ciphertext size is computed as the size of the plaintext extended to the next block. If padding is used and the size of the plaintext is an exact multiple of the block size, one extra block containing padding information will be added.
Let's say that you want to encrypt a nine-digit Social Security Number (SSN) using the Rijndael encryption algorithm with the 128-bit (16-byte) block size and PKCS #7 padding. (For the purpose of the illustration, assume that dashes are removed from the SSN value before the encryption, so that "123-45-6789" becomes "123456789", and the value is treated as a string, not as a number.) If the digits in the SSN are defined as ASCII characters, the size of the ciphertext can be calculated as:
CipherText = 9 + 16 - (9 MOD 16) = 9 + 16 - 9 = 16 (bytes)
Notice that if the size of the plaintext value is the exact multiple of the block size, an extra block containing padding information will be appended to the ciphertext. For example, if you are to encrypt a 16-digit credit card number (defined as a 16-character ASCII string), the size of the ciphertext will be:
CipherText = 16 + 16 - (16 MOD 16) = 16 + 16 - 0 = 32 (bytes)
That depends on the cipher you use... usually the length is the same as the length of the original stream... worstcase is that it gets padded to a multiple of the block length of the cipher
This is my code with RijndaelManaged:
MemoryStream textBytes = new MemoryStream();
string password = #"myKey123"; // Your Key Here
UnicodeEncoding UE = new UnicodeEncoding();
byte[] key = UE.GetBytes(password);
FileStream fsInput = new FileStream(#"C:\myEncryptFile.txt", FileMode.Open);
RijndaelManaged RMCrypto = new RijndaelManaged();
CryptoStream cs = new CryptoStream(fsInput, RMCrypto.CreateDecryptor(key, key),
CryptoStreamMode.Read);
cs.CopyTo(textBytes);
cs.Close();
fsInput.Close();
string myDecriptText = Encoding.UTF8.GetString(textBytes.ToArray());
You can use this function to get both length and data.
public static int GetLength(CryptoStream cs, out byte[] data)
{
var bytes = new List<byte>();
int b;
while ((b = cs.ReadByte()) != -1)
bytes.Add((byte)b);
data = bytes.ToArray();
return data.Length;
}

C#: AES error: Padding is invalid and cannot be removed. Same key and everything, help

I'm quite new to C# so please be patient with me. I know this question was asked a lot if times, but I couldn't find an answer to my problem.
I'm saving some data and before writing it to a file I convert it to binary and store it in array, which I encrypt and then write to file. I encrypt data in chunks (32 bytes). In the same way I read data in chunks of 32 bytes and then decrypt that data and then this should repeat till the end of file. But when it comes to decryption the following error is thrown:
Padding is invalid and cannot be removed.
I use the same key and iv (hardcoded just until I get it working)
Here is my encryption code, which works without problems:
//result
byte[] data = new byte[32];
//setup encryption (AES)
SymmetricAlgorithm aes = Aes.Create();
byte[] key = { 145, 12, 32, 245, 98, 132, 98, 214, 6, 77, 131, 44, 221, 3, 9,50};
byte[] iv = { 15, 122, 132, 5, 93, 198, 44, 31, 9, 39, 241, 49, 250, 188, 80, 7 };
ICryptoTransform encryptor = aes.CreateEncryptor(key, iv);
FileStream fStream = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 1024, false);
//prepare data to write (byte array 'data') ...
//encrypt
MemoryStream m = new MemoryStream();
using (Stream c = new CryptoStream(m, encryptor, CryptoStreamMode.Write))
c.Write(data, 0, data.Length);
data = m.ToArray();
fStream.Write(data, 0, data.Length);
And here is my decryption code:
FileStream fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
//setup encryption (AES)
SymmetricAlgorithm aes = Aes.Create();
byte[] key = { 145, 12, 32, 245, 98, 132, 98, 214, 6, 77, 131, 44, 221, 3, 9, 50 };
byte[] iv = { 15, 122, 132, 5, 93, 198, 44, 31, 9, 39, 241, 49, 250, 188, 80, 7 };
ICryptoTransform decryptor = aes.CreateDecryptor(key, iv);
//result
byte[] data = new byte[32];
//loop for reading the whole file ...
int len = fStream.Read(data, 0, 32);
//decrypt
MemoryStream m = new MemoryStream();
using (Stream c = new CryptoStream(m, decryptor, CryptoStreamMode.Write))
c.Write(data, 0, data.Length); //The exception is thrown in this line
data = m.ToArray();
//using the decrypted data and then looping back to reading and decrypting...
I tried all I could think of (which is not much because I'm very new to cryptography), I searched everywhere and I couldn't find a solution to my problem. I also helped myself with the book C# in a Nutshell .
If anyone has ideas on why this could happen I'll be really thankful because I have no ideas.
Thank you for your time and answers.
EDIT:
It seems that the size of the encrypted data is 48 bytes (12 bytes more than the original). Why is that so? I thought that it only adds bytes if they are not a multiple of the block size (16 bytes, my data is 32 bytes). Is data always larger, and with constant increase (I need to know that in order to properly read and decrypt).
Note: I can't directly use other streams because I need to have control over the output format and I believe it is also safer and faster to encrypt in memory.
Based on your edit:
EDIT: It seems that the size of the encrypted data is 48 bytes (12 bytes more than the original). Why is that so? I thought that it only adds bytes if they are not a multiple of the block size (16 bytes, my data is 32 bytes). Is data always larger, and with constant increase (I need to know that in order to properly read and decrypt).
If the encrypted data is 48 bytes, thats 16 bytes larger than your original array. This makes sense because the algorithm with pad the data because the default is PKCS7 (even if the size matches the block size, because it pads to the next multiple of the block-size). If you wish to keep it exactly 32 bytes, just change the Padding to None
aes.Padding = PaddingMode.None;
You seem to be treating the length of the plaintext as the length of the ciphertext. That's not a safe assumption.
Why are you copying between FileStream and MemoryStream, you can pass a FileStream directly to the encryptor/decryptor.
In PKCS7, there is a minimum of one padding byte (to store the number of padding bytes). So the output size will be Ceil16(input.Length + 1), or (input.Length & ~15) + 1.
The short of it is that AES encrypts messages in blocks of 16 bytes. If your message isn't an even multiple of 16 bytes, the algorithm needs to be a little different for the last block; specifically, the last block must be "padded" with a value known to the algorithm as a padding value (usually zero, sometimes something else like a space character value).
You're doing that yourself, by putting the data into a fixed-length byte array. You padded the data yourself, but the decrypter is now attempting to de-pad the last block and getting byte values it doesn't recognize as the padding that its encrypter counterpart would have added.
The key is not to pad the message. You can use the BitConverter class to cast byte arrays to and from IConvertible types (value types and strings), and then use that instead of rolling your own byte array. Then, when you decrypt, you can read from the decryption stream up to the ciphertext length, but don't expect there to be that many actual bytes in the decrypted result.

Categories

Resources