Skip to content

Instantly share code, notes, and snippets.

@ArrayIterator
Created December 24, 2024 21:57
Show Gist options
  • Save ArrayIterator/9f15024e4bf80d4319fb1453bb65b750 to your computer and use it in GitHub Desktop.
Save ArrayIterator/9f15024e4bf80d4319fb1453bb65b750 to your computer and use it in GitHub Desktop.
Javascript Implementation sha1 & hash_hmac<sha1>
/**
* SHA1
* Implementation of SHA1 algorithm as described in RFC 3174
* @see https://www.ietf.org/rfc/rfc3174.txt
*
* @param {string} string input to be hashed
* @param {boolean} [raw=false] If the optional binary is set to true, then the sha1 digest is instead returned in raw binary format with a length of 20, otherwise the returned value is a 40-character hexadecimal number.
* @return {string|false} Calculated the sha1 hash of a string, returning false if fail
*/
const sha1 = (string, raw = false) => {
string = typeof string === 'number' ? string.toString() : (
typeof string === 'boolean' ? (string ? '1' : '0') : string + ''
);
const strLen = string.length;
const len = strLen * 8;
const binLen = strLen >> 2;
if (binLen <= 0) {
return false;
}
const rotate_left = (n, s) => (n << s) | (n >>> (32 - s));
const safe_add_16b = (x, y) => ((x >> 16) + (y >> 16) + (((x & 0xFFFF) + (y & 0xFFFF)) >> 16) << 16) | (((x & 0xFFFF) + (y & 0xFFFF)) & 0xFFFF);
/**
* Perform the appropriate triplet combination function for the current iteration
* @param {number} t
* @param {number} b
* @param {number} c
* @param {number} d
* @return {number}
*/
const sha1_ft = (t, b, c, d) => {
if (t < 20) {
return (b & c) | ((~b) & d);
}
if (t < 40) {
return b ^ c ^ d;
}
if (t < 60) {
return (b & c) | (b & d) | (c & d);
}
return b ^ c ^ d;
}
/**
* Determine the appropriate additive constant for the current iteration
* @param {number} t
* @return {number}
*/
const sha1_kt = (t) => {
if (t < 20) {
return 1518500249;
}
if (t < 40) {
return 1859775393;
}
if (t < 60) {
return -1894007588;
}
return -899497514;
}
let i, t;
let binArray = new Array(binLen);
for (i = 0; i < len; i += 8) {
binArray[i >> 5] |= (string.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
}
/* append padding */
binArray[len >> 5] |= 0x80 << (24 - len % 32);
binArray[((len + 64 >> 9) << 4) + 15] = len;
let wordArray = Array(80),
a = 1732584193,
b = -271733879,
c = -1732584194,
d = 271733878,
e = -1009589776;
for (i = 0; i < binArray.length; i += 16) {
let oldA = a,
oldB = b,
oldC = c,
oldD = d,
oldE = e;
for (let j = 0; j < 80; j++) {
wordArray[j] = j < 16 ? binArray[i + j] : rotate_left(wordArray[j - 3] ^ wordArray[j - 8] ^ wordArray[j - 14] ^ wordArray[j - 16], 1);
t = safe_add_16b(
safe_add_16b(
rotate_left(a, 5),
sha1_ft(j, b, c, d)
),
safe_add_16b(
safe_add_16b(e, wordArray[j]),
sha1_kt(j)
)
);
e = d;
d = c;
c = rotate_left(b, 30);
b = a;
a = t;
}
a = safe_add_16b(a, oldA);
b = safe_add_16b(b, oldB);
c = safe_add_16b(c, oldC);
d = safe_add_16b(d, oldD);
e = safe_add_16b(e, oldE);
}
binArray = [a, b, c, d, e]; // reuse binArray variable
let hash = '';
// convert big-endian
if (raw) {
for (let i = 0; i < 160; i += 8) {
hash += String.fromCharCode((binArray[i >> 5] >>> (32 - 8 - i % 32)) & 0xFF);
}
} else {
const HEX = '0123456789abcdef';
for (let i = 0; i < 20; i++) {
hash += HEX.charAt((binArray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF);
hash += HEX.charAt((binArray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
}
}
return hash;
}
/**
* HMAC-SHA1
* Implementation of HMAC-SHA1 algorithm as described in RFC 2104
* @see https://www.ietf.org/rfc/rfc2104.txt
*
* @param {string} string input to be hashed
* @param {string} key secret key
* @param {boolean} [raw=false] If the optional binary is set to true, then the sha1 digest is instead returned in raw binary format with a length of 20, otherwise the returned value is a 40-character hexadecimal number.
* @return {string|false} returning hashed data, otherwise false if fail
*/
const hmac_sha1 = (string, key, raw = false) => {
key = typeof key === 'number' ? key.toString() : (
// boolean is 1 or 0
typeof key === 'boolean' ? (key ? '1' : '0') : key + ''
);
string = typeof string === 'number' ? string.toString() : (
typeof string === 'boolean' ? (string ? '1' : '0') : string + ''
);
if (key.length > 64) {
// keys longer than block-size are shortened
key = sha1(key, true);
if (key === false) {
return false;
}
}
const bytes = new Array(64);
let len = key.length;
while (len--) {
bytes[len] = key.charCodeAt(len) & 0xFF;
}
let oPadding = '',
iPadding = '';
while (bytes.length > 0) {
const byte = bytes.shift();
oPadding += String.fromCharCode(byte ^ 0x5C);
iPadding += String.fromCharCode(byte ^ 0x36);
}
const iPadRes = sha1(iPadding + string, true);
return iPadRes ? sha1(oPadding + iPadRes, raw) : false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment