I was writing the new
Twitter Client for
TweetPivot when I hit a problem. I needed to store a small amount of information on our users' machines; specifically in isolated storage. Most of this would be fine as plain text but one field needed to be encrypted. This meant I had to solve a problem I seem to come back to again and again - simple string cryptography.
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"?
Answer: probably no.
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);
}
}