132 lines
4.8 KiB
PHP
132 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
/**
|
|
* RFC 7914 scrypt (PBKDF2-HMAC-SHA256 + ROMix / Salsa20/8).
|
|
*/
|
|
final class Scrypt
|
|
{
|
|
public static function hash(string $password, string $salt, int $n, int $r, int $p, int $dklen): string
|
|
{
|
|
if ($n < 2 || ($n & ($n - 1)) !== 0) {
|
|
throw new \InvalidArgumentException('scrypt N must be a power of 2');
|
|
}
|
|
if ($r < 1 || $p < 1 || $dklen < 1) {
|
|
throw new \InvalidArgumentException('invalid scrypt parameters');
|
|
}
|
|
|
|
$blockSize = 128 * $r;
|
|
$B = hash_pbkdf2('sha256', $password, $salt, 1, $p * $blockSize, true);
|
|
$v = str_repeat("\0", $blockSize * $n);
|
|
$out = '';
|
|
for ($i = 0; $i < $p; $i++) {
|
|
$block = substr($B, $i * $blockSize, $blockSize);
|
|
$out .= self::romix($block, $n, $r, $v);
|
|
}
|
|
|
|
return hash_pbkdf2('sha256', $password, $out, 1, $dklen, true);
|
|
}
|
|
|
|
private static function romix(string $block, int $n, int $r, string &$v): string
|
|
{
|
|
$blockSize = 128 * $r;
|
|
$x = $block;
|
|
for ($i = 0; $i < $n; $i++) {
|
|
$v = substr_replace($v, $x, $i * $blockSize, $blockSize);
|
|
$x = self::blockMix($x, $r);
|
|
}
|
|
for ($i = 0; $i < $n; $i++) {
|
|
$j = self::integerify($x, $r) % $n;
|
|
$x = self::xorBytes($x, substr($v, $j * $blockSize, $blockSize));
|
|
$x = self::blockMix($x, $r);
|
|
}
|
|
|
|
return $x;
|
|
}
|
|
|
|
private static function blockMix(string $b, int $r): string
|
|
{
|
|
$x = substr($b, (2 * $r - 1) * 64, 64);
|
|
$y = '';
|
|
$y2 = '';
|
|
for ($i = 0; $i < 2 * $r; $i++) {
|
|
$x = self::xorBytes($x, substr($b, $i * 64, 64));
|
|
$x = self::salsa208($x);
|
|
if (($i & 1) === 0) {
|
|
$y .= $x;
|
|
} else {
|
|
$y2 .= $x;
|
|
}
|
|
}
|
|
|
|
return $y.$y2;
|
|
}
|
|
|
|
private static function integerify(string $b, int $r): int
|
|
{
|
|
$off = (2 * $r - 1) * 64;
|
|
$n = unpack('V2', substr($b, $off, 8));
|
|
|
|
return (int) ($n[1] | ($n[2] << 32));
|
|
}
|
|
|
|
private static function xorBytes(string $a, string $b): string
|
|
{
|
|
return $a ^ $b;
|
|
}
|
|
|
|
private static function salsa208(string $input): string
|
|
{
|
|
$x = array_values(unpack('V16', $input));
|
|
$z = $x;
|
|
for ($i = 0; $i < 8; $i += 2) {
|
|
$z[4] ^= self::rotl(($z[0] + $z[12]) & 0xFFFFFFFF, 7);
|
|
$z[8] ^= self::rotl(($z[4] + $z[0]) & 0xFFFFFFFF, 9);
|
|
$z[12] ^= self::rotl(($z[8] + $z[4]) & 0xFFFFFFFF, 13);
|
|
$z[0] ^= self::rotl(($z[12] + $z[8]) & 0xFFFFFFFF, 18);
|
|
$z[9] ^= self::rotl(($z[5] + $z[1]) & 0xFFFFFFFF, 7);
|
|
$z[13] ^= self::rotl(($z[9] + $z[5]) & 0xFFFFFFFF, 9);
|
|
$z[1] ^= self::rotl(($z[13] + $z[9]) & 0xFFFFFFFF, 13);
|
|
$z[5] ^= self::rotl(($z[1] + $z[13]) & 0xFFFFFFFF, 18);
|
|
$z[14] ^= self::rotl(($z[10] + $z[6]) & 0xFFFFFFFF, 7);
|
|
$z[2] ^= self::rotl(($z[14] + $z[10]) & 0xFFFFFFFF, 9);
|
|
$z[6] ^= self::rotl(($z[2] + $z[14]) & 0xFFFFFFFF, 13);
|
|
$z[10] ^= self::rotl(($z[6] + $z[2]) & 0xFFFFFFFF, 18);
|
|
$z[3] ^= self::rotl(($z[15] + $z[11]) & 0xFFFFFFFF, 7);
|
|
$z[7] ^= self::rotl(($z[3] + $z[15]) & 0xFFFFFFFF, 9);
|
|
$z[11] ^= self::rotl(($z[7] + $z[3]) & 0xFFFFFFFF, 13);
|
|
$z[15] ^= self::rotl(($z[11] + $z[7]) & 0xFFFFFFFF, 18);
|
|
$z[1] ^= self::rotl(($z[0] + $z[3]) & 0xFFFFFFFF, 7);
|
|
$z[2] ^= self::rotl(($z[1] + $z[0]) & 0xFFFFFFFF, 9);
|
|
$z[3] ^= self::rotl(($z[2] + $z[1]) & 0xFFFFFFFF, 13);
|
|
$z[0] ^= self::rotl(($z[3] + $z[2]) & 0xFFFFFFFF, 18);
|
|
$z[6] ^= self::rotl(($z[5] + $z[4]) & 0xFFFFFFFF, 7);
|
|
$z[7] ^= self::rotl(($z[6] + $z[5]) & 0xFFFFFFFF, 9);
|
|
$z[4] ^= self::rotl(($z[7] + $z[6]) & 0xFFFFFFFF, 13);
|
|
$z[5] ^= self::rotl(($z[4] + $z[7]) & 0xFFFFFFFF, 18);
|
|
$z[11] ^= self::rotl(($z[10] + $z[9]) & 0xFFFFFFFF, 7);
|
|
$z[8] ^= self::rotl(($z[11] + $z[10]) & 0xFFFFFFFF, 9);
|
|
$z[9] ^= self::rotl(($z[8] + $z[11]) & 0xFFFFFFFF, 13);
|
|
$z[10] ^= self::rotl(($z[9] + $z[8]) & 0xFFFFFFFF, 18);
|
|
$z[12] ^= self::rotl(($z[15] + $z[14]) & 0xFFFFFFFF, 7);
|
|
$z[13] ^= self::rotl(($z[12] + $z[15]) & 0xFFFFFFFF, 9);
|
|
$z[14] ^= self::rotl(($z[13] + $z[12]) & 0xFFFFFFFF, 13);
|
|
$z[15] ^= self::rotl(($z[14] + $z[13]) & 0xFFFFFFFF, 18);
|
|
}
|
|
$packed = '';
|
|
for ($i = 0; $i < 16; $i++) {
|
|
$packed .= pack('V', ($z[$i] + $x[$i]) & 0xFFFFFFFF);
|
|
}
|
|
|
|
return $packed;
|
|
}
|
|
|
|
private static function rotl(int $a, int $b): int
|
|
{
|
|
$a &= 0xFFFFFFFF;
|
|
|
|
return (($a << $b) | ($a >> (32 - $b))) & 0xFFFFFFFF;
|
|
}
|
|
}
|