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.

Revisions

  1. ArrayIterator created this gist Dec 24, 2024.
    164 changes: 164 additions & 0 deletions sha1.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,164 @@
    /**
    * 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;
    }