hmac.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // From https://gist.github.com/guillermodlpa/f6d955f838e9b10d1ef95b8e259b2c58
  2. // From https://gist.github.com/stevendesu/2d52f7b5e1f1184af3b667c0b5e054b8
  3. // To ensure cross-browser support even without a proper SubtleCrypto
  4. // impelmentation (or without access to the impelmentation, as is the case with
  5. // Chrome loaded over HTTP instead of HTTPS), this library can create SHA-256
  6. // HMAC signatures using nothing but raw JavaScript
  7. /* eslint-disable no-magic-numbers, id-length, no-param-reassign, new-cap */
  8. // By giving internal functions names that we can mangle, future calls to
  9. // them are reduced to a single byte (minor space savings in minified file)
  10. const uint8Array = Uint8Array;
  11. const uint32Array = Uint32Array;
  12. const pow = Math.pow;
  13. // Will be initialized below
  14. // Using a Uint32Array instead of a simple array makes the minified code
  15. // a bit bigger (we lose our `unshift()` hack), but comes with huge
  16. // performance gains
  17. const DEFAULT_STATE = new uint32Array(8);
  18. const ROUND_CONSTANTS: number[] = [];
  19. // Reusable object for expanded message
  20. // Using a Uint32Array instead of a simple array makes the minified code
  21. // 7 bytes larger, but comes with huge performance gains
  22. const M = new uint32Array(64);
  23. // After minification the code to compute the default state and round
  24. // constants is smaller than the output. More importantly, this serves as a
  25. // good educational aide for anyone wondering where the magic numbers come
  26. // from. No magic numbers FTW!
  27. function getFractionalBits(n: number) {
  28. return ((n - (n | 0)) * pow(2, 32)) | 0;
  29. }
  30. let n = 2;
  31. let nPrime = 0;
  32. while (nPrime < 64) {
  33. // isPrime() was in-lined from its original function form to save
  34. // a few bytes
  35. let isPrime = true;
  36. // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes
  37. // var sqrtN = pow(n, 1 / 2);
  38. // So technically to determine if a number is prime you only need to
  39. // check numbers up to the square root. However this function only runs
  40. // once and we're only computing the first 64 primes (up to 311), so on
  41. // any modern CPU this whole function runs in a couple milliseconds.
  42. // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no
  43. // scaling performance cost
  44. for (let factor = 2; factor <= n / 2; factor++) {
  45. if (n % factor === 0) {
  46. isPrime = false;
  47. }
  48. }
  49. if (isPrime) {
  50. if (nPrime < 8) {
  51. DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2));
  52. }
  53. ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3));
  54. nPrime++;
  55. }
  56. n++;
  57. }
  58. // For cross-platform support we need to ensure that all 32-bit words are
  59. // in the same endianness. A UTF-8 TextEncoder will return BigEndian data,
  60. // so upon reading or writing to our ArrayBuffer we'll only swap the bytes
  61. // if our system is LittleEndian (which is about 99% of CPUs)
  62. const LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0];
  63. function convertEndian(word: number) {
  64. if (LittleEndian) {
  65. return (
  66. // byte 1 -> byte 4
  67. (word >>> 24) |
  68. // byte 2 -> byte 3
  69. (((word >>> 16) & 0xff) << 8) |
  70. // byte 3 -> byte 2
  71. ((word & 0xff00) << 8) |
  72. // byte 4 -> byte 1
  73. (word << 24)
  74. );
  75. } else {
  76. return word;
  77. }
  78. }
  79. function rightRotate(word: number, bits: number) {
  80. return (word >>> bits) | (word << (32 - bits));
  81. }
  82. function sha256(data: Uint8Array) {
  83. // Copy default state
  84. const STATE = DEFAULT_STATE.slice();
  85. // Caching this reduces occurrences of ".length" in minified JavaScript
  86. // 3 more byte savings! :D
  87. const legth = data.length;
  88. // Pad data
  89. const bitLength = legth * 8;
  90. const newBitLength = 512 - ((bitLength + 64) % 512) - 1 + bitLength + 65;
  91. // "bytes" and "words" are stored BigEndian
  92. const bytes = new uint8Array(newBitLength / 8);
  93. const words = new uint32Array(bytes.buffer);
  94. bytes.set(data, 0);
  95. // Append a 1
  96. bytes[legth] = 0b10000000;
  97. // Store length in BigEndian
  98. words[words.length - 1] = convertEndian(bitLength);
  99. // Loop iterator (avoid two instances of "var") -- saves 2 bytes
  100. let round;
  101. // Process blocks (512 bits / 64 bytes / 16 words at a time)
  102. for (let block = 0; block < newBitLength / 32; block += 16) {
  103. const workingState = STATE.slice();
  104. // Rounds
  105. for (round = 0; round < 64; round++) {
  106. let MRound;
  107. // Expand message
  108. if (round < 16) {
  109. // Convert to platform Endianness for later math
  110. MRound = convertEndian(words[block + round]);
  111. } else {
  112. const gamma0x = M[round - 15];
  113. const gamma1x = M[round - 2];
  114. MRound =
  115. M[round - 7] +
  116. M[round - 16] +
  117. (rightRotate(gamma0x, 7) ^
  118. rightRotate(gamma0x, 18) ^
  119. (gamma0x >>> 3)) +
  120. (rightRotate(gamma1x, 17) ^
  121. rightRotate(gamma1x, 19) ^
  122. (gamma1x >>> 10));
  123. }
  124. // M array matches platform endianness
  125. M[round] = MRound |= 0;
  126. // Computation
  127. const t1 =
  128. (rightRotate(workingState[4], 6) ^
  129. rightRotate(workingState[4], 11) ^
  130. rightRotate(workingState[4], 25)) +
  131. ((workingState[4] & workingState[5]) ^
  132. (~workingState[4] & workingState[6])) +
  133. workingState[7] +
  134. MRound +
  135. ROUND_CONSTANTS[round];
  136. const t2 =
  137. (rightRotate(workingState[0], 2) ^
  138. rightRotate(workingState[0], 13) ^
  139. rightRotate(workingState[0], 22)) +
  140. ((workingState[0] & workingState[1]) ^
  141. (workingState[2] & (workingState[0] ^ workingState[1])));
  142. for (let i = 7; i > 0; i--) {
  143. workingState[i] = workingState[i - 1];
  144. }
  145. workingState[0] = (t1 + t2) | 0;
  146. workingState[4] = (workingState[4] + t1) | 0;
  147. }
  148. // Update state
  149. for (round = 0; round < 8; round++) {
  150. STATE[round] = (STATE[round] + workingState[round]) | 0;
  151. }
  152. }
  153. // Finally the state needs to be converted to BigEndian for output
  154. // And we want to return a Uint8Array, not a Uint32Array
  155. return new uint8Array(
  156. new uint32Array(
  157. STATE.map(function (val) {
  158. return convertEndian(val);
  159. }),
  160. ).buffer,
  161. );
  162. }
  163. function hmac(key: Uint8Array, data: ArrayLike<number>) {
  164. if (key.length > 64) key = sha256(key);
  165. if (key.length < 64) {
  166. const tmp = new Uint8Array(64);
  167. tmp.set(key, 0);
  168. key = tmp;
  169. }
  170. // Generate inner and outer keys
  171. const innerKey = new Uint8Array(64);
  172. const outerKey = new Uint8Array(64);
  173. for (let i = 0; i < 64; i++) {
  174. innerKey[i] = 0x36 ^ key[i];
  175. outerKey[i] = 0x5c ^ key[i];
  176. }
  177. // Append the innerKey
  178. const msg = new Uint8Array(data.length + 64);
  179. msg.set(innerKey, 0);
  180. msg.set(data, 64);
  181. // Has the previous message and append the outerKey
  182. const result = new Uint8Array(64 + 32);
  183. result.set(outerKey, 0);
  184. result.set(sha256(msg), 64);
  185. // Hash the previous message
  186. return sha256(result);
  187. }
  188. // Convert a string to a Uint8Array, SHA-256 it, and convert back to string
  189. const encoder = new TextEncoder();
  190. export function sign(
  191. inputKey: string | Uint8Array,
  192. inputData: string | Uint8Array,
  193. ) {
  194. const key =
  195. typeof inputKey === "string" ? encoder.encode(inputKey) : inputKey;
  196. const data =
  197. typeof inputData === "string" ? encoder.encode(inputData) : inputData;
  198. return hmac(key, data);
  199. }
  200. export function hex(bin: Uint8Array) {
  201. return bin.reduce((acc, val) => {
  202. const hexVal = "00" + val.toString(16);
  203. return acc + hexVal.substring(hexVal.length - 2);
  204. }, "");
  205. }
  206. export function hash(str: string) {
  207. return hex(sha256(encoder.encode(str)));
  208. }
  209. export function hashWithSecret(str: string, secret: string) {
  210. return hex(sign(secret, str)).toString();
  211. }