hkdf_expand = (hash, hash_len, prk, length, info) => {
  const b_info = Buffer.from(info || '')
  const info_len = b_info.length

  const steps = Math.ceil(length / hash_len)

  if (steps > 0xFF) {
    throw new Error(`OKM length ${length} is too long for ${hash} hash`)
  }

  // use single buffer with unnecessary create/copy/move operations
  const t = Buffer.alloc(hash_len * steps + info_len + 1)

  for (let c = 1, start = 0, end = 0; c <= steps; ++c) {
    // add info
    b_info.copy(t, end)
    // add counter
    t[end + info_len] = c

    createHmac(hash, prk)
      // use view: T(C) = T(C-1) | info | C
      .update(t.slice(start, end + info_len + 1))
      .digest()
      // put back to the same buffer
      .copy(t, end)

    start = end // used for T(C-1) start
    end += hash_len // used for T(C-1) end & overall end
  }

  return t.slice(0, length)
}
HKDF expand action.

@func
@alias hkdf.expand
@param {string} hash - Hash algorithm
@param {integer} hash_len - Hash digest length
@param {Buffer|string} prk - A buffer with pseudorandom key
@param {integer} length - length of output keying material in octets
@param {Buffer|string} info - Optional context (safe to skip)
@returns {Buffer} A buffer with output keying material

@note Values are hardcoded with fallback for unknown algorithms.