public function pbkdf2($algo, $password, $salt, $iterations, $length = 0)
{
if (function_exists('hash_pbkdf2')) {
$outputKey = hash_pbkdf2($algo, $password, $salt, $iterations, $length, true);
if ($outputKey === false) {
throw new InvalidParamException('Invalid parameters to hash_pbkdf2()');
}
return $outputKey;
}
// todo: is there a nice way to reduce the code repetition in hkdf() and pbkdf2()?
$test = @hash_hmac($algo, '', '', true);
if (!$test) {
throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo);
}
if (is_string($iterations) && preg_match('{^\\d{1,16}$}', $iterations)) {
$iterations = (int) $iterations;
}
if (!is_int($iterations) || $iterations < 1) {
throw new InvalidParamException('Invalid iterations');
}
if (is_string($length) && preg_match('{^\\d{1,16}$}', $length)) {
$length = (int) $length;
}
if (!is_int($length) || $length < 0) {
throw new InvalidParamException('Invalid length');
}
$hashLength = StringHelper::byteLength($test);
$blocks = $length !== 0 ? ceil($length / $hashLength) : 1;
$outputKey = '';
for ($j = 1; $j <= $blocks; $j++) {
$hmac = hash_hmac($algo, $salt . pack('N', $j), $password, true);
$xorsum = $hmac;
for ($i = 1; $i < $iterations; $i++) {
$hmac = hash_hmac($algo, $hmac, $password, true);
$xorsum ^= $hmac;
}
$outputKey .= $xorsum;
}
if ($length !== 0) {
$outputKey = StringHelper::byteSubstr($outputKey, 0, $length);
}
return $outputKey;
}