mirror of
https://github.com/TheAlgorithms/JavaScript.git
synced 2025-07-05 00:01:37 +08:00
feat: Added MD5 hashing algorithm (#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
This commit is contained in:
205
Hashes/MD5.js
Normal file
205
Hashes/MD5.js
Normal file
@ -0,0 +1,205 @@
|
||||
// Module that replicates the MD5 Cryptographic Hash
|
||||
// function in Javascript.
|
||||
|
||||
// main variables
|
||||
const S = [
|
||||
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
|
||||
]
|
||||
|
||||
const K = [
|
||||
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
|
||||
]
|
||||
|
||||
/**
|
||||
* Separates an array into equal sized chunks
|
||||
*
|
||||
* @param {Array|string} array - array or string to separate into chunks
|
||||
* @param {number} size - number of elements wanted in each chunk
|
||||
* @return {Array} - array of original array split into chunks
|
||||
*
|
||||
* @example
|
||||
* chunkify("this is a test", 2)
|
||||
*/
|
||||
function chunkify(array, size) {
|
||||
const chunks = []
|
||||
for (let i = 0; i < array.length; i += size) {
|
||||
chunks.push(array.slice(i, i + size))
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the bits to the left
|
||||
*
|
||||
* @param {number} bits - 32 bit number
|
||||
* @param {number} turns - number of rotations to make
|
||||
* @return {number} - number after bits rotation
|
||||
*
|
||||
* @example
|
||||
* rotateLeft(0b1011, 3); // 0b1011000
|
||||
*/
|
||||
function rotateLeft(bits, turns) {
|
||||
return (bits << turns) | (bits >>> (32 - turns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Uint8Array to Uint32Array
|
||||
*
|
||||
* @param {Uint8Array} u8Array Uint8Array to convert
|
||||
* @returns {Uint32Array} - Required Uint32Array
|
||||
*/
|
||||
function u8ToU32(u8Array) {
|
||||
const uint32Array = new Uint32Array(u8Array.length / 4)
|
||||
|
||||
for (let i = 0; i < u8Array.length; i += 4) {
|
||||
uint32Array[i / 4] =
|
||||
(u8Array[i] |
|
||||
(u8Array[i + 1] << 8) |
|
||||
(u8Array[i + 2] << 16) |
|
||||
(u8Array[i + 3] << 24)) >>>
|
||||
0
|
||||
}
|
||||
|
||||
return uint32Array
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Uint32Array to Uint8Array
|
||||
*
|
||||
* @param {Uint32Array} u32Array Uint32Array to convert
|
||||
* @returns {Uint8Array} - Required Uint8Array
|
||||
*/
|
||||
function u32ToU8(u32Array) {
|
||||
const uint8Array = new Uint8Array(u32Array.length * 4)
|
||||
|
||||
for (let i = 0; i < u32Array.length; i++) {
|
||||
uint8Array[i * 4] = u32Array[i] & 0xff
|
||||
uint8Array[i * 4 + 1] = (u32Array[i] >> 8) & 0xff
|
||||
uint8Array[i * 4 + 2] = (u32Array[i] >> 16) & 0xff
|
||||
uint8Array[i * 4 + 3] = (u32Array[i] >> 24) & 0xff
|
||||
}
|
||||
|
||||
return uint8Array
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds padding to the end of the given array
|
||||
*
|
||||
* @param {Uint8Array} u8Array Array to pad
|
||||
* @param {number} size Resulting size of the array
|
||||
*/
|
||||
function padEnd(u8Array, size) {
|
||||
const result = new Uint8Array(size)
|
||||
result.set(u8Array)
|
||||
result.fill(0, u8Array.length)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processes message to feed the algorithm loop
|
||||
*
|
||||
* @param {Uint8Array} message - message to pre-process
|
||||
* @return {Uint32Array} - processed message
|
||||
*/
|
||||
function preProcess(message) {
|
||||
// Extend message by adding '0'
|
||||
//
|
||||
// message.length + 1 is for adding '1' bit
|
||||
// 56 - (length % 64) is for padding with '0's
|
||||
// 8 is for appending 64 bit message length
|
||||
let m = padEnd(
|
||||
message,
|
||||
message.length + 1 + (56 - ((message.length + 1) % 64)) + 8
|
||||
)
|
||||
|
||||
// Add '1' bit at the end of the message
|
||||
m[message.length] = 1 << 7
|
||||
|
||||
// convert message to 32 bit uint array
|
||||
m = u8ToU32(m)
|
||||
|
||||
// Append the length of the message to the end
|
||||
// (ml / 0x100000000) | 0 is equivalent to (ml >> 32) & 0xffffffff) in other languages
|
||||
let ml = message.length * 8
|
||||
m[m.length - 2] = ml & 0xffffffff
|
||||
m[m.length - 1] = (ml / 0x100000000) | 0
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes message using MD5 Cryptographic Hash Function
|
||||
*
|
||||
* @see
|
||||
* For more info: https://en.wikipedia.org/wiki/MD5
|
||||
*
|
||||
* @param {Uint8Array} message - message to hash
|
||||
* @return {Uint8Array} - message digest (hash value)
|
||||
*/
|
||||
function MD5(message) {
|
||||
// Initialize variables:
|
||||
let [a0, b0, c0, d0] = [
|
||||
0x67452301 >>> 0,
|
||||
0xefcdab89 >>> 0,
|
||||
0x98badcfe >>> 0,
|
||||
0x10325476 >>> 0
|
||||
]
|
||||
|
||||
// pre-process message and split into 512 bit chunks
|
||||
const words = Array.from(preProcess(message))
|
||||
const chunks = chunkify(words, 16)
|
||||
|
||||
chunks.forEach(function (chunk, _) {
|
||||
// initialize variables for this chunk
|
||||
let [A, B, C, D] = [a0, b0, c0, d0]
|
||||
|
||||
for (let i = 0; i < 64; i++) {
|
||||
let [F, g] = [0, 0]
|
||||
|
||||
if (i <= 15) {
|
||||
F = (B & C) | (~B & D)
|
||||
g = i
|
||||
} else if (i <= 31) {
|
||||
F = (D & B) | (~D & C)
|
||||
g = (5 * i + 1) % 16
|
||||
} else if (i <= 47) {
|
||||
F = B ^ C ^ D
|
||||
g = (3 * i + 5) % 16
|
||||
} else {
|
||||
F = C ^ (B | ~D)
|
||||
g = (7 * i) % 16
|
||||
}
|
||||
|
||||
F = (F + A + K[i] + chunk[g]) >>> 0
|
||||
A = D
|
||||
D = C
|
||||
C = B
|
||||
B = ((B + rotateLeft(F, S[i])) & 0xffffffff) >>> 0
|
||||
}
|
||||
|
||||
// add values for this chunk to main hash variables (unsigned)
|
||||
a0 = (a0 + A) >>> 0
|
||||
b0 = (b0 + B) >>> 0
|
||||
c0 = (c0 + C) >>> 0
|
||||
d0 = (d0 + D) >>> 0
|
||||
})
|
||||
|
||||
return u32ToU8([a0, b0, c0, d0])
|
||||
}
|
||||
|
||||
// export MD5 function
|
||||
export { MD5 }
|
38
Hashes/tests/MD5.test.js
Normal file
38
Hashes/tests/MD5.test.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { MD5 } from '../MD5'
|
||||
|
||||
/**
|
||||
* Returns the MD5 hash of the given message as a hexadecimal string
|
||||
*
|
||||
* @param {Uint8Array} message - message to hash
|
||||
* @return {string} - hash as a hexadecimal string
|
||||
*/
|
||||
function hexMD5(message) {
|
||||
return Array.from(MD5(message), (byte) =>
|
||||
byte.toString(16).padStart(2, '0')
|
||||
).join('')
|
||||
}
|
||||
|
||||
describe('Testing MD5 function', () => {
|
||||
it('should return the correct hash for "The quick brown fox jumps over the lazy dog"', () => {
|
||||
const input = new TextEncoder().encode(
|
||||
'The quick brown fox jumps over the lazy dog'
|
||||
)
|
||||
const hash = hexMD5(input)
|
||||
|
||||
expect(hash).toBe('9e107d9d372bb6826bd81d3542a419d6')
|
||||
})
|
||||
|
||||
it('should return the correct hash for "JavaScript!"', () => {
|
||||
const input = new TextEncoder().encode('JavaScript!')
|
||||
const hash = hexMD5(input)
|
||||
|
||||
expect(hash).toBe('209eddd6b61af0643907a8e069a08fb8')
|
||||
})
|
||||
|
||||
it('should correctly hash an empty string', () => {
|
||||
const input = new TextEncoder().encode('')
|
||||
const hash = hexMD5(input)
|
||||
|
||||
expect(hash).toBe('d41d8cd98f00b204e9800998ecf8427e')
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user