public static function hkdfBlake2b(string $ikm, int $length, string $info = '', string $salt = '') : string
{
// Sanity-check the desired output length.
if ($length < 0 || $length > 255 * \Sodium\CRYPTO_GENERICHASH_KEYBYTES) {
throw new InvalidDigestLength('Argument 2: Bad HKDF Digest Length');
}
// "If [salt] not provided, is set to a string of HashLen zeroes."
if (empty($salt)) {
$salt = \str_repeat("", \Sodium\CRYPTO_GENERICHASH_KEYBYTES);
}
// HKDF-Extract:
// PRK = HMAC-Hash(salt, IKM)
// The salt is the HMAC key.
$prk = self::raw_keyed_hash($ikm, $salt);
// HKDF-Expand:
// This check is useless, but it serves as a reminder to the spec.
if (self::safeStrlen($prk) < \Sodium\CRYPTO_GENERICHASH_KEYBYTES) {
throw new CannotPerformOperation('An unknown error has occurred');
}
// T(0) = ''
$t = '';
$last_block = '';
for ($block_index = 1; self::safeStrlen($t) < $length; ++$block_index) {
// T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
$last_block = self::raw_keyed_hash($last_block . $info . \chr($block_index), $prk);
// T = T(1) | T(2) | T(3) | ... | T(N)
$t .= $last_block;
}
// ORM = first L octets of T
$orm = self::safeSubstr($t, 0, $length);
if ($orm === false) {
throw new CannotPerformOperation('An unknown error has occurred');
}
return $orm;
}