Created
October 29, 2016 08:35
-
-
Save ameerkat/07a748c9b571289711ebaf61f4b596e9 to your computer and use it in GitHub Desktop.
MD5 Implementation in C# based on Wikipedia psuedocode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"))); | |
} | |
} | |
} |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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: