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

Lines changed: 205 additions & 0 deletions
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

Lines changed: 38 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy