Sooner or later you’ll need to use a cryptographic hash function. Sometimes it’s to quickly check if two large byte arrays are the same, sometimes it’s for interoperability with some server, and other times it’s to obfuscate a string. In any case, performance of the various hash algorithms varies wildly. Today’s article performance tests all 27 hash algorithm permutations to see which is fastest and which is slowest. Read on for the performance test results!

Unity currently supports a good number of hash algorithms:

  • MD5
  • MD5CryptoServiceProvider
  • RIPEMD160Managed
  • RIPEMD160
  • SHA1Managed
  • SHA1CryptoServiceProvider
  • SHA256Managed
  • SHA256
  • SHA384Managed
  • SHA384
  • SHA512Managed
  • SHA512

And many more that use a key you provide, similar to seeding a random number generator:

  • HMACMD5
  • HMACRIPEMD160
  • MACTripleDES
  • HMACSHA1
  • HMACSHA256
  • HMACSHA384
  • HMACSHA512
  • HMACMD5
  • HMACRIPEMD160
  • MACTripleDES
  • HMACSHA1
  • HMACSHA1
  • HMACSHA256
  • HMACSHA384
  • HMACSHA512

To test them, I ran two tests for each. In the first test, I created 1000 instances of the hash algorithm class. In the second, I hashed a 1 MB byte array. Here’s the test:

using System;
using System.Security.Cryptography;
 
using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	private string report = string.Empty;
	private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
	private static readonly byte[] DataToHash = new byte[1024*1024];
	private const int NumCreateIterations = 1000;
	private const int NumHashIterations = 1;
 
	long TimeOperation(int numIterations, Action action)
	{
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < numIterations; ++i)
		{
			action();
		}
		return stopwatch.ElapsedMilliseconds;
	}
 
	long TimeCreate(Action action)
	{
		return TimeOperation(NumCreateIterations, action);
	}
 
	long TimeHash(HashAlgorithm hashAlgorithm)
	{
		return TimeOperation(NumHashIterations, () => hashAlgorithm.ComputeHash(DataToHash));
	}
 
	void Start()
	{
		var random = new System.Random(123);
		var bytes64 = new byte[64];
		var bytes16 = new byte[16];
		random.NextBytes(bytes64);
		random.NextBytes(bytes16);
		MD5 md5 = null;
		MD5CryptoServiceProvider md5csp = null;
		RIPEMD160Managed ripeMd160Managed = null;
		RIPEMD160 ripeMd160 = null;
		SHA1Managed sha1managed = null;
		SHA1CryptoServiceProvider sha1csp = null;
		SHA256Managed sha256managed = null;
		SHA256 sha256 = null;
		SHA384Managed sha384managed = null;
		SHA384 sha384 = null;
		SHA512Managed sha512managed = null;
		SHA512 sha512 = null;
		HMACMD5 hmacMd5 = null;
		HMACRIPEMD160 hmacRipeMd160 = null;
		MACTripleDES macTripleDes = null;
		HMACSHA1 hmacSha1 = null;
		HMACSHA256 hmacSha256 = null;
		HMACSHA384 hmacSha384 = null;
		HMACSHA512 hmacSha512 = null;
		HMACMD5 hmacMd5Keyed = null;
		HMACRIPEMD160 hmacRipeMd160Keyed = null;
		MACTripleDES macTripleDesKeyed = null;
		HMACSHA1 hmacSha1Keyed = null;
		HMACSHA1 hmacSha1KeyedManaged = null;
		HMACSHA256 hmacSha256Keyed = null;
		HMACSHA384 hmacSha384Keyed = null;
		HMACSHA512 hmacSha512Keyed = null;
		var createMd5 = TimeCreate(() => md5 = MD5.Create());
		var createMd5Csp = TimeCreate(() => md5csp = new MD5CryptoServiceProvider());
		var createRipeMd160Managed = TimeCreate(() => ripeMd160Managed = new RIPEMD160Managed());
		var createRipeMd160 = TimeCreate(() => ripeMd160 = RIPEMD160.Create());
		var createSha1Managed = TimeCreate(() => sha1managed = new SHA1Managed());
		var createSha1Csp = TimeCreate(() => sha1csp = new SHA1CryptoServiceProvider());
		var createSha256Managed = TimeCreate(() => sha256managed = new SHA256Managed());
		var createSha256 = TimeCreate(() => sha256 = SHA256.Create());
		var createSha384Managed = TimeCreate(() => sha384managed = new SHA384Managed());
		var createSha384 = TimeCreate(() => sha384 = SHA384.Create());
		var createSha512Managed = TimeCreate(() => sha512managed = new SHA512Managed());
		var createSha512 = TimeCreate(() => sha512 = SHA512.Create());
		var createHmacMd5 = TimeCreate(() => hmacMd5 = new HMACMD5());
		var createHmacRipeMd160 = TimeCreate(() => hmacRipeMd160 = new HMACRIPEMD160());
		var createHmacTripleDes = TimeCreate(() => macTripleDes = new MACTripleDES());
		var createHmacSha1 = TimeCreate(() => hmacSha1 = new HMACSHA1());
		var createHmacSha256 = TimeCreate(() => hmacSha256 = new HMACSHA256());
		var createHmacSha384 = TimeCreate(() => hmacSha384 = new HMACSHA384());
		var createHmacSha512 = TimeCreate(() => hmacSha512 = new HMACSHA512());
		var createHmacMd5Keyed = TimeCreate(() => hmacMd5Keyed = new HMACMD5(bytes64));
		var createHmacRipeMd160Keyed = TimeCreate(() => hmacRipeMd160Keyed = new HMACRIPEMD160(bytes64));
		var createHmacTripleDesKeyed = TimeCreate(() => macTripleDesKeyed = new MACTripleDES(bytes16));
		var createHmacSha1Keyed = TimeCreate(() => hmacSha1Keyed = new HMACSHA1(bytes64, false));
		var createHmacSha1KeyedManaged = TimeCreate(() => hmacSha1KeyedManaged = new HMACSHA1(bytes64, true));
		var createHmacSha256Keyed = TimeCreate(() => hmacSha256Keyed = new HMACSHA256(bytes64));
		var createHmacSha384Keyed = TimeCreate(() => hmacSha384Keyed = new HMACSHA384(bytes64));
		var createHmacSha512Keyed = TimeCreate(() => hmacSha512Keyed = new HMACSHA512(bytes64));
		var hashMd5 = TimeHash(md5);
		var hashMd5Csp = TimeHash(md5csp);
		var hashRipeMd160Managed = TimeHash(ripeMd160Managed);
		var hashRipeMd160 = TimeHash(ripeMd160);
		var hashSha1Managed = TimeHash(sha1managed);
		var hashSha1Csp = TimeHash(sha1csp);
		var hashSha256Managed = TimeHash(sha256managed);
		var hashSha256 = TimeHash(sha256);
		var hashSha384Managed = TimeHash(sha384managed);
		var hashSha384 = TimeHash(sha384);
		var hashSha512Managed = TimeHash(sha512managed);
		var hashSha512 = TimeHash(sha512);
		var hashHmacMd5 = TimeHash(hmacMd5);
		var hashHmacRipeMd160 = TimeHash(hmacRipeMd160);
		var hashHmacTripleDes = TimeHash(macTripleDes);
		var hashHmacSha1 = TimeHash(hmacSha1);
		var hashHmacSha256 = TimeHash(hmacSha256);
		var hashHmacSha384 = TimeHash(hmacSha384);
		var hashHmacSha512 = TimeHash(hmacSha512);
		var hashHmacMd5Keyed = TimeHash(hmacMd5Keyed);
		var hashHmacRipeMd160Keyed = TimeHash(hmacRipeMd160Keyed);
		var hashHmacTripleDesKeyed = TimeHash(macTripleDesKeyed);
		var hashHmacSha1Keyed = TimeHash(hmacSha1Keyed);
		var hashHmacSha1KeyedManaged = TimeHash(hmacSha1KeyedManaged);
		var hashHmacSha256Keyed = TimeHash(hmacSha256Keyed);
		var hashHmacSha384Keyed = TimeHash(hmacSha384Keyed);
		var hashHmacSha512Keyed = TimeHash(hmacSha512Keyed);
		report = "Hash Algorithm,1000x Create Time,1MB Hash Time\n"
			+ "MD5," + createMd5 + "," + hashMd5 + "\n"
			+ "MD5CryptoServiceProvider," + createMd5Csp + "," + hashMd5Csp + "\n"
			+ "RIPEMD160Managed," + createRipeMd160Managed + "," + hashRipeMd160Managed + "\n"
			+ "RIPEMD160," + createRipeMd160 + "," + hashRipeMd160 + "\n"
			+ "SHA1Managed," + createSha1Managed + "," + hashSha1Managed + "\n"
			+ "SHA1CryptoServiceProvider," + createSha1Csp + "," + hashSha1Csp + "\n"
			+ "SHA256Managed," + createSha256Managed + "," + hashSha256Managed + "\n"
			+ "SHA256," + createSha256 + "," + hashSha256 + "\n"
			+ "SHA384Managed," + createSha384Managed + "," + hashSha384Managed + "\n"
			+ "SHA384," + createSha384 + "," + hashSha384 + "\n"
			+ "SHA512Managed," + createSha512Managed + "," + hashSha512Managed + "\n"
			+ "SHA512," + createSha512 + "," + hashSha512 + "\n"
			+ "HMACMD5," + createHmacMd5 + "," + hashHmacMd5 + "\n"
			+ "HMACRIPEMD160," + createHmacRipeMd160 + "," + hashHmacRipeMd160 + "\n"
			+ "MACTripleDes," + createHmacTripleDes + "," + hashHmacTripleDes + "\n"
			+ "HMACSHA1," + createHmacSha1 + "," + hashHmacSha1 + "\n"
			+ "HMACSHA256," + createHmacSha256 + "," + hashHmacSha256 + "\n"
			+ "HMACSHA384," + createHmacSha384 + "," + hashHmacSha384 + "\n"
			+ "HMACSHA512," + createHmacSha512 + "," + hashHmacSha512 + "\n"
			+ "HMACMD5 (keyed)," + createHmacMd5Keyed + "," + hashHmacMd5Keyed + "\n"
			+ "HMACRIPEMD160 (keyed)," + createHmacRipeMd160Keyed + "," + hashHmacRipeMd160Keyed + "\n"
			+ "MACTripleDes (keyed)," + createHmacTripleDesKeyed + "," + hashHmacTripleDesKeyed + "\n"
			+ "HMACSHA1 (keyed)," + createHmacSha1Keyed + "," + hashHmacSha1Keyed + "\n"
			+ "HMACSHA1 (keyed+managed)," + createHmacSha1KeyedManaged + "," + hashHmacSha1KeyedManaged + "\n"
			+ "HMACSHA256 (keyed)," + createHmacSha256Keyed + "," + hashHmacSha256Keyed + "\n"
			+ "HMACSHA384 (keyed)," + createHmacSha384Keyed + "," + hashHmacSha384Keyed + "\n"
			+ "HMACSHA512 (keyed)," + createHmacSha512Keyed + "," + hashHmacSha512Keyed + "\n"
			;
	}
 
	void OnGUI()
	{
		GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report);
	}
}

