Skip to content

Commit 889d9c3

Browse files
feat: Added MD5 hashing algorithm (TheAlgorithms#1519)
* feat: Added MD5 hashing algorithm * Added wiki link * Remove spam? * Fix extend towards end * Return Uint32Array in MD5 function * Preprocess function now works on uint arrays * chunkify U32Array instead of string * Remove all string related functions * Replace typed arrays with named variables * Fix "Replace typed arrays with named variables" * Return Uint8 Array in MD5 function The MD5 function now returns a uint8 array with the correct endianness. * Add tests * Fix docstrings * Introduce hexMD5 function * Change test string * Format test file
1 parent aebd52f commit 889d9c3

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

Hashes/MD5.js

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Module that replicates the MD5 Cryptographic Hash
2+
// function in Javascript.
3+
4+
// main variables
5+
const S = [
6+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5,
7+
9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11,
8+
16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15,
9+
21
10+
]
11+
12+
const K = [
13+
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
14+
0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
15+
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
16+
0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
17+
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
18+
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
19+
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
20+
0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
21+
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
22+
0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
23+
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
24+
]
25+
26+
/**
27+
* Separates an array into equal sized chunks
28+
*
29+
* @param {Array|string} array - array or string to separate into chunks
30+
* @param {number} size - number of elements wanted in each chunk
31+
* @return {Array} - array of original array split into chunks
32+
*
33+
* @example
34+
* chunkify("this is a test", 2)
35+
*/
36+
function chunkify(array, size) {
37+
const chunks = []
38+
for (let i = 0; i < array.length; i += size) {
39+
chunks.push(array.slice(i, i + size))
40+
}
41+
return chunks
42+
}
43+
44+
/**
45+
* Rotates the bits to the left
46+
*
47+
* @param {number} bits - 32 bit number
48+
* @param {number} turns - number of rotations to make
49+
* @return {number} - number after bits rotation
50+
*
51+
* @example
52+
* rotateLeft(0b1011, 3); // 0b1011000
53+
*/
54+
function rotateLeft(bits, turns) {
55+
return (bits << turns) | (bits >>> (32 - turns))
56+
}
57+
58+
/**
59+
* Converts Uint8Array to Uint32Array
60+
*
61+
* @param {Uint8Array} u8Array Uint8Array to convert
62+
* @returns {Uint32Array} - Required Uint32Array
63+
*/
64+
function u8ToU32(u8Array) {
65+
const uint32Array = new Uint32Array(u8Array.length / 4)
66+
67+
for (let i = 0; i < u8Array.length; i += 4) {
68+
uint32Array[i / 4] =
69+
(u8Array[i] |
70+
(u8Array[i + 1] << 8) |
71+
(u8Array[i + 2] << 16) |
72+
(u8Array[i + 3] << 24)) >>>
73+
0
74+
}
75+
76+
return uint32Array
77+
}
78+
79+
/**
80+
* Converts Uint32Array to Uint8Array
81+
*
82+
* @param {Uint32Array} u32Array Uint32Array to convert
83+
* @returns {Uint8Array} - Required Uint8Array
84+
*/
85+
function u32ToU8(u32Array) {
86+
const uint8Array = new Uint8Array(u32Array.length * 4)
87+
88+
for (let i = 0; i < u32Array.length; i++) {
89+
uint8Array[i * 4] = u32Array[i] & 0xff
90+
uint8Array[i * 4 + 1] = (u32Array[i] >> 8) & 0xff
91+
uint8Array[i * 4 + 2] = (u32Array[i] >> 16) & 0xff
92+
uint8Array[i * 4 + 3] = (u32Array[i] >> 24) & 0xff
93+
}
94+
95+
return uint8Array
96+
}
97+
98+
/**
99+
* Adds padding to the end of the given array
100+
*
101+
* @param {Uint8Array} u8Array Array to pad
102+
* @param {number} size Resulting size of the array
103+
*/
104+
function padEnd(u8Array, size) {
105+
const result = new Uint8Array(size)
106+
result.set(u8Array)
107+
result.fill(0, u8Array.length)
108+
109+
return result
110+
}
111+
112+
/**
113+
* Pre-processes message to feed the algorithm loop
114+
*
115+
* @param {Uint8Array} message - message to pre-process
116+
* @return {Uint32Array} - processed message
117+
*/
118+
function preProcess(message) {
119+
// Extend message by adding '0'
120+
//
121+
// message.length + 1 is for adding '1' bit
122+
// 56 - (length % 64) is for padding with '0's
123+
// 8 is for appending 64 bit message length
124+
let m = padEnd(
125+
message,
126+
message.length + 1 + (56 - ((message.length + 1) % 64)) + 8
127+
)
128+
129+
// Add '1' bit at the end of the message
130+
m[message.length] = 1 << 7
131+
132+
// convert message to 32 bit uint array
133+
m = u8ToU32(m)
134+
135+
// Append the length of the message to the end
136+
// (ml / 0x100000000) | 0 is equivalent to (ml >> 32) & 0xffffffff) in other languages
137+
let ml = message.length * 8
138+
m[m.length - 2] = ml & 0xffffffff
139+
m[m.length - 1] = (ml / 0x100000000) | 0
140+
141+
return m
142+
}
143+
144+
/**
145+
* Hashes message using MD5 Cryptographic Hash Function
146+
*
147+
* @see
148+
* For more info: https://en.wikipedia.org/wiki/MD5
149+
*
150+
* @param {Uint8Array} message - message to hash
151+
* @return {Uint8Array} - message digest (hash value)
152+
*/
153+
function MD5(message) {
154+
// Initialize variables:
155+
let [a0, b0, c0, d0] = [
156+
0x67452301 >>> 0,
157+
0xefcdab89 >>> 0,
158+
0x98badcfe >>> 0,
159+
0x10325476 >>> 0
160+
]
161+
162+
// pre-process message and split into 512 bit chunks
163+
const words = Array.from(preProcess(message))
164+
const chunks = chunkify(words, 16)
165+
166+
chunks.forEach(function (chunk, _) {
167+
// initialize variables for this chunk
168+
let [A, B, C, D] = [a0, b0, c0, d0]
169+
170+
for (let i = 0; i < 64; i++) {
171+
let [F, g] = [0, 0]
172+
173+
if (i <= 15) {
174+
F = (B & C) | (~B & D)
175+
g = i
176+
} else if (i <= 31) {
177+
F = (D & B) | (~D & C)
178+
g = (5 * i + 1) % 16
179+
} else if (i <= 47) {
180+
F = B ^ C ^ D
181+
g = (3 * i + 5) % 16
182+
} else {
183+
F = C ^ (B | ~D)
184+
g = (7 * i) % 16
185+
}
186+
187+
F = (F + A + K[i] + chunk[g]) >>> 0
188+
A = D
189+
D = C
190+
C = B
191+
B = ((B + rotateLeft(F, S[i])) & 0xffffffff) >>> 0
192+
}
193+
194+
// add values for this chunk to main hash variables (unsigned)
195+
a0 = (a0 + A) >>> 0
196+
b0 = (b0 + B) >>> 0
197+
c0 = (c0 + C) >>> 0
198+
d0 = (d0 + D) >>> 0
199+
})
200+
201+
return u32ToU8([a0, b0, c0, d0])
202+
}
203+
204+
// export MD5 function
205+
export { MD5 }

Hashes/tests/MD5.test.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { MD5 } from '../MD5'
2+
3+
/**
4+
* Returns the MD5 hash of the given message as a hexadecimal string
5+
*
6+
* @param {Uint8Array} message - message to hash
7+
* @return {string} - hash as a hexadecimal string
8+
*/
9+
function hexMD5(message) {
10+
return Array.from(MD5(message), (byte) =>
11+
byte.toString(16).padStart(2, '0')
12+
).join('')
13+
}
14+
15+
describe('Testing MD5 function', () => {
16+
it('should return the correct hash for "The quick brown fox jumps over the lazy dog"', () => {
17+
const input = new TextEncoder().encode(
18+
'The quick brown fox jumps over the lazy dog'
19+
)
20+
const hash = hexMD5(input)
21+
22+
expect(hash).toBe('9e107d9d372bb6826bd81d3542a419d6')
23+
})
24+
25+
it('should return the correct hash for "JavaScript!"', () => {
26+
const input = new TextEncoder().encode('JavaScript!')
27+
const hash = hexMD5(input)
28+
29+
expect(hash).toBe('209eddd6b61af0643907a8e069a08fb8')
30+
})
31+
32+
it('should correctly hash an empty string', () => {
33+
const input = new TextEncoder().encode('')
34+
const hash = hexMD5(input)
35+
36+
expect(hash).toBe('d41d8cd98f00b204e9800998ecf8427e')
37+
})
38+
})

0 commit comments

Comments
 (0)