| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- // 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();
- }
|