I am trying to encrypt some data with the following code:
public static byte[] EncryptString(byte[] input, string password)
{
PasswordDeriveBytes pderiver = new PasswordDeriveBytes(password, null);
byte[] ivZeros = new byte[8];
byte[] pbeKey = pderiver.CryptDeriveKey("RC2", "MD5", 128, ivZeros);
RC2CryptoServiceProvider RC2 = new RC2CryptoServiceProvider();
byte[] IV = new byte[8];
ICryptoTransform encryptor = RC2.CreateEncryptor(pbeKey, IV);
MemoryStream msEncrypt = new MemoryStream();
CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
csEncrypt.Write(input, 0, input.Length);
csEncrypt.FlushFinalBlock();
return msEncrypt.ToArray();
}
However, when it reaches initializing my CryptoStream object, it throws the following error:
"Stream does not support seeking."
To clarify, there is no error handling in the above code, so just running this will not "break", persay. But stepping through the code, the CryptoStream object will show this error in its properties once it's been initialized.
Why is this? And how can I avoid it?
So the code actually runs without an exception, but the problem is when you're looking at the properties in the debugger? If so, that's easy - some properties (Position, for example) rely on being able to seek within the stream. You can't do that with a CryptoStream - so the property evaluation fails.
You don't need to avoid this - it's perfectly fine.
Can you use one of the constructors on the MemoryStream where you pass 'true' to the writable parameter?
To avoid this problem, its much easier to use:
using (var reader = new StreamReader(csEncrypt))
{
return reader.ReadToEnd();
}
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.
Let e = 'password' and I am transforming it to 'as9kio0736' in a CryptoStream.
Let d = 'as9kio0736' and I am transforming it to 'password in a CryptoStream.
When I am transforming d back to 'password' why is it not considered writing in a CryptoStream?
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
}
}
using (MemoryStream msDecrypt = new MemoryStream(cipherText)) {
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) {
using (StreamReader srDecrypt = new StreamReader(csDecrypt)) {
plaintext = srDecrypt.ReadToEnd();
}
}
}
CryptoStream is designed to perform transformation from a stream to another stream only and allows transformations chaining. For instance you can encrypt a data stream then Base 64 encode the encryption output.
Chose the mode depending on whether you want to write to the transformation input stream or read from the transformation output stream.
CryptoStream documentation is misleading.
The first CrytoStream constructor argument is described as:
"The stream on which to perform the cryptographic transformation"
This description is ok if constructor third argument value is CryptoStreamMode.Read.
But if third argument value is CryptoStreamMode.Write the first constructor argument description should be:
"The stream on which the result of cryptographic transformation is written to"
Also, documentation does not mention clearly that if you use CryptoStreamMode.Write, you MUST call FlushFinalBlock on your CryptoStream object after you finish writing.
To summarize this:
Writing to the transformation input stream:
CryptoStream constructor arguments:
argument 1: destination stream
argument 3: CryptoStreamMode.Write
CryptoStream object use:
Write data to the CryptoStream object
Call FlushFinalBlock on the CryptoStream object
Reading from the transformation output stream:
CryptoStream constructor arguments:
argument 1: source stream
argument 3: CryptoStreamMode.Read
CryptoStream object use:
Read data from the CryptoStream object until you reach the stream end
You can use CryptoStream in either direction for either operation; it's just where your data is and what you want to do with it.
If the data to process is already in a Stream (and you're okay with the stream getting drained and disposed), use CryptoStream in read mode and read the data out (including by using cryptoStream.CopyTo(someOtherStream)). If the data is in a byte[] and you want to write it to a Stream, use CryptoStream.Write.
In the .NET Core tests you can find examples both ways.
Using Read for both encrypt and decrypt: https://github.com/dotnet/corefx/blob/61fb32299a276f1aa4103c85dcec215dfddc252d/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs#L466-L480
Using Write for both encrypt and decrypt: https://github.com/dotnet/corefx/blob/61fb32299a276f1aa4103c85dcec215dfddc252d/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs#L609-L631
Heck, those are even in the same files. It's all just a matter of preference.
CryptoStream gives the flexibility to read/write for both Encrypt and Decrypt. For the case where the direction is fixed it can be changed to move in either direction regardless of the implementation.
Here's a way to do it:
https://github.com/djpai/StreamConduit
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);
}
After reading many questions and answers on SO, I'm yet to figure out why the code I've written below is producing unexpected results. I used the Rfc2898DeriveBytes class to produce a 128-Bit Key for use with the RijndaelManaged class. I generate the IV using GenerateIV(). Am I doing something wrong here? Many of the code examples I've looked at are pretty much exactly this.
string text = "TEXT TO ENCRYPT";
string key = "MY_KEY";
byte[] saltRaw = BitConverter.GetBytes((long)text.Length);
Rfc2898DeriveBytes deriver = new Rfc2898DeriveBytes(key, saltRaw);
byte[] keyRaw = deriver.GetBytes(32);
RijndaelManaged rij = new RijndaelManaged();
rij.Key = keyRaw;
rij.GenerateIV();
ICryptoTransform encryptor = rij.CreateEncryptor(rij.Key, rij.IV);
MemoryStream sMemory = new MemoryStream();
CryptoStream sCrypt = new CryptoStream(sMemory, encryptor, CryptoStreamMode.Write);
StreamWriter sWriter = new StreamWriter(sCrypt);
sWriter.Write(text);
byte[] r = sMemory.ToArray();
sWriter.Dispose();
sCrypt.Dispose();
sMemory.Dispose();
encryptor.Dispose();
return r; // length of r is 0.
The problem here is that the byte[] r has a length of 0, so nothing was written to the memory stream. CryptoStream doesn't implement the length property so I can't check to see if anything was written to it. I'm also aware that I don't preprend the Salt or the IV to the result, I'm yet to do this.
Addressing solely your zero-length r issue, this is due to buffering within the StreamWriter, so at the point you attempt to get the contents of the MemoryStream, nothing has actually been written to it.
byte[] r;
using (var sMemory = new MemoryStream())
{
using (var sCrypt = new CryptoStream(sMemory, encryptor, CryptoStreamMode.Write))
{
using (var sWriter = new StreamWriter(sCrypt))
{
sWriter.Write(text);
}
}
r = sMemory.ToArray();
}
I strongly suggest that you use using() blocks as above, rather than explicitly calling Dispose() as this will ensure proper disposal even in case of exceptions, and also helps avoid use of objects after they are disposed, or forgetting to call Dispose() at all.
Even assuming the code works as expected following the change above, there are various other security issues in the code which are beyond the scope of this answer.
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.