Implementing SHA1 Hash Algorithm in JavaScript
When builidng web applications, securing data transmission betwean client and server becomes essential. SHA1 (Secure Hash Algorithm 1) provides a one-way cryptographic hash function that converts input data into a fixed-size hexadecimal digest, making it useful for integrity verification and digital signature scenarios.
SHA1 Implemantation
The following is a standalone SHA1 implementation using vanilla JavaScript:
function stringToUtf8Bytes(text) {
const result = [];
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i);
if (charCode < 0x80) {
result.push(charCode);
} else if (charCode < 0x800) {
result.push(0xC0 | (charCode >> 6), 0x80 | (charCode & 0x3F));
} else if (charCode < 0x10000) {
result.push(0xE0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3F), 0x80 | (charCode & 0x3F));
} else if (charCode < 0x110000) {
const surrogate = (charCode - 0x10000) >> 10;
const lowSurrogate = charCode & 0x3FF;
result.push(0xF0 | surrogate, 0x80 | ((lowSurrogate >> 6) & 0x3F), 0x80 | (lowSurrogate & 0x3F));
}
}
return result;
}
function rotateLeft(value, shiftAmount) {
return (value << shiftAmount) | (value >>> (32 - shiftAmount));
}
function computeSha1(input) {
const utf8Bytes = stringToUtf8Bytes(input);
const originalLength = utf8Bytes.length;
const paddedLength = ((originalLength + 8) >>> 6 << 4) + 16;
const wordArray = new Uint32Array(paddedLength);
for (let i = 0; i < originalLength; i++) {
const offset = (i >>> 2);
wordArray[offset] |= utf8Bytes[i] << (24 - (i & 3) * 8);
}
const lengthBitOffset = (originalLength >>> 2);
wordArray[lengthBitOffset] |= 0x80 << (24 - (originalLength & 3) * 8);
wordArray[paddedLength - 1] = originalLength << 3;
const h0 = 0x67452301;
const h1 = 0xEFCDAB89;
const h2 = 0x98BADCFE;
const h3 = 0x10325476;
const h4 = 0xC3D2E1F0;
const constants = [
0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6
];
function roundFunction(k) {
return function(m) {
return (m[1] & m[2]) | (~m[1] & m[3]);
};
}
function roundFunction2(k) {
return function(m) {
return m[1] ^ m[2] ^ m[3];
};
}
function roundFunction3(k) {
return function(m) {
return (m[1] & m[2]) | (m[1] & m[3]) | (m[2] & m[3]);
};
}
const functions = [
roundFunction(0), roundFunction2(0), roundFunction3(0), roundFunction2(0)
];
for (let chunk = 0; chunk < wordArray.length; chunk += 16) {
const words = wordArray.subarray(chunk, chunk + 16);
let current = [h0, h1, h2, h3, h4];
const initial = [...current];
const extended = new Uint32Array(80);
for (let i = 0; i < 16; i++) {
extended[i] = words[i];
}
for (let i = 16; i < 80; i++) {
const a = extended[i - 3];
const b = extended[i - 8];
const c = extended[i - 14];
const d = extended[i - 16];
extended[i] = rotateLeft(a ^ b ^ c ^ d, 1);
}
for (let i = 0; i < 80; i++) {
const fn = functions[Math.floor(i / 20)];
const temp = rotateLeft(current[0], 5) + fn(current) + current[4] + extended[i] + constants[Math.floor(i / 20)];
current[4] = current[3];
current[3] = current[2];
current[2] = rotateLeft(current[1], 30);
current[1] = current[0];
current[0] = temp;
}
for (let i = 0; i < 5; i++) {
current[i] = (current[i] + initial[i]) >>> 0;
}
}
const hashBuffer = new ArrayBuffer(20);
const hashView = new DataView(hashBuffer);
for (let i = 0; i < 5; i++) {
hashView.setUint32(i * 4, current[i], false);
}
const hashArray = new Uint8Array(hashBuffer);
return Array.from(hashArray)
.map byte => byte.toString(16).padStart(2, '0')
.join('');
}
Usage Example
const plaintext = 'Hello World';
const hashResult = computeSha1(plaintext);
console.log('Input:', plaintext);
console.log('SHA1 Hash:', hashResult);
const password = 'userPassword123';
const hashedPassword = computeSha1(password);
console.log('Password Hash:', hashedPassword);
This implementation processes the input string through UTF-8 encoding, applies SHA1 padding according to the FIPS 180-1 specification, and produces a 40-character hexadecimal hash value. The algorithm processes data in 512-bit chunks using 80 rounds of operations with modular addition and bitwise manipulations.
Note that SHA1 is considered cryptographically weak by modern standards due to collision vulnerabilities. For new security-critical applications, SHA-256 or SHA-3 from the SHA-2 family are recommended instead.