Crypto Rule #1 seems to be "Don't roll your own.". OK fine, so give me something pre-rolled. C# / .Net doesn't contain the method I was looking for:
string Encrypt(string plainText, string password, string salt);
Luckily, @blowdart came to my rescue and posted this code. Now, there's a few things worth pointing out about this:
- Barry's book sales suggest he might actually know what he's doing, but who else amongst us has the confidence and knowledge to say that it's safe and reliable?
- Did anyone read the code and think "actually, TripleDES would suit my needs, not Rijndael"?
So, given that a) it's hard to do correctly and b) this standard method would probably suit 99% of developers 99% of the time why, then, do we not find this in the BCL? If I want to open a file I can use System.IO.File.OpenRead(string path). If I need to take more control over the process I can use a StreamReader and dictate encodings etc.
I've included my final code below (you deserve something for reading this far!). Porting it into Silverlight simplifies matters - mainly because there's only one implementation of SymmetricAlgorithm. "But Barry defaulted to Rijndael. Does this mean that Silverlight is less secure than standard .Net applications?" Another crypto conundrum!
I do feel that this functionality needs abstracting behind a simplified facade. I genuinely don't care how long my initialisation vector is or whether I'm using DES, DoubleDES or even TripleDES. Cryptography is simply a tool for hiding stuff and it should be easier, safer and more confidence-imspiring than it currently is. @blowdart's subsequent tweet kinda sums up the complexity of the subject. Signatures, really? You might just as well have asked me if I wanted tweeters with it! #referenceToOldJoke
using System; using System.IO; using System.Security.Cryptography; using System.Text; public static class CryptographicExtensions { public static string Encrypt(this string plainText, string password, string salt) { var algorithm = new AesManaged(); algorithm.SetKeyAndIV(password, salt); return Encrypt(algorithm, plainText); } public static string Encrypt(this string plainText, ref byte[] key, ref byte[] iv) { if (string.IsNullOrEmpty(plainText)) return plainText; var algorithm = new AesManaged(); if (key != null) algorithm.Key = key; else key = algorithm.Key; if (iv != null) algorithm.IV = iv; else iv = algorithm.IV; return Encrypt(algorithm, plainText); } private static string Encrypt(SymmetricAlgorithm algorithm, string plainText) { var plainTextAsBytes = Encoding.UTF8.GetBytes(plainText); using (ICryptoTransform transform = algorithm.CreateEncryptor()) { using (var outputStream = new MemoryStream()) { using (var inputStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write)) { inputStream.Write(plainTextAsBytes, 0, plainText.Length); inputStream.FlushFinalBlock(); var cipherText = outputStream.ToArray(); return Convert.ToBase64String(cipherText); } } } } public static string Decrypt(this string cypherText, string password, string salt) { var algorithm = new AesManaged(); algorithm.SetKeyAndIV(password, salt); return Decrypt(algorithm, cypherText); } public static string Decrypt(this string cypherText, byte[] key, byte[] iv) { if (string.IsNullOrEmpty(cypherText)) return cypherText; if (key == null) throw new ArgumentNullException("key"); if (iv == null) throw new ArgumentNullException("iv"); var algorithm = new AesManaged() { Key = key, IV = iv }; return Decrypt(algorithm, cypherText); } private static string Decrypt(SymmetricAlgorithm algorithm, string cypherText) { var cipherTextAsBytes = Convert.FromBase64String(cypherText); using (ICryptoTransform transform = algorithm.CreateDecryptor()) { using (var outputStream = new MemoryStream()) { using (var inputStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write)) { inputStream.Write(cipherTextAsBytes, 0, cipherTextAsBytes.Length); inputStream.FlushFinalBlock(); var plainTextAsBytes = outputStream.ToArray(); return Encoding.UTF8.GetString(plainTextAsBytes, 0, plainTextAsBytes.Length); } } } } public static void SetKeyAndIV(this SymmetricAlgorithm algorithm, string password, string salt) { string paddedSalt = salt; while (paddedSalt.Length < 8) paddedSalt += salt; var rfc2898 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(paddedSalt)); algorithm.Key = rfc2898.GetBytes(algorithm.KeySize / 8); algorithm.IV = rfc2898.GetBytes(algorithm.BlockSize / 8); } }