A BlockHasher helper class

A BlockHasher helper class

There are a few instances in which you'll need to hash a combination of data. You might resort to creating one big string and hashing that. It has a clear disadvantage from a memory and processing point of few. It might even be impractical when files or streams are involved. That's why I created a BlockHasher utility class that helps to generate these types of hashes.

HashAlgorithm

.NET offers the following through the HashAlgorithm class:

Wanted: more features

Basically you want the following features in this scenario:

To make things more readable, you might want to return the hash as a hexadecimal string or - a shorter, but less frequently used - base64 string. I find myself always using the same lines of code, so I added it to the class.

BlockHasher

Without further ado, let's see the class:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

/// <summary>
/// Helps with block hashing.
/// </summary>
public class BlockHasher : IDisposable
{
    private HashAlgorithm _hasher;
    /// <summary>
    /// Initializes a new instance of the <see cref = "BlockHasher"/> class.
    /// </summary>
    /// <param name = "name">The name.</param>
    public BlockHasher(string name)
    {
        if (String.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException("name");
        }

        _hasher = HashAlgorithm.Create(name);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref = "BlockHasher"/> class.
    /// </summary>
    /// <param name = "algorithm">The algorithm.</param>
    public BlockHasher(HashAlgorithm algorithm)
    {
        if (algorithm == null)
        {
            throw new ArgumentNullException("algorithm");
        }

        _hasher = algorithm;
    }

    /// <summary>
    /// Indicates how to format the string.
    /// </summary>
    public enum StringFormat
    {
        Hexadecimal,
        Base64
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        _hasher.Dispose();
    }

    /// <summary>
    /// Gets the hash.
    /// </summary>
    /// <returns>A byte array with the hash.</returns>
    public byte[] GetHash()
    {
        _hasher.TransformFinalBlock(new byte[0], 0, 0);
        return _hasher.Hash;
    }

    /// <summary>
    /// Gets the string hash.
    /// </summary>
    /// <param name = "format">The format.</param>
    /// <returns>The hash.</returns>
    public string GetStringHash(StringFormat format = StringFormat.Hexadecimal)
    {
        var hash = GetHash();
        switch (format)
        {
            case StringFormat.Hexadecimal:
            {
                string result = "";
                for (int i = 0; i < hash.Length; i++)
                {
                    result += hash[i].ToString("x2");
                }

                return result;
            }

            case StringFormat.Base64:
            {
                return Convert.ToBase64String(hash);
            }

            default:
            {
                throw new NotSupportedException();
            }
        }
    }

    /// <summary>
    /// Transforms the specified stream.
    /// </summary>
    /// <param name = "stream">The stream.</param>
    /// <param name = "bufferSize">Size of the buffer.</param>
    public void Transform(Stream stream, int bufferSize = 8 * 1024)
    {
        if (stream == null)
        {
            throw new ArgumentNullException("stream");
        }

        byte[] buffer = new byte[bufferSize];
        int read;
        while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
        {
            Transform(buffer, 0, read);
        }
    }

    /// <summary>
    /// Transforms the specified string. Assumes UTF8 encoding.
    /// </summary>
    /// <param name = "str">The string.</param>
    public void Transform(string str)
    {
        Transform(str, Encoding.UTF8);
    }

    /// <summary>
    /// Transforms the specified string using the specified encoding.
    /// </summary>
    /// <param name = "str">The string.</param>
    /// <param name = "encoding">The encoding.</param>
    public void Transform(string str, Encoding encoding)
    {
        var bytes = encoding.GetBytes(str);
        Transform(bytes);
    }

    /// <summary>
    /// Transforms the specified buffer.
    /// </summary>
    /// <param name = "buffer">The buffer.</param>
    public void Transform(byte[] buffer)
    {
        Transform(buffer, 0, buffer.Length);
    }

    /// <summary>
    /// Transforms the specified buffer.
    /// </summary>
    /// <param name = "buffer">The buffer.</param>
    /// <param name = "offset">The offset.</param>
    /// <param name = "length">The length.</param>
    public void Transform(byte[] buffer, int offset, int length)
    {
        _hasher.TransformBlock(buffer, offset, length, null, 0);
    }

    /// <summary>
    /// Transforms the specified stream asynchronously.
    /// </summary>
    /// <param name = "stream">The stream.</param>
    /// <param name = "bufferSize">Size of the buffer.</param>
    /// <returns>The task.</returns>
    public async Task TransformAsync(Stream stream, int bufferSize = 8 * 1024)
    {
        byte[] buffer = new byte[bufferSize];
        int read;
        while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            Transform(buffer, 0, read);
        }
    }
}

API Signature Hashing

Lots of API's use a HMAC SHA1 signature to authenticate messages. I've created some example code on how the BlockHasher can be used to generate the signature:

[TestCategory("UnitTest")]
[TestMethod]
public void BlockHasher_ApiRequest_HMACSHA1()
{
    //api information
    string apiKey = "DvTVukOITB5GJ5r79IEy3J9LALZ1LLex";
    string clientSecret = "5d18SSM38x1lGjMD5qCX1FJGsw4jJ12t";
    //request data
    Dictionary<string, string> request = new Dictionary<string, string>();
    request.Add("Message", "Hello world!");
    request.Add("PublishDate", "1984-09-12");
    request.Add("Tags", "first,message,ever");
    request.Add("Active", "true");
    string signature = null;
    using (var algorithm = new HMACSHA1(Encoding.ASCII.GetBytes(clientSecret)))
    {
        var hasher = new BlockHasher(algorithm);
        var orderedKeys = request.Keys.OrderBy(k => k);
        //hash keys
        foreach (var k in orderedKeys)
        {
            hasher.Transform(k);
            hasher.Transform(",");
        }

        //hash values
        foreach (var k in orderedKeys)
        {
            hasher.Transform(request[k]);
            hasher.Transform(",");
        }

        hasher.Transform(apiKey);
        signature = hasher.GetStringHash(BlockHasher.StringFormat.Base64);
    }

    Assert.AreEqual("279y647EmEXFNFH2ZtNesIc6Skw=", signature);
}

Async stream hashing

Hashing big files using async streams is pretty easy:

public async Task<string> HashFile(string path, string algorithm = "md5")
{
    using (var file = File.OpenRead(path))
    {
        using (var blockhasher = new BlockHasher("md5"))
        {
            await blockhasher.TransformAsync(file);
            return blockhasher.GetStringHash();
        }
    }
}

Wrap up

So that's it. Be sure to visit the class on GitHub if you think it should be changed / expanded.