I am in the process of writing a small encoding class which takes a byte[] array, and cyphers it. As the IV can be made public, I wish to prepend it to the ciphertext to use for decryption.
Following the MSDN Documentation and some SO Post source (Cant find link), I started the stream with the raw IV;
//Start encryption process
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
using (var swWriter = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swWriter.Write(encryptor.IV);
swWriter.Write(plainTextRaw);
}
}
Console.WriteLine("Encrypted Value: {0}", BitConverter.ToString(msEncrypt.ToArray()).Replace("-", String.Empty));
return msEncrypt.ToArray();
}
However, upon writing a simple unit test extracting the first 16 bytes, they do not seem to match.
I am certain the IV is being encrypted within the stream, so where is the best place to inject it?
Just write the IV tot the initial memory stream msEncrypt, not to the stream that is being encrypted swWriter.
Related
I'm trying to encrypt/decrypt a string with AES, using streams. I'm using the following code for encryption:
var provider = Aes.Create();
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using var encryptor = provider.CreateEncryptor();
using var memoryStream = new MemoryStream();
using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
using var streamWriter = new StreamWriter(cryptoStream, Encoding.UTF8);
streamWriter.Write(plainText);
cryptoStream.FlushFinalBlock();
var cipher = memoryStream.ToArray();
This successfully produces a byte array, though no matter the plaintext length, the cipher is always 16 bytes. From my understanding, with a block size of 16, a plaintext string with a length of 16 or more should result in a cipher that is larger than 16 bytes. Also, even for plaintext that is less than 16 bytes, decryption always results in an empty string.
var provider = Aes.Create();
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using var decryptor = _provider.CreateDecryptor(key, iv);
using var memoryStream = new MemoryStream(cipher);
using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
using var streamReader = new StreamReader(cryptoStream, Encoding.UTF8);
var plainText = streamReader.ReadToEnd();
My code is based on this sample in the Microsoft docs, though I'm calling cryptoStream.FlushFinalBlock(), after writing to the stream, although this isn't working as desired.
Calling FlushFinalBlock is not really necessary if you correctly close the stream. It might be useful if you want to write the last block (including padding) without closing it.
However, using the generic streaming API, preferably with the using statement and closing the stream should write any bytes left in the buffer + any padding that could be required.
Of course you should include any stream that writes to the CryptoStream in that using statement, otherwise they may have leftover data. Of course the receiving stream should only be closed after the data has been retrieved, for instance in case a MemoryStream is used.
I'm trying to write some straight forward encryption routines. Here's what I've been able to come up with based on searching the Web.
public string Encrypt(string plainText)
{
byte[] encrypted;
// Create an AesCryptoServiceProvider object
// with the specified key and IV.
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
msEncrypt.WriteByte((byte)aesAlg.Key.Length);
msEncrypt.Write(aesAlg.Key, 0, aesAlg.Key.Length);
msEncrypt.WriteByte((byte)aesAlg.IV.Length);
msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);
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 Convert.ToBase64String(encrypted);
}
public string Decrypt(string cipherText)
{
string plaintext = null;
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(cipherText)))
{
int l = msDecrypt.ReadByte();
byte[] key = new byte[l];
msDecrypt.Read(key, 0, l);
l = msDecrypt.ReadByte();
byte[] IV = new byte[l];
msDecrypt.Read(IV, 0, l);
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(key, IV);
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;
}
Two questions:
First, most of the examples I found hard coded the Key and IV. So what I'm doing is writing it to the encrypted bytes. This will make my encrypted data larger. Is there a better way?
Also, I'm not using any password. Would one use a password to generate a custom Key? And, if so, how would I know how long that key needed to be?
First, most of the examples I found hard coded the Key and IV. So what I'm doing is writing it to the encrypted bytes. This will make my encrypted data larger. Is there a better way?
Obviously you should not write the key to the unprotected stream, as the key needs to be shared or established in advance and remain secret. This sharing of the secret key can be performed in many ways, ranging from key agreement to key derivation, ratcheting, etc. etc.
Also, I'm not using any password. Would one use a password to generate a custom Key? And, if so, how would I know how long that key needed to be?
That's a possibility. However, remind yourself that passwords are often not that strong, so if password based encryption (PBE) can be avoided, it may be a good idea to do so.
If you derive a key from a password, you should use a Password Based Key Derivation Function (also sometimes called a password hash). In C# there is an implementation of PBKDF2 (badly) called Rfc2898DeriveBytes. By now that's not very state of the art either, but it should suffice - if you set a high enough iteration count anyway.
When you derive a key from a human remembered password then 128 bits is plenty. There is almost no way that the key can be found easier than the password that was used to derive it.
I am compressing a file using GZip and then encrypting using AES.
When I step through the code, I see that the magic number (1f 8b) is present in the intermediate compressed unencrypted data and then the file is encrypted. When I go to decrypt the file, the intermediate decrypted compressed data does not contain the magic number and the GZipStream fails when decompressing it.
Here is the main code:
private static void CompressThenEncrypt(string inputFileName, string outputFileName, ICryptoTransform encryptor)
{
using (var inputFileStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read))
{
using (var outputFileStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write))
using (var cryptoStream = new CryptoStream(outputFileStream, encryptor, CryptoStreamMode.Write))
{
using (var gZipStream = new GZipStream(cryptoStream, CompressionMode.Compress))
{
inputFileStream.CopyTo(gZipStream);
}
}
}
}
private static void DecryptThenDecompress(string inputFileName, string outputFileName, ICryptoTransform decryptor)
{
using (var inputFileStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read))
{
using (var cryptoStream = new CryptoStream(inputFileStream, decryptor, CryptoStreamMode.Read))
using (var gZipStream = new GZipStream(cryptoStream, CompressionMode.Decompress))
using (var outputFileStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write))
{
gZipStream.CopyTo(outputFileStream);
}
}
}
Source file is 19000 bytes. When compressed it becomes 603 bytes (with magic number), then encrypted it becomes 608 bytes (due to padding). When decrypted it becomes 603 bytes (no magic number) and I simply can not get beyond this point.
Here is the calling code:
using (var aes = new AesCryptoServiceProvider())
{
ICryptoTransform encryptor = aes.CreateEncryptor();
ICryptoTransform decryptor = aes.CreateDecryptor();
CompressThenEncrypt(OriginalFileName, CompressThenEncryptFileName, encryptor);
DecryptThenDecompress(CompressThenEncryptFileName, DecryptThenDecompressFileName, decryptor);
}
Edit: More Info
Compressed Data (603 bytes):
1F-8B-08-00-00-00-00-00-04-00-ED-CA-B1-0D-00-20-08-00-C1-DE-C4-21-51-64-FF-11-DC-80-84-FE-9A-6F-FE-E2-DC-7C-15-6D-F7-EA-3F-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-D4-44-7D-3F-A6-A4-8A-30-E6-02-00
Encrypted Data (608 bytes):
C3-02-64-A9-15-15-52-A9-9F-2A-62-EF-89-B2-9A-E0-72-9A-BA-E2-A2-C8-35-BA-3C-CF-D7-07-8B-DC-D4-63-65-AF-A8-62-22-E5-CC-C6-7D-6F-9B-64-57-9F-C0-94-75-E7-6C-A9-DB-B3-57-29-01-95-F5-B9-E5-3D-FD-AB-EC-E3-AC-AF-EB-A6-C0-81-B3-47-4A-EB-F6-DD-03-DF-92-0A-82-D9-DD-4E-46-DF-55-2E-EB-34-AC-98-1A-7E-A9-25-94-2D-E8-32-4B-F9-2A-61-64-CB-09-9C-D5-8C-A2-0A-C1-22-90-98-93-26-A6-9F-69-F8-EE-6E-95-96-56-28-71-3B-94-1E-5F-50-DB-15-DF-C6-46-3F-04-57-5E-0B-47-44-BB-13-9F-14-08-FB-87-E6-97-65-1B-CA-50-52-7F-10-5D-AF-CC-2F-5E-D0-39-A6-C4-70-3B-90-5F-63-EA-F0-59-46-9E-99-2D-31-34-66-5D-72-E5-85-D5-00-1E-E7-B2-1C-3B-E0-E8-F8-35-BF-90-24-00-DC-47-09-66-92-2F-43-92-48-CB-42-4C-3C-86-CC-67-33-62-A1-1E-76-D1-D6-AD-5F-50-DC-D9-C7-31-F2-33-FE-77-CB-4C-EE-2A-AE-54-63-46-48-B4-FA-6F-0A-E0-1B-F2-F6-C2-D0-E0-24-A1-79-B8-29-FB-04-F3-D5-4E-CC-64-E8-FA-67-55-7B-E6-CD-FD-D2-13-3D-F0-C4-10-A9-5C-BB-34-66-54-A4-6E-B7-AC-54-7A-21-5F-C3-01-7B-97-AD-71-C5-2A-E9-39-B1-27-C7-F8-AC-BF-76-EA-D6-C8-05-22-54-4B-71-73-F2-FD-8E-6E-D7-D2-F2-F7-83-B3-9F-75-3C-CA-BA-BD-F3-C2-E6-16-37-9E-C1-88-C4-69-F5-95-E8-A5-81-C9-FC-22-73-1D-09-AB-A8-6D-A3-BA-CB-0F-27-4E-C7-8C-7A-6C-9D-9B-9D-1F-45-F2-7C-B7-7B-F6-DF-24-50-71-A7-BA-F7-F2-8C-AC-19-A3-86-77-4A-EC-5A-06-12-1F-00-AD-5D-EE-E0-61-D0-80-B7-2C-72-2E-77-6A-32-24-0C-64-78-63-37-A9-08-A2-90-9D-21-5E-E4-5E-E6-1D-66-7D-F5-E1-FC-3C-F1-DE-76-68-D7-1D-46-70-A5-32-31-2B-F5-02-6A-E4-95-CD-8E-B8-76-8D-6E-0B-98-E9-4A-DD-8D-C3-6F-D8-0C-BE-C7-32-3F-99-26-94-26-41-80-E4-E6-E7-D3-26-E9-2F-C0-5D-7B-98-24-BD-6E-9B-E8-9D-F6-DF-51-90-FE-EE-86-DE-9E-31-9F-1C-BA-1A-C4-5B-DD-5A-84-43-02-B9-99-01-6A-95-7C-FF-86-28-C2-4C-EF-4D-D6-36-BD-08-0F-30-25-E7-FF-D9-BC-DA-A7-87-65-1A-1E-83-55-D2-60-38-EC-51-97-FA-FD-11-70-83-70-66-39-78-47-93-7C-B7-FB-48-96-2F-C6-1E-6E-7D-29-38-F6-AB-06-45-C5-F7-50-D6-C7-44-5C-AB-96-A0-60-7D-0E-63-4E-B2-EA
Decrypted Data (603 Bytes):
66-90-D0-0F-8B-67-60-9B-AC-39-FC-45-04-3F-9D-C5-08-00-C1-DE-C4-21-51-64-FF-11-DC-80-84-FE-9A-6F-FE-E2-DC-7C-15-6D-F7-EA-3F-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-14-45-51-D4-44-7D-3F-A6-A4-8A-30-E6-02-00
As you can see, all data is the same in the compressed format except for the first 16 bytes:
Before Encryption: 1F-8B-08-00-00-00-00-00-04-00-ED-CA-B1-0D-00-20
After Decryption: 66-90-D0-0F-8B-67-60-9B-AC-39-FC-45-04-3F-9D-C5
The magic number is gone and I do not know why.
The file that I am encrypting is a text file with the line abcdefabcdefabcdefabcdefabcdefabcdef repeated 5000 times.
Having just the first 16 bytes (default block size of AES) being incorrect is the signature of another problem which is the fact that the AesCryptoServiceProvider decryptor object cannot be reused as it stores state information from the last decryption which causes the such bizarre results in subsequent decryptions.
The full original calling code included the fatal reuse of the decryptor object.
Full original calling code:
using (var aes = new AesCryptoServiceProvider())
{
ICryptoTransform encryptor = aes.CreateEncryptor();
ICryptoTransform decryptor = aes.CreateDecryptor(); // <-- Decryptor fails on second usage
// Compress/ Encrypt
CompressThenEncrypt(OriginalFileName, CompressThenEncryptFileName, encryptor);
EncryptThenCompress(OriginalFileName, EncryptThenCompressFileName, encryptor);
// Decrypt/ Decompress
DecompressThenDecrypt(EncryptThenCompressFileName, DecompressThenDecryptFileName, decryptor);
DecryptThenDecompress(CompressThenEncryptFileName, DecryptThenDecompressFileName, decryptor);
}
This was a personal exercise I developed to showcase the importance of compressing then encrypting data. I had two scenarios "Compress Then Encrypt" and "Encrypt Then Compress" and I also wrote out the code to perform the reverse operations.
The issue occurred when I reused the ICryptoTransform decryptor object. Despite the fact that the CanReuseTransform property is true - it is false advertisement. According to this answer it appears that there is a bug where the input buffer is not cleared correctly after decryption. The linked answer describes a few workarounds and simply creating a separate decryptor object works.
Revised working calling code:
using (var aes = new AesCryptoServiceProvider())
{
ICryptoTransform encryptor = aes.CreateEncryptor();
ICryptoTransform decryptor = aes.CreateDecryptor();
ICryptoTransform decryptor2 = aes.CreateDecryptor();
// Compress/ Encrypt
CompressThenEncrypt(OriginalFileName, CompressThenEncryptFileName, encryptor);
EncryptThenCompress(OriginalFileName, EncryptThenCompressFileName, encryptor);
// Decrypt/ Decompress
DecompressThenDecrypt(EncryptThenCompressFileName, DecompressThenDecryptFileName, decryptor);
DecryptThenDecompress(CompressThenEncryptFileName, DecryptThenDecompressFileName, decryptor2);
}
I need to implement a simple file encryption and then decrypt it, when needed, to a memory stream.
The easiest way seems to do this with File.Encrypt, but is it possible to decrypt the file to memory stream, instead of decrypting the file before reading it to memory stream, and thus exposing it for a while?
And if File.Encrypt is not the best way for this scenario, what would you recommend?
File.Encrypt is an OS feature but it sounds like really you want to control how the encryption is done.
http://msdn.microsoft.com/en-us/library/system.io.file.encrypt.aspx
// This is where the data will be written do.
MemoryStream dataStream = new MemoryStream();
// The encryption vectors
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};
// Build the encryption mathematician
using (TripleDESCryptoServiceProvider encryption = new TripleDESCryptoServiceProvider())
using (ICryptoTransform transform = encryption.CreateEncryptor(key, iv))
using (Stream encryptedOutputStream = new CryptoStream(dataStream, transform, CryptoStreamMode.Write))
using (StreamWriter writer = new StreamWriter(encryptedOutputStream))
{
// In this block, you do your writing, and it will automatically be encrypted
writer.Write("This is the encrypted output data I want to write");
}
Encryption is not for the faint of heart. Be forewarned though, you really should have a strong sense of regular IO and data streams before you attempt this though.
Implementing Crypto deceptively easy, and actually rather tedious, there are a lot of details, and the details wrong are usually what's exploited security wise. The best practice is to use a high level encryption framework that hides these details ivs, salts, mac, comparisons, padding, key rotation, and while it's not improbable for high level frameworks to have the details wrong, when they do, they get found and fixed, the code snippets on stack overflow generally do not.
I have been porting the Google Keyczar framework so such a high level library would exist for C#.
Keyczar-dotnet
And it is usable for encrypting and decrypting io streams.
Install in your project with nuget
PM> Install-Package Keyczar -Pre
Then create your key set. (by having a separate key set file, it gives you the ability to rotating keys in the future and prevents you from accidentally hard coding something that should not ever be hard coded.)
PM> KeyczarTool.exe create --location=path_to_key_set --purpose=crypt
PM> KeyczarTool.exe addkey --location=path_to_key_set --status=primary
Then in your code you can use any IO stream you want for both encryption:
using(var encrypter = new Encrypter("path_to_key_set"))
{
encrypter.Encrypt(plaintextStream, ciphertextStream);
}
and decryption:
using(var crypter = new Crypter("path_to_key_set"))
{
crypter.Decrypt(ciphertextStream, plaintextStream);
}
This was the first encryption code I wrote - be warned, although a good starting point to understand what's going on, static passwords and static salts are a bad idea! (thanks for highlighting this CodesInChaos)
You can decrypt to any stream you like, including straight to a memory stream...
FileInfo file = new FileInfo("SomeFile");
using (FileStream inFs = file.OpenRead())
{
using (MemoryStream outMs = new MemoryStream())
{
encryption.Decrypt(inFs, outMs);
BinaryFormatter bf = new BinaryFormatter();
targetType target= bf.Deserialize(outMs) as targetType;
}
}
where encryption is one of these:
public class EncryptionHelper
{
static SymmetricAlgorithm encryption;
static string password = "password";
static string salt = "this is my salt. There are many like it, but this one is mine.";
static EncryptionHelper()
{
encryption = new RijndaelManaged();
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, Encoding.ASCII.GetBytes(salt));
encryption.Key = key.GetBytes(encryption.KeySize / 8);
encryption.IV = key.GetBytes(encryption.BlockSize / 8);
encryption.Padding = PaddingMode.PKCS7;
}
public void Encrypt(Stream inStream, Stream OutStream)
{
ICryptoTransform encryptor = encryption.CreateEncryptor();
inStream.Position = 0;
CryptoStream encryptStream = new CryptoStream(OutStream, encryptor, CryptoStreamMode.Write);
inStream.CopyTo(encryptStream);
encryptStream.FlushFinalBlock();
}
public void Decrypt(Stream inStream, Stream OutStream)
{
ICryptoTransform encryptor = encryption.CreateDecryptor();
inStream.Position = 0;
CryptoStream encryptStream = new CryptoStream(inStream, encryptor, CryptoStreamMode.Read);
encryptStream.CopyTo(OutStream);
OutStream.Position = 0;
}
}
I want to compress and then encrypt my data, and for improved speed (by not having to write to byte arrays and back) decided to chain the streams used for compression and encryption together.
It works perfectly when I write (compress and encrypt) the data, but when I try to read the data (decompress and decrypt), the Read operation breaks - simply calling Read once reads exactly 0 bytes, because the first Read always returns 0. Looping as in the below code almost works, except that at a certain point, Read stops returning anything > 0 even though there's still data to be read.
Everything before those last few bytes are decompressed and decrypted perfectly.
The number of bytes left when that happens remains the same for the same plaintext; for example, it's always 9 bytes for a certain string, but always 1 byte for another.
The following is the relevant encryption and decryption code; any ideas as to what could be going wrong?
Encryption:
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (DeflateStream zip = new DeflateStream(csEncrypt, CompressionMode.Compress, true))
{
zip.Write(stringBytes, 0, stringBytes.Length);
csEncrypt.FlushFinalBlock();
Decryption:
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream())
{
// Writes the actual data (sans prepended headers) to the stream
msDecrypt.Write(stringBytes, prependLength, stringBytes.Length - prependLength);
// Reset position to prepare for read
msDecrypt.Position = 0;
// init buffer to read to
byte[] buffer = new byte[originalSize];
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (DeflateStream zip = new DeflateStream(csDecrypt, CompressionMode.Decompress))
{
// Hangs with "offset" at a small, deterministic number away from originalSize (I've gotten 9 less and 1 less for different strings)
// Loop fixed as per advice
int offset = 0;
while (offset < originalSize)
{
int read = zip.Read(buffer, offset, originalSize - offset);
if (read > 0)
offset += read;
else if (read < 0)
Console.WriteLine(read); // Catch it if it happens.
}
// Hangs with "left" at a small, deterministic number (I've gotten 9 and 1 for different strings)
/*
for (int left = buffer.Length; left > 0; )
left -= zip.Read(buffer, 0, left);
*/
Solution (Modification to Encryption):
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (DeflateStream zip = new DeflateStream(csEncrypt, CompressionMode.Compress, true))
zip.Write(stringBytes, 0, stringBytes.Length);
//Flush after DeflateStream is disposed.
csEncrypt.FlushFinalBlock();
The problem lies in the following line:
csEncrypt.FlushFinalBlock();
If you remove that, the code will work.
The reason is that when you write to DeflateStream, not all data is written to the underlying stream. That happens only when you call Close() or Dispose() explicitly or implicitly by leaving the using block.
So in your code, this happens:
You Write() all of the data to the DeflateStream, which in turn writes most of the data to the underlying CryptoStream.
You call csEncrypt.FlushFinalBlock(), which closes the CryptoStream.
You leave the using block of the DeflateStream, which tries to write the rest of the data to the already closed CryptoStream.
You leave the using block of the CryptoStream, which would call FlushFinalBlock(), if it wasn't called already.
The correct sequence of events is:
You Write() all of the data to the DeflateStream, which in turn writes most of the data to to the underlying CryptoStream.
You leave the using block of the DeflateStream, which writes the rest of the data to the already closed CryptoStream.
You leave the using block of the CryptoStream, which calls FlushFinalBlock().
Although I would expect that writing to a closed stream would fail with an exception. I'm not sure why that doesn't happen.
I had similar issue recently while reading from remote stream.
For me, solution was to change line which read entire stream in single call into while loop:
zip.Write(stringBytes, 0, stringBytes.Length);
Remote streams not always can return requested amount of data, so try read stream while you read enough bytes.