hmac.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { hash as assertHash, bytes as assertBytes, exists as assertExists } from './_assert.js';
  2. import { Hash, CHash, Input, toBytes } from './utils.js';
  3. // HMAC (RFC 2104)
  4. export class HMAC<T extends Hash<T>> extends Hash<HMAC<T>> {
  5. oHash: T;
  6. iHash: T;
  7. blockLen: number;
  8. outputLen: number;
  9. private finished = false;
  10. private destroyed = false;
  11. constructor(hash: CHash, _key: Input) {
  12. super();
  13. assertHash(hash);
  14. const key = toBytes(_key);
  15. this.iHash = hash.create() as T;
  16. if (typeof this.iHash.update !== 'function')
  17. throw new Error('Expected instance of class which extends utils.Hash');
  18. this.blockLen = this.iHash.blockLen;
  19. this.outputLen = this.iHash.outputLen;
  20. const blockLen = this.blockLen;
  21. const pad = new Uint8Array(blockLen);
  22. // blockLen can be bigger than outputLen
  23. pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
  24. for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36;
  25. this.iHash.update(pad);
  26. // By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone
  27. this.oHash = hash.create() as T;
  28. // Undo internal XOR && apply outer XOR
  29. for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c;
  30. this.oHash.update(pad);
  31. pad.fill(0);
  32. }
  33. update(buf: Input) {
  34. assertExists(this);
  35. this.iHash.update(buf);
  36. return this;
  37. }
  38. digestInto(out: Uint8Array) {
  39. assertExists(this);
  40. assertBytes(out, this.outputLen);
  41. this.finished = true;
  42. this.iHash.digestInto(out);
  43. this.oHash.update(out);
  44. this.oHash.digestInto(out);
  45. this.destroy();
  46. }
  47. digest() {
  48. const out = new Uint8Array(this.oHash.outputLen);
  49. this.digestInto(out);
  50. return out;
  51. }
  52. _cloneInto(to?: HMAC<T>): HMAC<T> {
  53. // Create new instance without calling constructor since key already in state and we don't know it.
  54. to ||= Object.create(Object.getPrototypeOf(this), {});
  55. const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
  56. to = to as this;
  57. to.finished = finished;
  58. to.destroyed = destroyed;
  59. to.blockLen = blockLen;
  60. to.outputLen = outputLen;
  61. to.oHash = oHash._cloneInto(to.oHash);
  62. to.iHash = iHash._cloneInto(to.iHash);
  63. return to;
  64. }
  65. destroy() {
  66. this.destroyed = true;
  67. this.oHash.destroy();
  68. this.iHash.destroy();
  69. }
  70. }
  71. /**
  72. * HMAC: RFC2104 message authentication code.
  73. * @param hash - function that would be used e.g. sha256
  74. * @param key - message key
  75. * @param message - message data
  76. */
  77. export const hmac = (hash: CHash, key: Input, message: Input): Uint8Array =>
  78. new HMAC<any>(hash, key).update(message).digest();
  79. hmac.create = (hash: CHash, key: Input) => new HMAC<any>(hash, key);