Skip to content

Instantly share code, notes, and snippets.

@ameerkat
Created October 29, 2016 08:35
Show Gist options
  • Save ameerkat/07a748c9b571289711ebaf61f4b596e9 to your computer and use it in GitHub Desktop.
Save ameerkat/07a748c9b571289711ebaf61f4b596e9 to your computer and use it in GitHub Desktop.
MD5 Implementation in C# based on Wikipedia psuedocode
using System;
using System.Linq;
namespace MD5
{
/// <summary>
/// RFC for MD5 https://tools.ietf.org/html/rfc1321
/// Based on the pseudo code from Wikipedia: https://en.wikipedia.org/wiki/MD5
/// </summary>
public class MD5
{
/*
* Round shift values
*/
static int[] s = new int[64] {
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
/*
* Constant K Values
*/
static uint[] K = new uint[64] {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
public static uint leftRotate(uint x, int c)
{
return (x << c) | (x >> (32 - c));
}
// assumes whole bytes as input
public static string Calculate(byte[] input)
{
uint a0 = 0x67452301; // A
uint b0 = 0xefcdab89; // B
uint c0 = 0x98badcfe; // C
uint d0 = 0x10325476; // D
var addLength = (56 - ((input.Length + 1) % 64)) % 64; // calculate the new length with padding
var processedInput = new byte[input.Length + 1 + addLength + 8];
Array.Copy(input, processedInput, input.Length);
processedInput[input.Length] = 0x80; // add 1
byte[] length = BitConverter.GetBytes(input.Length * 8); // bit converter returns little-endian
Array.Copy(length, 0, processedInput, processedInput.Length - 8, 4); // add length in bits
for (int i = 0; i < processedInput.Length / 64; ++i)
{
// copy the input to M
uint[] M = new uint[16];
for(int j = 0; j < 16; ++j)
M[j] = BitConverter.ToUInt32(processedInput, (i * 64) + (j * 4));
// initialize round variables
uint A = a0, B = b0, C = c0, D = d0, F = 0, g = 0;
// primary loop
for (uint k = 0; k < 64; ++k)
{
if (k <= 15)
{
F = (B & C) | (~B & D);
g = k;
}
else if (k >= 16 && k <= 31)
{
F = (D & B) | (~D & C);
g = ((5 * k) + 1) % 16;
}
else if (k >= 32 && k <= 47)
{
F = B ^ C ^ D;
g = ((3 * k) + 5) % 16;
}
else if (k >= 48)
{
F = C ^ (B | ~D);
g = (7 * k) % 16;
}
var dtemp = D;
D = C;
C = B;
B = B + leftRotate((A + F + K[k] + M[g]), s[k]);
A = dtemp;
}
a0 += A;
b0 += B;
c0 += C;
d0 += D;
}
return GetByteString(a0) + GetByteString(b0) + GetByteString(c0) + GetByteString(d0);
}
private static string GetByteString(uint x)
{
return String.Join("", BitConverter.GetBytes(x).Select(y => y.ToString("x2")));
}
}
}
@briandunnington
Copy link

I know this file is 6 years old, but it has a bug that will prevent it from generating the correct MD5 values for certain inputs. Specifically, the way that the padding is calculated for inputs that have more than 56 bytes but less than 64 is wrong which results in the wrong hash being generated. The problem can be seen when using the test strings from the RFC (https://www.rfc-editor.org/rfc/rfc1321) such as:

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

One possible fix is to replace lines 57-63 with this instead:

            /* NOTE: this padding code replaces the original implementation that had a bug in it */
            var processedInputBuilder = new List<byte>(input) { 0x80 };
            while (processedInputBuilder.Count % 64 != 56) processedInputBuilder.Add(0x0);
            processedInputBuilder.AddRange(BitConverter.GetBytes((long)input.Length * 8)); // bit converter returns little-endian
            var processedInput = processedInputBuilder.ToArray();
            /* */

@mostafahk
Copy link

I know this file is 6 years old, but it has a bug that will prevent it from generating the correct MD5 values for certain inputs. Specifically, the way that the padding is calculated for inputs that have more than 56 bytes but less than 64 is wrong which results in the wrong hash being generated. The problem can be seen when using the test strings from the RFC (https://www.rfc-editor.org/rfc/rfc1321) such as:

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

One possible fix is to replace lines 57-63 with this instead:

            /* NOTE: this padding code replaces the original implementation that had a bug in it */
            var processedInputBuilder = new List<byte>(input) { 0x80 };
            while (processedInputBuilder.Count % 64 != 56) processedInputBuilder.Add(0x0);
            processedInputBuilder.AddRange(BitConverter.GetBytes((long)input.Length * 8)); // bit converter returns little-endian
            var processedInput = processedInputBuilder.ToArray();
            /* */

You are my hero.

@fperreaultnv
Copy link

... it has a bug that will prevent it from generating the correct MD5 values for certain inputs.

Inputs where (input.Length + 1) % 64 equals between 57 and 63 are problematic indeed, but I believe you can simply replace line 57 with

var addLength = (56 + 64 - ((input.Length + 1) % 64)) % 64; // calculate the new length with padding

Adding the + 64 ensures that we always get a positive number, for (input.Length + 1) % 64 could well be greater than 56, resulting in a negative number, and failing the preprocessing algorithm.

If you think it's clearer, you can also do it like this.

if (addLength < 0) 
{
  addLength += 64;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment