I've figured out the mistake I made. I was testing with very short strings and didn't think it strange that my encrypted size was always 16 bytes.
When I tested with longer strings, that didn't feel right. It turns out that my issue was in the encryption logic: I needed to flush the StreamWriter
in addition to the CryptoStream
. So below is my now working encryption and decryption logic.
Note that an additional improvement to the logic is that I am now also generating a random salt during key generation and that random salt is also prepended to the encrypted byte[].
private const int SaltLength = 8;
public class KeyPackage
{
public byte[] KeySalt { get; set; }
public byte[] Key { get; set; }
}
public KeyPackage GetKey()
{
// Read the password from the configuration
string Key = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];
// Generate Key from the password stored in configuration using a random salt
using (Rfc2898DeriveBytes db = new Rfc2898DeriveBytes(Key, SaltLength))
{
// Using AES-128, we need 128 / 8 bytes in the key
return new KeyPackage() { Key = db.GetBytes(128 / 8), KeySalt = db.Salt };
}
}
public byte[] GetKey(byte[] keySalt)
{
// Read the password from the configuration
string Key = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];
// Generate Key from the password stored in configuration using the known salt
using (Rfc2898DeriveBytes db = new Rfc2898DeriveBytes(Key, keySalt))
{
// Using AES-128, we need 128 / 8 bytes in the key
return db.GetBytes(128 / 8);
}
}
/// <summary>
/// Encrypts the specified plain text using the
/// encryption key found in the configuration file.
/// </summary>
/// <param name="plainText">The plain text to encrypted.</param>
/// <returns>The cipher text.</returns>
public byte[] Encrypt(string plainText)
{
byte[] encrypted = null;
using (AesManaged alg = new AesManaged())
{
System.Diagnostics.Debug.WriteLine("Key size: {0}", alg.KeySize);
alg.GenerateIV();
KeyPackage kp = GetKey();
alg.Key = kp.Key;
ICryptoTransform transform = alg.CreateEncryptor();
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write))
using (StreamWriter w = new StreamWriter(cs))
{
w.Write(plainText);
w.Flush(); // This was missing from my first post
cs.FlushFinalBlock();
// Create a byte array big enough to hold the IV and the encrypted value
encrypted = new byte[kp.KeySalt.Length + alg.IV.Length + ms.Length];
// Copy the random generated salt to the start of the encrypted bytes
Buffer.BlockCopy(kp.KeySalt, 0, encrypted, 0, kp.KeySalt.Length);
// Copy the random generated initialization vector to the start of the encrypted bytes
Buffer.BlockCopy(alg.IV, 0, encrypted, kp.KeySalt.Length, alg.IV.Length);
// Copy the encrypted value at the end
Buffer.BlockCopy(ms.ToArray(), 0, encrypted, alg.IV.Length + kp.KeySalt.Length, (int)ms.Length);
}
}
return encrypted;
}
/// <summary>
///
/// </summary>
/// <param name="cipherText"></param>
/// <returns></returns>
public string Decrypt(byte[] cipherText)
{
string plain = null;
using (AesManaged alg = new AesManaged())
{
// Extract the initialization vector from the entire ciphertext
byte[] IV = new byte[alg.IV.Length];
byte[] KeySalt = new byte[SaltLength];
Buffer.BlockCopy(cipherText, 0, KeySalt, 0, SaltLength);
Buffer.BlockCopy(cipherText, SaltLength, IV, 0, alg.IV.Length);
alg.IV = IV;
alg.Key= GetKey(KeySalt);
ICryptoTransform transform = alg.CreateDecryptor();
// Extract the encrypted value from the entire ciphertext
int ActualCipherLength = cipherText.Length - alg.IV.Length - SaltLength;
byte[] encrypted = new byte[ActualCipherLength];
Buffer.BlockCopy(cipherText, alg.IV.Length + SaltLength, encrypted, 0, ActualCipherLength);
using (MemoryStream ms = new MemoryStream(encrypted))
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read))
using (StreamReader r = new StreamReader(cs))
{
plain = r.ReadToEnd();
}
}
return plain;
}
}