21
Hash Algorithm in .NET Core
Many developers whose starting out in .NET Core under the System.Security.Cryptography
namespaces are quite confused because most of them don’t know where to start.
At least learning the hash function is a good start, then learn the other two types of cryptography algorithms, which are symmetric and asymmetric.
However, in this article, we’ll cover the hashing algorithm, and we’ll show the basic steps of using the hashing algorithm using the .NET Core library.
In addition, I have created a library wherein you can choose any hashing algorithm.
There is a lot of definitions of hash function on the internet. Let me try to describe it for you.
Once you have an input and that input will be computed, and once it is calculated, a hash value will return.
For instance, let’s say I have a string input of ‘jin’ without the quotes.
jin
Once you have passed this (‘jin’) inside the hash function, the hash function will return a computed hash value that will be unreadable.
For example, please see the result below.
4e766f099e980c1ed89164271b470d32d330563ac6f866f3a46146bb429bb71f
Please, remember that it is one way and can’t be reversed.
Hopefully, I was able to describe to you to the level that anyone can understand.
Now, going back to the formal definition.
Hashing is a way of calculating a fixed length of some data so that no two inputs produce the same output (hash value). Therefore, if there are changes in the input data, the hash value changes as well.
Another thing to note: the hash is permanently fixed in length. It has no direct relationship to the input length.
If you didn’t understand that, you could go back to my first personal definition of it 😊.
But anyway, that’s not the point here. I know you would like to see the codes to get you started, but before that, let’s tackle the common hash functions. At least we are familiar with them before digging into the code for lesser confusion.
Several hash functions are widely used in different organizations.
The truth is most of these hash functions or hash algorithms were designed by great minds, mathematicians, and computer scientists.
But I’m not in that level, though, 😊.
And of course, as time goes by, some of these hash functions have shown to have weaknesses, but all are considered good enough for noncryptographic applications and, of course if used properly.
The Message-Digest (MD5) algorithm is the fastest and widely used hash function. It produces a 128-bit hash value. At first, it was designed to be used as a cryptographic hash function, but it has been found to have a lot of vulnerabilities. Therefore, it is no longer recommended. However, it is still used for database partitioning and computing checksums to validate file transfers even today.
It is the first version of the Secure Hash Algorithm (SHA) algorithm family, and it generates a 160-bit hash (20 bytes) compared to MD5, which produces a 128-bit hash.
However, like MD5, SHA1 was soon found to have vulnerabilities. Therefore, today, it is no longer considered to be any less resistant to attack than MD5.
It is the second version of the Secure Hash Algorithm (SHA) algorithm family, and it has many variants.
Here are the commonly known variants SHA-256 and SHA-512.
Both are different in word size. SHA-256 uses 32-byte words (256 bit), while SHA-512 uses 65-byte words (512 bit).
SHA-256 is widely used today and is highly recommended by the National Institute of Standards and Technology. There are also truncated versions such as SHA-224, SHA-384, SHA-512/224, and SHA-512/256.
It is the third version of the Secure Hash Algorithm (SHA) algorithm family, and it supports the same hash lengths as SHA-2 but essentially differs from the rest of the SHA family to its internal structure.
The hashing functions are represented as classes in the .NET Core framework library: MD5, SHA1, SHA256, SHA384, and SHA512.
Moreover, these abstract classes are derived from HashAlgorithm
class which is also the parent of these classes. To appreciate even more, please see the diagram below for us to see their relationships.
But wait, if you’re coming from the .NET Framework, you’ll be expecting RIPEMD160
should be part of the derived classes of HashAlgorithm
. However, this time it’s not around 😢. We’re not informed that RIPEMD160
was removed for some reason. You can check can it here. But that’s OK, in my opinion. But anyway, if you’re still interested in looking into it, you can check the source code directly here.
- Create the hash algorithm instance. You can choose from MD5, SHA1, SHA256, SHA384, and SHA512.
- Invoke the
ComputeHash
method by passing a byte array. Just remember, you can pass any raw data, and you need to convert that raw data into a byte array. - The
ComputeHash
method, after a successful execution it will return a byte array, and you should store that in a variable. - After the successful execution of
ComputeHash
, you can then convert the byte array into a string.
See, not so many steps, easy to remember, right?
So, let’s try to see an example below, and let’s just try to use SHA256
using System;
using System.Security.Cryptography;
using System.Text;
namespace HashingAlgorith.SHA256.Example
{
class Program
{
static void Main(string[] args)
{
string rawData = "I love C#";
string result = string.Empty;
/*1. Create the hash algorithm instance.
You can choose from MD5, SHA1, SHA256, SHA384,
and SHA512.*/
using (var myHash = SHA256Managed.Create())
{
/*
* 2. Invoke the ComputeHash method by passing
a byte array.
* Just remember, you can pass any raw data,
and you need to convert that raw data
into a byte array.
*/
var byteArrayResultOfRawData =
Encoding.UTF8.GetBytes(rawData);
/*
* 3. The ComputeHash method, after a successful
execution it will return a byte array,
* and you should store that in a variable.
*/
var byteArrayResult =
myHash.ComputeHash(byteArrayResultOfRawData);
/*
* 4. After the successful execution of ComputeHash,
you can then convert
the byte array into a string.
*/
result =
string.Concat(Array.ConvertAll(byteArrayResult,
h => h.ToString("X2")));
}
Console.WriteLine($"The {rawData} will be converted into this: {Environment.NewLine}{result}");
Console.ReadLine();
}
}
}
This section will show a helper class to quickly choose the algorithm you want bypassing as an argument.
Let’s see an example below.
using HashingAlgorihtm.Library;
namespace HashingAlgorithm.Console.App
{
class Program
{
static void Main(string[] args)
{
string plainText = "Hello World";
/*
* In this example, I believe it is a straightforward
call or invoke of the method.
* First, you just needed to pass the
arguments such as plainText, the raw data
in this case, ‘Hello World.’
* Second, you can choose from
the different algorithms available:
MD5, SHA1, SHA256, SHA384, and SHA512.
*/
string hashResult =
HashingHelper.ComputeHashInLowerCase(plainText,
HashingClassAlgorithms.MD5);
System.Console.WriteLine(hashResult);
System.Console.ReadLine();
}
}
}
From the code example above, it is pretty easy, in my opinion. You just need to pass the plaintext and choose your algorithm. By the way, when using this helper, the algorithm is an enumeration, so you can quickly select from it and no need to type the name in a string format.
If you are really interested, you can read the entire code of the helper class below. Just a warning, the code is as-is no warranties or whatsoever. You can modify and improve it at your own risk. Thanks.
using System;
using System.Security.Cryptography;
using System.Text;
namespace HashingAlgorihtm.Library
{
public enum HashingClassAlgorithms
{
MD5 = 0,
SHA1 = 1,
SHA256 = 2,
SHA384 = 3,
SHA512 = 4
}
public sealed class Hashing
{
public HashingClassAlgorithms HashingAlgorithms
{ get; private set; }
public Hashing(HashingClassAlgorithms hashingAlgorithm)
{
this.HashingAlgorithms = hashingAlgorithm;
}
public HashAlgorithm CreateNewInstance()
{
HashAlgorithm result = null;
switch (this.HashingAlgorithms)
{
case HashingClassAlgorithms.MD5:
result = MD5.Create();
break;
case HashingClassAlgorithms.SHA1:
result = SHA1Managed.Create();
break;
case HashingClassAlgorithms.SHA256:
result = SHA256Managed.Create();
break;
case HashingClassAlgorithms.SHA384:
result = SHA384.Create();
break;
case HashingClassAlgorithms.SHA512:
result = SHA512Managed.Create();
break;
}
return result;
}
}
public class HashingHelper
{
private static void Validate(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
throw new
ArgumentNullException
("Input parameter is empty or only contains whitespace");
}
}
private static void Validate(HashAlgorithm algorithm)
{
if (algorithm == null)
{
throw new ArgumentNullException("algorithm is null");
}
}
private static HashAlgorithm CreateNewInstance(HashingClassAlgorithms algorithm)
{
return new Hashing(algorithm).CreateNewInstance();
}
private static void ValidateParams(string input, HashAlgorithm algorithm)
{
Validate(input);
Validate(algorithm);
}
private static string ComputeHash(string input, HashAlgorithm algorithm, bool upperCase = true)
{
string result = string.Empty;
var hashingService = algorithm;
using (hashingService)
{
byte[] hash = hashingService.ComputeHash(Encoding.UTF8.GetBytes(input));
result = string.Concat(Array.ConvertAll(hash, h => h.ToString($"{(upperCase ? "X2" : "x2")}")));
}
return result;
}
private static HashAlgorithm ProcessAlgorithm(string input, HashingClassAlgorithms algorithm)
{
HashAlgorithm result = CreateNewInstance(algorithm);
ValidateParams(input, result);
return result;
}
public static string ComputeHashInUpperCase(string input, HashingClassAlgorithms algorithm)
{
HashAlgorithm result = ProcessAlgorithm(input, algorithm);
return ComputeHash(input, result);
}
/// <summary>
/// Computes the hash returns its value.
/// </summary>
/// <param name="input">plainText</param>
/// <param name="algorithm">Name of the algorithm that you are interested using.</param>
/// <returns></returns>
public static string ComputeHashInLowerCase(string input, HashingClassAlgorithms algorithm)
{
HashAlgorithm result = ProcessAlgorithm(input, algorithm);
return ComputeHash(input, result, false);
}
}
}
For the last section of the helper library, we always do some tests on the project as developers. Thus, I decided to make a unit test for this library if it works as expected and gives the exact results based on the bit and bytes we hope.
Please see the entire unit test below. After that, it’s up to you to judge.
Again, this is just a simple helper class that is not used or meant to use in any production environment; I just did this out of curiosity while learning the hashing algorithm.
using HashingAlgorihtm.Library;
using NUnit.Framework;
using System.Linq;
using System.Text;
namespace HashingAlgorithm.Test
{
/*
*
*/
public class HashingAlgorithmHelperTest
{
private string plainText = "Stay hungry and stay foolish";
/*
* MD5 yields hexadecimal digits (0-15 / 0-F), so they are four bits each. 128 / 4 = 32 characters.
*/
[Test]
public void Hashing_Use_MD5_Test()
{
var result1 = HashingHelper.ComputeHashInLowerCase(plainText, HashingClassAlgorithms.MD5);
var result2 = HashingHelper.ComputeHashInUpperCase(plainText, HashingClassAlgorithms.MD5);
TestContext.WriteLine($"{plainText} using MD5 in lowercase: {result1} and in uppercase: {result2}");
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result1) == 32);
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result2) == 32);
Assert.IsTrue(result1.ToCharArray().All(c => char.IsLower(c) || char.IsDigit(c)));
Assert.IsTrue(result2.ToCharArray().All( c=> char.IsUpper(c) || char.IsDigit(c)));
}
/**
* SHA-1 yields hexadecimal digits too (0-15 / 0-F), so 160 / 4 = 40 characters.
*/
[Test]
public void Hashing_Use_SHA1_Test()
{
var result1 = HashingHelper.ComputeHashInLowerCase(plainText, HashingClassAlgorithms.SHA1);
var result2 = HashingHelper.ComputeHashInUpperCase(plainText, HashingClassAlgorithms.SHA1);
TestContext.WriteLine($"{plainText} using MD5 in lowercase: {result1} and in uppercase: {result2}");
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result1) == 40);
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result2) == 40);
Assert.IsTrue(result1.ToCharArray().All(c => char.IsLower(c) || char.IsDigit(c)));
Assert.IsTrue(result2.ToCharArray().All(c => char.IsUpper(c) || char.IsDigit(c)));
}
/**
* SHA-2 (256 variant) yields hexadecimal digits too (0-15 / 0-F), so 256 / 4 = 64 characters.
*/
[Test]
public void Hashing_Use_SHA256_Test()
{
var result1 = HashingHelper.ComputeHashInLowerCase(plainText, HashingClassAlgorithms.SHA256);
var result2 = HashingHelper.ComputeHashInUpperCase(plainText, HashingClassAlgorithms.SHA256);
TestContext.WriteLine($"{plainText} using MD5 in lowercase: {result1} and in uppercase: {result2}");
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result1) == 64);
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result2) == 64);
Assert.IsTrue(result1.ToCharArray().All(c => char.IsLower(c) || char.IsDigit(c)));
Assert.IsTrue(result2.ToCharArray().All(c => char.IsUpper(c) || char.IsDigit(c)));
}
/**
* SHA-2 (384 variant) yields hexadecimal digits too (0-15 / 0-F), so 384 / 4 = 96 characters.
*/
[Test]
public void Hashing_Use_SHA384_Test()
{
var result1 = HashingHelper.ComputeHashInLowerCase(plainText, HashingClassAlgorithms.SHA384);
var result2 = HashingHelper.ComputeHashInUpperCase(plainText, HashingClassAlgorithms.SHA384);
TestContext.WriteLine($"{plainText} using MD5 in lowercase: {result1} and in uppercase: {result2}");
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result1) == 96);
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result2) == 96);
Assert.IsTrue(result1.ToCharArray().All(c => char.IsLower(c) || char.IsDigit(c)));
Assert.IsTrue(result2.ToCharArray().All(c => char.IsUpper(c) || char.IsDigit(c)));
}
/**
* SHA-2 (512 variant) yields hexadecimal digits too (0-15 / 0-F), so 512 / 4 = 128 characters.
*/
[Test]
public void Hashing_Use_SHA512_Test()
{
var result1 = HashingHelper.ComputeHashInLowerCase(plainText, HashingClassAlgorithms.SHA512);
var result2 = HashingHelper.ComputeHashInUpperCase(plainText, HashingClassAlgorithms.SHA512);
TestContext.WriteLine($"{plainText} using MD5 in lowercase: {result1} and in uppercase: {result2}");
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result1) == 128);
Assert.IsTrue(UTF8Encoding.UTF8.GetByteCount(result2) == 128);
Assert.IsTrue(result1.ToCharArray().All(c => char.IsLower(c) || char.IsDigit(c)));
Assert.IsTrue(result2.ToCharArray().All(c => char.IsUpper(c) || char.IsDigit(c)));
}
}
}
There you have it guys, in this post, we have started by describing what’s a hashing function and we have seen some of the common hashing function algorithms.
Then we have shown a simple code sample on implementing a hashing algorithm using the .NET Core library.
Lastly, we have seen a helper class that we can easily use, less typing and more of a straightforward helper method.
If you have to have the code examples, you get it here on GitHub.
jindeveloper / Hash-Algorithm-Helper-Library-in-.NET-Core
Hash Algorithm Helper Library in .NET Core
Once again, I hope you have enjoyed this article/tutorial as I have enjoyed writing it.
Stay tuned for more (keep visiting our blog and share this with your friends, colleagues, and network).
Until next time, happy programming!
Please don’t forget to bookmark, like, and comment. Cheers! And Thank you!
21