Monday, December 29, 2008

How to encrypt/decrypt with passphrase

I was stuck with this when I started using IIS7's AppCmd utility. The utility allows to export and import appication pool configuration, while not having an option to encode/decode identity password. But it was required to store the configuration under the source control system. So I decided to write a simple command-line encoding/decoding utility for a particular section being fetched by regular expression.

I use a symmetric algorithm because key is defined with passphrase for both encoding and decoding operations. Creating key from passphrase is based on hashing. We are required to have keys with fixed length and character sets, thus hashing goes a long way here:
var bytes = Encoding.Unicode.GetBytes(passphrase);
var key = SHA256Managed.Create().ComputeHash(bytes);
var iv = MD5.Create().ComputeHash(bytes);
Key and vector may have different requirements regarding length of byte arrays. For the RijndaelManaged class they are 32 and 16 respectively. Thus I use SHA256 and MD5 algorithms to get keys with the appropriate length.
var alg = SymmetricAlgorithm.Create();
var ms = new MemoryStream();
var buffer = Encoding.Unicode.GetBytes(text);
 
using (var enc = new CryptoStream(
    ms, alg.CreateEncryptor(key, iv),
    CryptoStreamMode.Write
))
{
    enc.Write(buffer, 0, buffer.Length);
}
The code above feeds the entire text to the CryptoStream. For passwords encoding it shouldn't be a perfomance issue. Decoding has quite similar implementation. Rather than using alg.CreateEncryptor it should use alg.CreateDecryptor there.

Additionally I use a couple of extention methods to format byte array to hexadecimal string and vice versa. Here are the entire Encode/Decode methods implementation and the helper methods:

private static string Encode(string text, string passphrase)
{
    var bytes = Encoding.Unicode.GetBytes(passphrase);
    var key = SHA256Managed.Create().ComputeHash(bytes);
    var iv = MD5.Create().ComputeHash(bytes);
 
    var alg = SymmetricAlgorithm.Create();
    var ms = new MemoryStream();
    var buffer = Encoding.Unicode.GetBytes(text);
 
    using(var enc = new CryptoStream(
        ms, alg.CreateEncryptor(key, iv),
        CryptoStreamMode.Write
    )) enc.Write(buffer, 0, buffer.Length);
 
    return ms.ToArray().ToHexString();
}
 
private static string Decode(string text, string passphrase)
{           
    var bytes = Encoding.Unicode.GetBytes(passphrase);
    var key = SHA256Managed.Create().ComputeHash(bytes);
    var iv = MD5.Create().ComputeHash(bytes);
 
    var alg = SymmetricAlgorithm.Create();
    var ms = new MemoryStream();
 
    var buffer = text.ToByteArray();
 
    try
    {
        using (var enc = new CryptoStream(
            ms, alg.CreateDecryptor(key, iv),
            CryptoStreamMode.Write
        )) enc.Write(buffer, 0, buffer.Length);
    }
    catch (Exception)
    {
        Console.Error.WriteLine("Error: wrong passphrase.");
        Environment.Exit(2);
    }
 
    return Encoding.Unicode.GetString(ms.ToArray());
}
...
internal static string ToHexString(this byte[] bytes)
{
    StringBuilder builder = new StringBuilder(3 * bytes.Length);
 
    for (int i = 0; i < bytes.Length; i++)
    {
        builder.AppendFormat("{0:x2}", bytes[i]);
    }
 
    return builder.ToString().ToLowerInvariant();
}
 
internal static byte[] ToByteArray(this string hexString)
{
    byte[] buffer = new byte[hexString.Length / 2];
 
    for (int i = 0; i < hexString.Length; i += 2)
    {
        buffer[i / 2] = byte.Parse(hexString.Substring(i, 2), NumberStyles.HexNumber);
    }
   
    return buffer;
}

No comments:

Post a Comment