Skip to content

Instantly share code, notes, and snippets.

@guillermodlpa
Forked from stevendesu/HMAC.js
Last active May 22, 2025 12:39
Show Gist options
  • Select an option

  • Save guillermodlpa/f6d955f838e9b10d1ef95b8e259b2c58 to your computer and use it in GitHub Desktop.

Select an option

Save guillermodlpa/f6d955f838e9b10d1ef95b8e259b2c58 to your computer and use it in GitHub Desktop.

Revisions

  1. guillermodlpa revised this gist Apr 23, 2024. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion HMAC.js
    Original file line number Diff line number Diff line change
    @@ -211,7 +211,6 @@ function hmac(key: Uint8Array, data: ArrayLike<number>) {
    }

    // Convert a string to a Uint8Array, SHA-256 it, and convert back to string
    // const encoder = new TextEncoder('utf-8');
    const encoder = new TextEncoder();

    export function sign(inputKey: string, inputData: string) {
  2. guillermodlpa revised this gist Apr 23, 2024. 1 changed file with 189 additions and 224 deletions.
    413 changes: 189 additions & 224 deletions HMAC.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // From https://gist.github.com/stevendesu/2d52f7b5e1f1184af3b667c0b5e054b8

    // To ensure cross-browser support even without a proper SubtleCrypto
    // impelmentation (or without access to the impelmentation, as is the case with
    // Chrome loaded over HTTP instead of HTTPS), this library can create SHA-256
    @@ -7,265 +9,228 @@

    // By giving internal functions names that we can mangle, future calls to
    // them are reduced to a single byte (minor space savings in minified file)
    var uint8Array = Uint8Array;
    var uint32Array = Uint32Array;
    var pow = Math.pow;
    const uint8Array = Uint8Array;
    const uint32Array = Uint32Array;
    const pow = Math.pow;

    // Will be initialized below
    // Using a Uint32Array instead of a simple array makes the minified code
    // a bit bigger (we lose our `unshift()` hack), but comes with huge
    // performance gains
    var DEFAULT_STATE = new uint32Array(8);
    var ROUND_CONSTANTS = [];
    const DEFAULT_STATE = new uint32Array(8);
    const ROUND_CONSTANTS: number[] = [];

    // Reusable object for expanded message
    // Using a Uint32Array instead of a simple array makes the minified code
    // 7 bytes larger, but comes with huge performance gains
    var M = new uint32Array(64);
    const M = new uint32Array(64);

    // After minification the code to compute the default state and round
    // constants is smaller than the output. More importantly, this serves as a
    // good educational aide for anyone wondering where the magic numbers come
    // from. No magic numbers FTW!
    function getFractionalBits(n)
    {
    return ((n - (n | 0)) * pow(2, 32)) | 0;
    function getFractionalBits(n: number) {
    return ((n - (n | 0)) * pow(2, 32)) | 0;
    }

    var n = 2, nPrime = 0;
    while (nPrime < 64)
    {
    // isPrime() was in-lined from its original function form to save
    // a few bytes
    var isPrime = true;
    // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes
    // var sqrtN = pow(n, 1 / 2);
    // So technically to determine if a number is prime you only need to
    // check numbers up to the square root. However this function only runs
    // once and we're only computing the first 64 primes (up to 311), so on
    // any modern CPU this whole function runs in a couple milliseconds.
    // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no
    // scaling performance cost
    for (var factor = 2; factor <= n / 2; factor++)
    {
    if (n % factor === 0)
    {
    isPrime = false;
    }
    }
    if (isPrime)
    {
    if (nPrime < 8)
    {
    DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2));
    }
    ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3));

    nPrime++;
    }

    n++;
    let n = 2;
    let nPrime = 0;
    while (nPrime < 64) {
    // isPrime() was in-lined from its original function form to save
    // a few bytes
    let isPrime = true;
    // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes
    // var sqrtN = pow(n, 1 / 2);
    // So technically to determine if a number is prime you only need to
    // check numbers up to the square root. However this function only runs
    // once and we're only computing the first 64 primes (up to 311), so on
    // any modern CPU this whole function runs in a couple milliseconds.
    // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no
    // scaling performance cost
    for (let factor = 2; factor <= n / 2; factor++) {
    if (n % factor === 0) {
    isPrime = false;
    }
    }
    if (isPrime) {
    if (nPrime < 8) {
    DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2));
    }
    ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3));

    nPrime++;
    }

    n++;
    }

    // For cross-platform support we need to ensure that all 32-bit words are
    // in the same endianness. A UTF-8 TextEncoder will return BigEndian data,
    // so upon reading or writing to our ArrayBuffer we'll only swap the bytes
    // if our system is LittleEndian (which is about 99% of CPUs)
    var LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0];

    function convertEndian(word)
    {
    if (LittleEndian)
    {
    return (
    // byte 1 -> byte 4
    (word >>> 24) |
    // byte 2 -> byte 3
    (((word >>> 16) & 0xff) << 8) |
    // byte 3 -> byte 2
    ((word & 0xff00) << 8) |
    // byte 4 -> byte 1
    (word << 24)
    );
    }
    else
    {
    return word;
    }
    const LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0];

    function convertEndian(word: number) {
    if (LittleEndian) {
    return (
    // byte 1 -> byte 4
    (word >>> 24) |
    // byte 2 -> byte 3
    (((word >>> 16) & 0xff) << 8) |
    // byte 3 -> byte 2
    ((word & 0xff00) << 8) |
    // byte 4 -> byte 1
    (word << 24)
    );
    } else {
    return word;
    }
    }

    function rightRotate(word, bits)
    {
    return (word >>> bits) | (word << (32 - bits));
    function rightRotate(word: number, bits: number) {
    return (word >>> bits) | (word << (32 - bits));
    }

    function sha256(data)
    {
    // Copy default state
    var STATE = DEFAULT_STATE.slice();

    // Caching this reduces occurrences of ".length" in minified JavaScript
    // 3 more byte savings! :D
    var legth = data.length;

    // Pad data
    var bitLength = legth * 8;
    var newBitLength = (512 - ((bitLength + 64) % 512) - 1) + bitLength + 65;

    // "bytes" and "words" are stored BigEndian
    var bytes = new uint8Array(newBitLength / 8);
    var words = new uint32Array(bytes.buffer);

    bytes.set(data, 0);
    // Append a 1
    bytes[legth] = 0b10000000;
    // Store length in BigEndian
    words[words.length - 1] = convertEndian(bitLength);

    // Loop iterator (avoid two instances of "var") -- saves 2 bytes
    var round;

    // Process blocks (512 bits / 64 bytes / 16 words at a time)
    for (var block = 0; block < newBitLength / 32; block += 16)
    {
    var workingState = STATE.slice();

    // Rounds
    for (round = 0; round < 64; round++)
    {
    var MRound;
    // Expand message
    if (round < 16)
    {
    // Convert to platform Endianness for later math
    MRound = convertEndian(words[block + round]);
    }
    else
    {
    var gamma0x = M[round - 15];
    var gamma1x = M[round - 2];
    MRound =
    M[round - 7] + M[round - 16] + (
    rightRotate(gamma0x, 7) ^
    rightRotate(gamma0x, 18) ^
    (gamma0x >>> 3)
    ) + (
    rightRotate(gamma1x, 17) ^
    rightRotate(gamma1x, 19) ^
    (gamma1x >>> 10)
    )
    ;
    }

    // M array matches platform endianness
    M[round] = MRound |= 0;

    // Computation
    var t1 =
    (
    rightRotate(workingState[4], 6) ^
    rightRotate(workingState[4], 11) ^
    rightRotate(workingState[4], 25)
    ) +
    (
    (workingState[4] & workingState[5]) ^
    (~workingState[4] & workingState[6])
    ) + workingState[7] + MRound + ROUND_CONSTANTS[round]
    ;
    var t2 =
    (
    rightRotate(workingState[0], 2) ^
    rightRotate(workingState[0], 13) ^
    rightRotate(workingState[0], 22)
    ) +
    (
    (workingState[0] & workingState[1]) ^
    (workingState[2] & (workingState[0] ^
    workingState[1]))
    )
    ;

    for (var i = 7; i > 0; i--)
    {
    workingState[i] = workingState[i - 1];
    }
    workingState[0] = (t1 + t2) | 0;
    workingState[4] = (workingState[4] + t1) | 0;
    }

    // Update state
    for (round = 0; round < 8; round++)
    {
    STATE[round] = (STATE[round] + workingState[round]) | 0;
    }
    }

    // Finally the state needs to be converted to BigEndian for output
    // And we want to return a Uint8Array, not a Uint32Array
    return new uint8Array(new uint32Array(
    STATE.map(function(val) { return convertEndian(val); })
    ).buffer);
    function sha256(data: Uint8Array) {
    // Copy default state
    const STATE = DEFAULT_STATE.slice();

    // Caching this reduces occurrences of ".length" in minified JavaScript
    // 3 more byte savings! :D
    const legth = data.length;

    // Pad data
    const bitLength = legth * 8;
    const newBitLength = 512 - ((bitLength + 64) % 512) - 1 + bitLength + 65;

    // "bytes" and "words" are stored BigEndian
    const bytes = new uint8Array(newBitLength / 8);
    const words = new uint32Array(bytes.buffer);

    bytes.set(data, 0);
    // Append a 1
    bytes[legth] = 0b10000000;
    // Store length in BigEndian
    words[words.length - 1] = convertEndian(bitLength);

    // Loop iterator (avoid two instances of "var") -- saves 2 bytes
    let round;

    // Process blocks (512 bits / 64 bytes / 16 words at a time)
    for (let block = 0; block < newBitLength / 32; block += 16) {
    const workingState = STATE.slice();

    // Rounds
    for (round = 0; round < 64; round++) {
    let MRound;
    // Expand message
    if (round < 16) {
    // Convert to platform Endianness for later math
    MRound = convertEndian(words[block + round]);
    } else {
    const gamma0x = M[round - 15];
    const gamma1x = M[round - 2];
    MRound =
    M[round - 7] +
    M[round - 16] +
    (rightRotate(gamma0x, 7) ^ rightRotate(gamma0x, 18) ^ (gamma0x >>> 3)) +
    (rightRotate(gamma1x, 17) ^ rightRotate(gamma1x, 19) ^ (gamma1x >>> 10));
    }

    // M array matches platform endianness
    M[round] = MRound |= 0;

    // Computation
    const t1 =
    (rightRotate(workingState[4], 6) ^
    rightRotate(workingState[4], 11) ^
    rightRotate(workingState[4], 25)) +
    ((workingState[4] & workingState[5]) ^ (~workingState[4] & workingState[6])) +
    workingState[7] +
    MRound +
    ROUND_CONSTANTS[round];
    const t2 =
    (rightRotate(workingState[0], 2) ^
    rightRotate(workingState[0], 13) ^
    rightRotate(workingState[0], 22)) +
    ((workingState[0] & workingState[1]) ^
    (workingState[2] & (workingState[0] ^ workingState[1])));
    for (let i = 7; i > 0; i--) {
    workingState[i] = workingState[i - 1];
    }
    workingState[0] = (t1 + t2) | 0;
    workingState[4] = (workingState[4] + t1) | 0;
    }

    // Update state
    for (round = 0; round < 8; round++) {
    STATE[round] = (STATE[round] + workingState[round]) | 0;
    }
    }

    // Finally the state needs to be converted to BigEndian for output
    // And we want to return a Uint8Array, not a Uint32Array
    return new uint8Array(
    new uint32Array(
    STATE.map(function (val) {
    return convertEndian(val);
    }),
    ).buffer,
    );
    }

    function hmac(key, data)
    {
    if (key.length > 64)
    key = sha256(key);

    if (key.length < 64)
    {
    const tmp = new Uint8Array(64);
    tmp.set(key, 0);
    key = tmp;
    }

    // Generate inner and outer keys
    var innerKey = new Uint8Array(64);
    var outerKey = new Uint8Array(64);
    for (var i = 0; i < 64; i++)
    {
    innerKey[i] = 0x36 ^ key[i];
    outerKey[i] = 0x5c ^ key[i];
    }

    // Append the innerKey
    var msg = new Uint8Array(data.length + 64);
    msg.set(innerKey, 0);
    msg.set(data, 64);

    // Has the previous message and append the outerKey
    var result = new Uint8Array(64 + 32);
    result.set(outerKey, 0);
    result.set(sha256(msg), 64);

    // Hash the previous message
    return sha256(result);
    function hmac(key: Uint8Array, data: ArrayLike<number>) {
    if (key.length > 64) key = sha256(key);

    if (key.length < 64) {
    const tmp = new Uint8Array(64);
    tmp.set(key, 0);
    key = tmp;
    }

    // Generate inner and outer keys
    const innerKey = new Uint8Array(64);
    const outerKey = new Uint8Array(64);
    for (let i = 0; i < 64; i++) {
    innerKey[i] = 0x36 ^ key[i];
    outerKey[i] = 0x5c ^ key[i];
    }

    // Append the innerKey
    const msg = new Uint8Array(data.length + 64);
    msg.set(innerKey, 0);
    msg.set(data, 64);

    // Has the previous message and append the outerKey
    const result = new Uint8Array(64 + 32);
    result.set(outerKey, 0);
    result.set(sha256(msg), 64);

    // Hash the previous message
    return sha256(result);
    }

    // Convert a string to a Uint8Array, SHA-256 it, and convert back to string
    const encoder = new TextEncoder("utf-8");
    // const encoder = new TextEncoder('utf-8');
    const encoder = new TextEncoder();

    function sign(inputKey, inputData)
    {
    const key = typeof inputKey === "string" ? encoder.encode(inputKey) : inputKey;
    const data = typeof inputData === "string" ? encoder.encode(inputData) : inputData;
    return hmac(key, data);
    export function sign(inputKey: string, inputData: string) {
    const key = typeof inputKey === 'string' ? encoder.encode(inputKey) : inputKey;
    const data = typeof inputData === 'string' ? encoder.encode(inputData) : inputData;
    return hmac(key, data);
    }

    function hash(str)
    {
    return hex(sha256(encoder.encode(str)));
    export function hex(bin: Uint8Array) {
    return bin.reduce((acc, val) => {
    const hexVal = '00' + val.toString(16);
    return acc + hexVal.substring(hexVal.length - 2);
    }, '');
    }

    function hex(bin)
    {
    return bin.reduce((acc, val) =>
    acc + ("00" + val.toString(16)).substr(-2)
    , "");
    export function hash(str: string) {
    return hex(sha256(encoder.encode(str)));
    }

    module.exports = {
    sign: sign,
    hash: hash,
    hex: hex
    };
    export function hashWithSecret(str: string, secret: string) {
    return hex(sign(secret, str)).toString();
    }
  3. @stevendesu stevendesu created this gist Apr 30, 2020.
    271 changes: 271 additions & 0 deletions HMAC.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,271 @@
    // To ensure cross-browser support even without a proper SubtleCrypto
    // impelmentation (or without access to the impelmentation, as is the case with
    // Chrome loaded over HTTP instead of HTTPS), this library can create SHA-256
    // HMAC signatures using nothing but raw JavaScript

    /* eslint-disable no-magic-numbers, id-length, no-param-reassign, new-cap */

    // By giving internal functions names that we can mangle, future calls to
    // them are reduced to a single byte (minor space savings in minified file)
    var uint8Array = Uint8Array;
    var uint32Array = Uint32Array;
    var pow = Math.pow;

    // Will be initialized below
    // Using a Uint32Array instead of a simple array makes the minified code
    // a bit bigger (we lose our `unshift()` hack), but comes with huge
    // performance gains
    var DEFAULT_STATE = new uint32Array(8);
    var ROUND_CONSTANTS = [];

    // Reusable object for expanded message
    // Using a Uint32Array instead of a simple array makes the minified code
    // 7 bytes larger, but comes with huge performance gains
    var M = new uint32Array(64);

    // After minification the code to compute the default state and round
    // constants is smaller than the output. More importantly, this serves as a
    // good educational aide for anyone wondering where the magic numbers come
    // from. No magic numbers FTW!
    function getFractionalBits(n)
    {
    return ((n - (n | 0)) * pow(2, 32)) | 0;
    }

    var n = 2, nPrime = 0;
    while (nPrime < 64)
    {
    // isPrime() was in-lined from its original function form to save
    // a few bytes
    var isPrime = true;
    // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes
    // var sqrtN = pow(n, 1 / 2);
    // So technically to determine if a number is prime you only need to
    // check numbers up to the square root. However this function only runs
    // once and we're only computing the first 64 primes (up to 311), so on
    // any modern CPU this whole function runs in a couple milliseconds.
    // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no
    // scaling performance cost
    for (var factor = 2; factor <= n / 2; factor++)
    {
    if (n % factor === 0)
    {
    isPrime = false;
    }
    }
    if (isPrime)
    {
    if (nPrime < 8)
    {
    DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2));
    }
    ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3));

    nPrime++;
    }

    n++;
    }

    // For cross-platform support we need to ensure that all 32-bit words are
    // in the same endianness. A UTF-8 TextEncoder will return BigEndian data,
    // so upon reading or writing to our ArrayBuffer we'll only swap the bytes
    // if our system is LittleEndian (which is about 99% of CPUs)
    var LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0];

    function convertEndian(word)
    {
    if (LittleEndian)
    {
    return (
    // byte 1 -> byte 4
    (word >>> 24) |
    // byte 2 -> byte 3
    (((word >>> 16) & 0xff) << 8) |
    // byte 3 -> byte 2
    ((word & 0xff00) << 8) |
    // byte 4 -> byte 1
    (word << 24)
    );
    }
    else
    {
    return word;
    }
    }

    function rightRotate(word, bits)
    {
    return (word >>> bits) | (word << (32 - bits));
    }

    function sha256(data)
    {
    // Copy default state
    var STATE = DEFAULT_STATE.slice();

    // Caching this reduces occurrences of ".length" in minified JavaScript
    // 3 more byte savings! :D
    var legth = data.length;

    // Pad data
    var bitLength = legth * 8;
    var newBitLength = (512 - ((bitLength + 64) % 512) - 1) + bitLength + 65;

    // "bytes" and "words" are stored BigEndian
    var bytes = new uint8Array(newBitLength / 8);
    var words = new uint32Array(bytes.buffer);

    bytes.set(data, 0);
    // Append a 1
    bytes[legth] = 0b10000000;
    // Store length in BigEndian
    words[words.length - 1] = convertEndian(bitLength);

    // Loop iterator (avoid two instances of "var") -- saves 2 bytes
    var round;

    // Process blocks (512 bits / 64 bytes / 16 words at a time)
    for (var block = 0; block < newBitLength / 32; block += 16)
    {
    var workingState = STATE.slice();

    // Rounds
    for (round = 0; round < 64; round++)
    {
    var MRound;
    // Expand message
    if (round < 16)
    {
    // Convert to platform Endianness for later math
    MRound = convertEndian(words[block + round]);
    }
    else
    {
    var gamma0x = M[round - 15];
    var gamma1x = M[round - 2];
    MRound =
    M[round - 7] + M[round - 16] + (
    rightRotate(gamma0x, 7) ^
    rightRotate(gamma0x, 18) ^
    (gamma0x >>> 3)
    ) + (
    rightRotate(gamma1x, 17) ^
    rightRotate(gamma1x, 19) ^
    (gamma1x >>> 10)
    )
    ;
    }

    // M array matches platform endianness
    M[round] = MRound |= 0;

    // Computation
    var t1 =
    (
    rightRotate(workingState[4], 6) ^
    rightRotate(workingState[4], 11) ^
    rightRotate(workingState[4], 25)
    ) +
    (
    (workingState[4] & workingState[5]) ^
    (~workingState[4] & workingState[6])
    ) + workingState[7] + MRound + ROUND_CONSTANTS[round]
    ;
    var t2 =
    (
    rightRotate(workingState[0], 2) ^
    rightRotate(workingState[0], 13) ^
    rightRotate(workingState[0], 22)
    ) +
    (
    (workingState[0] & workingState[1]) ^
    (workingState[2] & (workingState[0] ^
    workingState[1]))
    )
    ;

    for (var i = 7; i > 0; i--)
    {
    workingState[i] = workingState[i - 1];
    }
    workingState[0] = (t1 + t2) | 0;
    workingState[4] = (workingState[4] + t1) | 0;
    }

    // Update state
    for (round = 0; round < 8; round++)
    {
    STATE[round] = (STATE[round] + workingState[round]) | 0;
    }
    }

    // Finally the state needs to be converted to BigEndian for output
    // And we want to return a Uint8Array, not a Uint32Array
    return new uint8Array(new uint32Array(
    STATE.map(function(val) { return convertEndian(val); })
    ).buffer);
    }

    function hmac(key, data)
    {
    if (key.length > 64)
    key = sha256(key);

    if (key.length < 64)
    {
    const tmp = new Uint8Array(64);
    tmp.set(key, 0);
    key = tmp;
    }

    // Generate inner and outer keys
    var innerKey = new Uint8Array(64);
    var outerKey = new Uint8Array(64);
    for (var i = 0; i < 64; i++)
    {
    innerKey[i] = 0x36 ^ key[i];
    outerKey[i] = 0x5c ^ key[i];
    }

    // Append the innerKey
    var msg = new Uint8Array(data.length + 64);
    msg.set(innerKey, 0);
    msg.set(data, 64);

    // Has the previous message and append the outerKey
    var result = new Uint8Array(64 + 32);
    result.set(outerKey, 0);
    result.set(sha256(msg), 64);

    // Hash the previous message
    return sha256(result);
    }

    // Convert a string to a Uint8Array, SHA-256 it, and convert back to string
    const encoder = new TextEncoder("utf-8");

    function sign(inputKey, inputData)
    {
    const key = typeof inputKey === "string" ? encoder.encode(inputKey) : inputKey;
    const data = typeof inputData === "string" ? encoder.encode(inputData) : inputData;
    return hmac(key, data);
    }

    function hash(str)
    {
    return hex(sha256(encoder.encode(str)));
    }

    function hex(bin)
    {
    return bin.reduce((acc, val) =>
    acc + ("00" + val.toString(16)).substr(-2)
    , "");
    }

    module.exports = {
    sign: sign,
    hash: hash,
    hex: hex
    };