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.
Related
I have a working implementation of TripleDESCng (tested against some test vectors), but the following happens:
When I encrypt plain text This is a sample message (24 bytes, thus for this it would be 3 blocks) (hex for it is 5468697320697320612073616D706C65206D657373616765) with an example key, I get E81F113DD7C5D965E082F3D42EC1E2CA39BCDBCCBC0A2BD9. However, when I decrypt this with the same example key, I get 5468697320697320612073616D706C650000000000000000, which, when converted back to ASCII, is:
This is a sample.
Any reason other than my code why this would behave this way? To encrypt and decrypt, I use 24 byte keys (ECB mode).
EDIT:
using (var tripleDES = new TripleDESCryptoServiceProvider())
{
byte[] data = ASCIIEncoding.ASCII.GetBytes("This is a sample message");
Console.WriteLine(BitConverter.ToString(data));
tripleDES.IV = new byte[tripleDES.BlockSize / 8];
var encryptor = tripleDES.CreateEncryptor();
byte[] result = new byte[data.Length];
encryptor.TransformBlock(data, 0, data.Length, result, 0);
var decryptor = tripleDES.CreateDecryptor();
byte[] result2 = new byte[result.Length];
decryptor.TransformBlock(result, 0, result.Length, result2, 0);
Console.WriteLine(BitConverter.ToString(result2));
}
Console.ReadLine();
With almost all modes1, you should make sure that the final part of your data is pushed through TransformFinalBlock rather than TransformBlock2, to make sure it knows that no more data is coming and to ensure final blocks are flushed/written.
It's bad form, in general, to assume the output size is going to match the input size.
the mode is not a problem, IV is set to 0s either way
Yes, that'll mean that the first block was not affected by your choice of Mode. But all subsequent blocks will be, because they will use the chaining mode and the previous block, not the IV. So if you want ECB (you shouldn't3) you need to explicitly set that mode.
1Your code is using CBC, not EBC as you claim in your narrative. CBC is the default mode for .NET encryption classes.
2And when using this second method, pay attention to it's return value, as mjwills commented.
3You've picked an outdated crypto algorithm, paired it with an outdated mode of operation, and your words I've quoted above mean that you don't understand modes. Added together, I would suggest that you're not well placed to be writing code that uses crypto currently. The .NET classes can make it seem easy to write crypto code but you still have to understand how to make good choices in using them. Better to spend more time on researching these things before writing code.
I think that your problem is in the method of the encryptor / decryptor that you are using: the TransformBlock method is conceived to transform a block when you will be encrypting multiple blocks.
That is not the case in your code, where you want to transform a single block, and therefore you should be using the TransformFinalBlock method instead. BTW I took the liberty of making your sample buildable.
using System;
using System.Text;
namespace Tests
{
class Program
{
static void Main(string[] args)
{
System.Security.Cryptography.TripleDESCryptoServiceProvider tripleDES = new System.Security.Cryptography.TripleDESCryptoServiceProvider();
byte[] data = Encoding.UTF8.GetBytes("This is a sample message");
byte[] key = Encoding.UTF8.GetBytes("NOSTROMOHASSOMEGODPOWERS");
tripleDES.Key = key;
tripleDES.IV = new byte[tripleDES.BlockSize / 8];
var encryptor = tripleDES.CreateEncryptor();
byte[] result = new byte[data.Length];
result = encryptor.TransformFinalBlock(data, 0, data.Length);
string res = BitConverter.ToString(result).Replace("-","");
Console.WriteLine(BitConverter.ToString(result).Replace("-",""));
byte[] data2 = result;
tripleDES.Key = key;
tripleDES.IV = new byte[tripleDES.BlockSize / 8];
var decryptor = tripleDES.CreateDecryptor();
byte[] result2 = new byte[data2.Length];
result2 = decryptor.TransformFinalBlock(data2, 0, data2.Length);
Console.WriteLine(Encoding.UTF8.GetString(result2));
}
}
}
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.
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've got a program that under Windows and .NET Framework 4 works perfectly well however, under Mono (built in MonoDevelop 2.6), the Encrypt() and Decrypt() function seem to only half work...
To the point at which if I locally encrypt something and then immediately decrypt it (under Mono), the first 10 or so characters of the message are scrambled jiberish, but anything following looks perfectly valid!
The Encrypt function is as follows:
public byte[] Encrypt(string plainText)
{
DateTime now = DateTime.Now;
string timeStamp = now.Millisecond.ToString("000") + "." + now.Second.ToString("00") + "." +
now.Minute.ToString("00") + "." + now.Hour.ToString("00") + Constants.MessageSplitChar;
plainText = plainText.Insert(0, timeStamp);
MemoryStream memoryStream = new MemoryStream();
lock (this.encryptor)
{
CryptoStream cryptoStream = new CryptoStream(memoryStream, this.encryptor, CryptoStreamMode.Write);
StreamWriter writer = new StreamWriter(cryptoStream);
try
{
writer.Write(plainText);
}
finally
{
writer.Close();
cryptoStream.Close();
memoryStream.Close();
}
}
byte[] encryptedMessage = memoryStream.ToArray();
return this.AppendArrays(BitConverter.GetBytes(encryptedMessage.Length), encryptedMessage);
}
The Decrypt function is as follows:
public string Decrypt(byte[] cipherText)
{
try
{
string plainText = string.Empty;
MemoryStream memoryStream = new MemoryStream(cipherText);
lock (this.decryptor)
{
CryptoStream cryptoStream = new CryptoStream(memoryStream, this.decryptor, CryptoStreamMode.Read);
StreamReader reader = new StreamReader(cryptoStream);
try
{
plainText = reader.ReadToEnd();
plainText = plainText.Substring(plainText.IndexOf("|") + 1);
plainText = plainText.TrimEnd("\0".ToCharArray());
}
finally
{
reader.Close();
cryptoStream.Close();
memoryStream.Close();
}
}
return plainText;
}
catch (Exception ex)
{
CMicroBingoServer.LogManager.Write(ex.ToString(), MessagePriority.Error);
return "DECRYPTION_FAILED";
}
}
Your code does not show how your decryptor and encryptor instances are created.
This can be a problem because if you're reusing the instance then you must check ICryptoTransform.CanReuseTransform. If it returns false then you cannot reuse the same encryptor/decryptor and must create new instances.
That's even more important since Mono and .NET have different defaults for some algorithms. But in any case skipping this check means that any changes in future .NET framework (or via configuration files, since cryptography is pluggable using CryptoConfig) is likely to break your code someday.
It may be important how many characters bytes at the beginning are seemingly gibberish... If the first block is coming out nonsense but the rest is fine then it could be that your initialization vector is not correct on decryption.
Under the most common block mode, CBC, when decrypting the IV only effects teh decryption of the first block of data since after that its the cipher text that acts as the IV for later blocks.
Are you explicitly setting IVs for encrypting and decrypting? If not then I would imagine that the two have different behaviours when dealing with unset IVs (eg windows uses all zeros and mono generates a random IV - this would cause windows to decrypt fine becuase the IV is the same whereas mono may be generating two different IVs for the encrypt and decrypt process.
I don't know mono stuff well enough to look into the exact solution but something along these lines seems likely.
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();
}