Files
coruna-lab/app/Support/Scrypt.php
T
2026-08-24 06:23:07 +08:00

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;
}
}