I'm relatively new to encrypting, and I have some questions. I intend to write a standalone C# program that will write and read an encrypted file with sensitive data using a password entered by the user when the program is started. Both the program and the file will be stored together on a private place, but one never knows that they could fall in the wrong hands. As the program is written in C# it can be decompiled, but I always heard that is not a problem, as the encryption should be safe even if the algorithm is known.
Here is the test code I've written, some questions will follow:
private string TestEncryption(string password, string input, string filename)
{
// Encryption
byte[] inputBytes = AddHash(Encoding.UTF8.GetBytes(input));
int iterations = 10000;
int saltSize = 64;
using (Rfc2898DeriveBytes rbg = new Rfc2898DeriveBytes(password, saltSize, iterations))
{
if (rbg.Salt.Length != saltSize)
throw new Exception("Invalid salt size");
using (AesManaged aes = new AesManaged())
{
byte[] key = rbg.GetBytes(aes.KeySize >> 3);
byte[] iv = rbg.GetBytes(aes.BlockSize >> 3);
using (ICryptoTransform cryptoTransform = aes.CreateEncryptor(key, iv))
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
cryptoStream.Write(inputBytes, 0, inputBytes.Length);
byte[] encryptedBytes = AddHash(ConcatBytes(memoryStream.ToArray(), rbg.Salt));
using (FileStream file = new FileStream(filename, FileMode.Create, FileAccess.Write))
file.Write(encryptedBytes, 0, encryptedBytes.Length);
}
}
}
// Decryption
using (FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
inputBytes = new byte[file.Length];
file.Read(inputBytes, 0, inputBytes.Length);
}
int length;
if (!VerifyHash(inputBytes, out length))
throw new Exception("File corrupted");
byte[] salt = new byte[saltSize];
length -= saltSize;
Buffer.BlockCopy(inputBytes, length, salt, 0, saltSize);
using (Rfc2898DeriveBytes rbg = new Rfc2898DeriveBytes(password, salt, iterations))
using (AesManaged aes = new AesManaged())
{
byte[] key = rbg.GetBytes(aes.KeySize >> 3);
byte[] iv = rbg.GetBytes(aes.BlockSize >> 3);
try
{
using (ICryptoTransform cryptoTransform = aes.CreateDecryptor(key, iv))
using (MemoryStream sourceStream = new MemoryStream(inputBytes, 0, length))
using (CryptoStream cryptoStream = new CryptoStream(sourceStream, cryptoTransform, CryptoStreamMode.Read))
using (MemoryStream decryptedStream = new MemoryStream())
{
cryptoStream.CopyTo(decryptedStream);
byte[] decryptedBytes = decryptedStream.ToArray();
if (!VerifyHash(decryptedBytes, out length))
throw new Exception("Invalid password");
return Encoding.UTF8.GetString(decryptedBytes, 0, length);
}
}
catch (CryptographicException exception)
{ throw new Exception("Probable invalid password", exception); }
}
return null;
}
private byte[] AddHash(byte[] bytes)
{
using (MD5 md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(bytes);
if (hash.Length != (md5.HashSize >> 3))
throw new Exception("Invalid hash size");
return ConcatBytes(bytes, hash);
}
}
private bool VerifyHash(byte[] bytes, out int length)
{
using (MD5 md5 = MD5.Create())
{
int hashSize = md5.HashSize >> 3;
length = bytes.Length - hashSize;
if (bytes.Length > hashSize)
{
byte[] hash = new byte[hashSize];
Buffer.BlockCopy(bytes, length, hash, 0, hashSize);
if (hash.SequenceEqual(md5.ComputeHash(bytes, 0, length)))
return true;
}
}
return false;
}
private byte[] ConcatBytes(params byte[][] buffers)
{
int length = buffers.Sum(b => b.Length);
byte[] result = new byte[length];
int offset = 0;
foreach (byte[] buffer in buffers)
{
Buffer.BlockCopy(buffer, 0, result, offset, buffer.Length);
offset += buffer.Length;
}
return result;
}
Now for some questions:
- As you may have noticed, I save the salt I used to generate the Key and IV in the file. Does this compromise the encryption or not ? I supposed it is not, as when we store hashed passwords, they also include their salt.
- I'm using a salt size of 64 and 10000 iterations, are these a correct values ?
- I'm also saving some hash values (MD5) to detect errors. I'm saving one at the end of the file, calculated on the encrypted bytes (including the salt), to test if the file is not corrupted. I don't think this should be any problem as it doesn't compromise the encryption. The other hash code is added to the unencrypted data before encryption to test if the data is correctly decrypted. I'm wondering if this is a good idea, as I think it could help brute force decryption attacks ? If not, what are better alternatives ?
- Is AES (Rijndael) still the best symmetric encryption algorithm around, or are there better ?
- Should i enforce a minimum size for the password or is that not needed as the Key and IV are randomly generated based on the password and random salt ?
- Can the CryptographicException be avoided when using a wrong password ?
(I posted this question on stackoverflow, but someone suggested i move it to a more appropriate site)
Marc
EDIT: Added a sixth question.