hkdf.js 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. /**
  2. * HKDF (RFC 5869): extract + expand in one step.
  3. * See https://soatok.blog/2021/11/17/understanding-hkdf/.
  4. * @module
  5. */
  6. import { hmac } from "./hmac.js";
  7. import { ahash, anumber, clean, toBytes } from "./utils.js";
  8. /**
  9. * HKDF-extract from spec. Less important part. `HKDF-Extract(IKM, salt) -> PRK`
  10. * Arguments position differs from spec (IKM is first one, since it is not optional)
  11. * @param hash - hash function that would be used (e.g. sha256)
  12. * @param ikm - input keying material, the initial key
  13. * @param salt - optional salt value (a non-secret random value)
  14. */
  15. export function extract(hash, ikm, salt) {
  16. ahash(hash);
  17. // NOTE: some libraries treat zero-length array as 'not provided';
  18. // we don't, since we have undefined as 'not provided'
  19. // https://github.com/RustCrypto/KDFs/issues/15
  20. if (salt === undefined)
  21. salt = new Uint8Array(hash.outputLen);
  22. return hmac(hash, toBytes(salt), toBytes(ikm));
  23. }
  24. const HKDF_COUNTER = /* @__PURE__ */ Uint8Array.from([0]);
  25. const EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
  26. /**
  27. * HKDF-expand from the spec. The most important part. `HKDF-Expand(PRK, info, L) -> OKM`
  28. * @param hash - hash function that would be used (e.g. sha256)
  29. * @param prk - a pseudorandom key of at least HashLen octets (usually, the output from the extract step)
  30. * @param info - optional context and application specific information (can be a zero-length string)
  31. * @param length - length of output keying material in bytes
  32. */
  33. export function expand(hash, prk, info, length = 32) {
  34. ahash(hash);
  35. anumber(length);
  36. const olen = hash.outputLen;
  37. if (length > 255 * olen)
  38. throw new Error('Length should be <= 255*HashLen');
  39. const blocks = Math.ceil(length / olen);
  40. if (info === undefined)
  41. info = EMPTY_BUFFER;
  42. // first L(ength) octets of T
  43. const okm = new Uint8Array(blocks * olen);
  44. // Re-use HMAC instance between blocks
  45. const HMAC = hmac.create(hash, prk);
  46. const HMACTmp = HMAC._cloneInto();
  47. const T = new Uint8Array(HMAC.outputLen);
  48. for (let counter = 0; counter < blocks; counter++) {
  49. HKDF_COUNTER[0] = counter + 1;
  50. // T(0) = empty string (zero length)
  51. // T(N) = HMAC-Hash(PRK, T(N-1) | info | N)
  52. HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T)
  53. .update(info)
  54. .update(HKDF_COUNTER)
  55. .digestInto(T);
  56. okm.set(T, olen * counter);
  57. HMAC._cloneInto(HMACTmp);
  58. }
  59. HMAC.destroy();
  60. HMACTmp.destroy();
  61. clean(T, HKDF_COUNTER);
  62. return okm.slice(0, length);
  63. }
  64. /**
  65. * HKDF (RFC 5869): derive keys from an initial input.
  66. * Combines hkdf_extract + hkdf_expand in one step
  67. * @param hash - hash function that would be used (e.g. sha256)
  68. * @param ikm - input keying material, the initial key
  69. * @param salt - optional salt value (a non-secret random value)
  70. * @param info - optional context and application specific information (can be a zero-length string)
  71. * @param length - length of output keying material in bytes
  72. * @example
  73. * import { hkdf } from '@noble/hashes/hkdf';
  74. * import { sha256 } from '@noble/hashes/sha2';
  75. * import { randomBytes } from '@noble/hashes/utils';
  76. * const inputKey = randomBytes(32);
  77. * const salt = randomBytes(32);
  78. * const info = 'application-key';
  79. * const hk1 = hkdf(sha256, inputKey, salt, info, 32);
  80. */
  81. export const hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
  82. //# sourceMappingURL=hkdf.js.map