If you want to try out the test yourself, simply paste the above code into a TestScript.cs file in your Unity project’s Assets directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:

  • 2.3 Ghz Intel Core i7-3615QM
  • Mac OS X 10.10.5
  • Unity 5.1.2f1, Mac OS X Standalone, x86_64, non-development
  • 640×480, Fastest, Windowed

And here are the results I got:

Hash Algorithm 1000x Create Time 1MB Hash Time
MD5 14 6
MD5CryptoServiceProvider 4 4
RIPEMD160Managed 10 27
RIPEMD160 8 25
SHA1Managed 1 9
SHA1CryptoServiceProvider 2 8
SHA256Managed 1 16
SHA256 6 15
SHA384Managed 0 12
SHA384 5 12
SHA512Managed 0 16
SHA512 6 19
HMACMD5 13 17
HMACRIPEMD160 14 33
MACTripleDes 21 203
HMACSHA1 13 16
HMACSHA256 12 21
HMACSHA384 12 16
HMACSHA512 12 21
HMACMD5 (keyed) 13 16
HMACRIPEMD160 (keyed) 16 38
MACTripleDes (keyed) 14 201
HMACSHA1 (keyed) 11 17
HMACSHA1 (keyed+managed) 10 16
HMACSHA256 (keyed) 8 21
HMACSHA384 (keyed) 8 16
HMACSHA512 (keyed) 8 21

Hash Algorithm Performance

Hash Algorithm Performance (HMAC)

Hash Algorithm Performance (TripleDES)

There’s a lot of variability in the results, so I’ve split them up into three graphs to compare similar tests. All in its own test is the MACTripleDES class which is horrendously slow compared to every other algorithm. Don’t use it unless you really have to.

Elsewhere I’ve split the keyed/HMAC algorithms from the non-keyed algorithms. The non-keyed ones tend to be faster, especially in creation time. Unless you need to use a keyed algorithm for security reasons, stick with the non-keyed version for performance.

One standout across both keyed and non-keyed algorithms is RIPEMD160, which is quite a lot slower at hashing than the rest of the algorithms except TripleDES. Again, unless you need it specifically it’s better to look elsewhere.

That leaves the rest of the algorithms as close competitors. Even so, there are up to 3x differences between them. The classic MD5 is the slowest to create, but hashes the quickest. Cache any instances you create if you’re going to hash a lot of byte arrays!

The SHA algorithms are roughly in order of their complexity. SHA-1 is fastest, then the SHA-2 family: SHA-256, SHA-384, SHA-512. For some reason though, SHA-384 clocks in a lot faster than either SHA-256 or SHA-512.

Finally, there are various classes for any given algorithm. Many have “managed” and regular versions and sometimes there is a “CryptoServiceProvider” version. The “managed” version is usually slower, except in the case of SHA-512 and HMAC SHA-1. The “CryptoServiceProvider” classes for MD5 and SHA-1 are also quicker, so you should prefer those when using those algorithms.

Exactly which algorithm you choose will be up to your unique requirements of creation speed, hashing speed, digest (i.e. output) size, and cryptographic strength. However, the above performance numbers should help if that’s a consideration in your choice.

Let me know in the comments if you’ve got any thoughts or experience hashing in Unity.