So I was wondering if there are any major differences between the various implementations of the hash algorithms, take the SHA series of algorithms for example. All of them have 3 implementations each, 1 in managed code and 2 wrappers around different native crypto APIs, but are there any major differences between using any of them? I can imagine that the wrapper versions could have higher performance since its being executed in native code, but surley hey all need to perform the exact same calculations and thus provide the same output ie hey are interchangable. Is this correct?
For instance SHA512CNG cant be used on XP SP2 (docs are wrong) but SHA512MANAGED can.
@Maxim - Thank you, but not quite what I was asking for. I was asking if there is any difference, other than possibly performance, from using the Managed/CryptoServiceProvider/CNG implementations of a given hash algorithm. With .NET 3.5 you get all of the hash algorithms with three implementations, so
SHA512Managed
SHA512CryptoServiceProvider
SHA512Cng
The latter two being wrappers around native APIs. This is true for all SHAxxx implementations for example.
One difference is that the native versions (at least some of them) are FIPS-certified (i.e., approved by the US government), whereas the managed ones are not. If your code happens to be running on a Windows machine that has been configured as "FIPS only", attempts to use the managed versions will fail.
Most Windows machines are not configured in that way, but if you're deploying to a government- or defense-oriented (or other highly secure) environment you may run into this situation.
See http://blogs.msdn.com/shawnfa/archive/2005/05/16/417975.aspx.
The Cng versions are supposed to be a little faster, but I just wrote up a little program that compares the speeds of each. (I had a client that was asking about the performance characteristics of MD5 vs. SHA1)
I was surprised to find out there is little to no difference between MD5 and SHA1, but was also surprised that there is a slight difference in Cng and the CryptoServiceProvider.
The source is pretty straight forward, I added reps to do the same iteration multiple times so I could average in case there was any weirdness going on, on my machine during one of the runs.
call the following with a call like this:
CalculateHash(1, 1024, new SHA1CryptoServiceProvider());
static long CalculateHash(UInt64 repetitions, UInt64 size, HashAlgorithm engine)
{
RandomNumberGenerator rng = RandomNumberGenerator.Create();
byte[][] goo = new byte[repetitions][];
for (UInt64 i = 0; i < repetitions; i++)
{
goo[i] = new byte[size];
rng.GetBytes(goo[i]);
}
DateTime start = DateTime.Now;
for (UInt64 i = 0; i < repetitions; i++)
{
engine.ComputeHash(goo[i]);
}
return DateTime.Now.Subtract(start).Ticks;
}
I ran this in a loop of increasing size to figure out if one fell over when using large or small inputs. Here is the loop, and the data follows (my computer ran out of ram at 2^28):
int loops = 32;
UInt64 reps = 1;
int width = 20;
Console.WriteLine("Loop#".PadRight(6) +
"MD5".PadRight(width) +
"SHA1".PadRight(width) +
"SHA1Cng".PadRight(width) +
"SHA256".PadRight(width) +
"SHA256Cng".PadRight(width));
for (int i = 0; i < loops; i++)
{
UInt64 size = (UInt64)Math.Pow((double)2, (double)i);
Console.WriteLine((i + 1).ToString().PadRight(6) +
CalculateHash(reps, size, new MD5CryptoServiceProvider()).ToString().PadRight(width) +
CalculateHash(reps, size, new SHA1CryptoServiceProvider()).ToString().PadRight(width) +
CalculateHash(reps, size, new SHA1Cng() ).ToString().PadRight(width) +
CalculateHash(reps, size, new SHA256CryptoServiceProvider()).ToString().PadRight(width) +
CalculateHash(reps, size, new SHA256Cng()).ToString().PadRight(width));
}
Loop# MD5 SHA1 SHA1Cng SHA256 SHA256Cng
1 50210 0 0 0 0
2 0 0 0 0 0
3 0 0 0 0 0
4 0 0 0 0 0
5 0 0 0 0 0
6 0 0 0 0 0
7 0 0 0 0 0
8 0 0 0 0 0
9 0 0 0 0 0
10 0 0 10042 0 0
11 0 0 0 0 0
12 0 0 0 0 0
13 0 0 0 0 0
14 0 0 0 0 0
15 10042 0 0 10042 10042
16 10042 0 0 0 0
17 0 0 0 10042 10042
18 0 10042 10042 20084 10042
19 0 10042 10042 30126 40168
20 20084 20084 20084 70294 70294
21 30126 40168 40168 140588 140588
22 60252 70294 80336 291218 281176
23 120504 140588 180756 572394 612562
24 241008 281176 361512 1144788 1215082
25 482016 572394 723024 2289576 2420122
26 953990 1134746 1456090 4538984 4830202
27 1907980 2259450 2982474 9118136 9660404
28 3805918 4508858 5804276 18336692 19581900
I did a quick and dirty comparison between CNG
and managed
on SHA512 which is the slowest of all SHA algorithms AFAIK using the code below.
static void Main(string[] args)
{
int loops = 10000000;
var data = Encoding.ASCII.GetBytes("123");
var hashLoop = new Action<HashAlgorithm>((HashAlgorithm ha) =>
{
for (int i = 0; i < loops; i++)
ha.ComputeHash(data);
});
var t1 = Task.Factory.StartNew(() =>
{
Time(hashLoop, new SHA512Managed());
});
var t2 = Task.Factory.StartNew(() =>
{
Time(hashLoop, new SHA512Cng());
});
Task.WaitAll(t1, t2);
Console.WriteLine("Benchmark done!");
Console.ReadKey();
}
static void Time(Action<HashAlgorithm> action, HashAlgorithm ha)
{
var sw = new Stopwatch();
sw.Start();
action(ha);
sw.Stop();
Console.WriteLine("{1} done in {0}ms", sw.ElapsedMilliseconds, ha.ToString());
}
After several runs I found that the difference was that CNG was considerably faster than managed version of the algorithm with about 21.7% to 49.5%
Another difference between the Managed and the CNG versions is the supported .Net Framework version: e.g.
- the AES Managed version starts from 3.5, while the CNG from 4.6.2 and for
- SHA512, Managed starts from 1.1 and Cng from 3.5.
However, I believe that if we are not constrained by the framework version or to support legacy OS versions, we should use the CNG versions:
- The hashing algorithms postfixed with Cng are the only ones that use bcrypt
- The fact that it might take longer is actually an advantage as it protects from brute force attacks: on the user side 300ms or 3ms makes no difference, while for an attacker it is an order 100 magnitude!