|
|
@@ -0,0 +1,246 @@
|
|
|
+// From https://gist.github.com/guillermodlpa/f6d955f838e9b10d1ef95b8e259b2c58
|
|
|
+// 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
|
|
|
+// 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)
|
|
|
+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
|
|
|
+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
|
|
|
+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: number) {
|
|
|
+ return ((n - (n | 0)) * pow(2, 32)) | 0;
|
|
|
+}
|
|
|
+
|
|
|
+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)
|
|
|
+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: number, bits: number) {
|
|
|
+ return (word >>> bits) | (word << (32 - bits));
|
|
|
+}
|
|
|
+
|
|
|
+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: 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();
|
|
|
+
|
|
|
+export function sign(
|
|
|
+ inputKey: string | Uint8Array,
|
|
|
+ inputData: string | Uint8Array,
|
|
|
+) {
|
|
|
+ const key =
|
|
|
+ typeof inputKey === "string" ? encoder.encode(inputKey) : inputKey;
|
|
|
+ const data =
|
|
|
+ typeof inputData === "string" ? encoder.encode(inputData) : inputData;
|
|
|
+ return hmac(key, data);
|
|
|
+}
|
|
|
+
|
|
|
+export function hex(bin: Uint8Array) {
|
|
|
+ return bin.reduce((acc, val) => {
|
|
|
+ const hexVal = "00" + val.toString(16);
|
|
|
+ return acc + hexVal.substring(hexVal.length - 2);
|
|
|
+ }, "");
|
|
|
+}
|
|
|
+
|
|
|
+export function hash(str: string) {
|
|
|
+ return hex(sha256(encoder.encode(str)));
|
|
|
+}
|
|
|
+
|
|
|
+export function hashWithSecret(str: string, secret: string) {
|
|
|
+ return hex(sign(secret, str)).toString();
|
|
|
+}
|