How do I improve the performance of this PHP hash calculator?

65 views Asked by At

I want to generate scrypt hashes in PHP. We're using it to link with a system that only suppots scrypt, so unfortunately we can't just choose another algorithm that PHP comes with native support for. We also need to run this on servers we don't necessarily have root access to and may not have the ability to install PHP extensions on. By default, the iteration count is set at 260000.

I've tried my best to set up a pure PHP implementation of scrypt, using PHP's built in hash_pbkdf2 function along with the following function to calculate the scrypt salt. I call this function with array_walk($salt_calc, 'gen_salt', [262144, 8]);.

<?php
ini_set('memory_limit', '-1');

function gen_salt(&$x, $key = '', $y = [262144, 8]){
    //$start_time = microtime(true);
    $v = [];
    for ($i = 0; $i < $y[0] * 2; $i++) {
        if($i >= $y[0]) $x ^= $v[unpack('V', substr($x, -64))[1] % $y[0]]; else $v[$i] = $x;
        $b = substr($x, -64);
        $out = ['', ''];
        for ($n = 0; $n < 2*$y[1]; $n++) {
            $b32 = [];
            for ($j = 0; $j < 16; $j++)
                $b32[] = unpack("V", substr($b ^ substr($x, 64 * $n, 64), $j * 4, 4))[1];
            
            $salsa = $b32;
            for ($si = 0; $si < 8; $si += 2) {
                $a = ($salsa[ 0] + $salsa[12]) & 0xffffffff;
                $salsa[ 4] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[ 4] + $salsa[ 0]) & 0xffffffff;
                $salsa[ 8] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[ 8] + $salsa[ 4]) & 0xffffffff;
                $salsa[12] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[12] + $salsa[ 8]) & 0xffffffff;
                $salsa[ 0] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[ 5] + $salsa[ 1]) & 0xffffffff;
                $salsa[ 9] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[ 9] + $salsa[ 5]) & 0xffffffff;
                $salsa[13] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[13] + $salsa[ 9]) & 0xffffffff;
                $salsa[ 1] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[ 1] + $salsa[13]) & 0xffffffff;
                $salsa[ 5] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[10] + $salsa[ 6]) & 0xffffffff;
                $salsa[14] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[14] + $salsa[10]) & 0xffffffff;
                $salsa[ 2] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[ 2] + $salsa[14]) & 0xffffffff;
                $salsa[ 6] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[ 6] + $salsa[ 2]) & 0xffffffff;
                $salsa[10] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[15] + $salsa[11]) & 0xffffffff;
                $salsa[ 3] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[ 3] + $salsa[15]) & 0xffffffff;
                $salsa[ 7] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[ 7] + $salsa[ 3]) & 0xffffffff;
                $salsa[11] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[11] + $salsa[ 7]) & 0xffffffff;
                $salsa[15] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[ 0] + $salsa[ 3]) & 0xffffffff;
                $salsa[ 1] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[ 1] + $salsa[ 0]) & 0xffffffff;
                $salsa[ 2] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[ 2] + $salsa[ 1]) & 0xffffffff;
                $salsa[ 3] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[ 3] + $salsa[ 2]) & 0xffffffff;
                $salsa[ 0] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[ 5] + $salsa[ 4]) & 0xffffffff;
                $salsa[ 6] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[ 6] + $salsa[ 5]) & 0xffffffff;
                $salsa[ 7] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[ 7] + $salsa[ 6]) & 0xffffffff;
                $salsa[ 4] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[ 4] + $salsa[ 7]) & 0xffffffff;
                $salsa[ 5] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[10] + $salsa[ 9]) & 0xffffffff;
                $salsa[11] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[11] + $salsa[10]) & 0xffffffff;
                $salsa[ 8] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[ 8] + $salsa[11]) & 0xffffffff;
                $salsa[ 9] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[ 9] + $salsa[ 8]) & 0xffffffff;
                $salsa[10] ^= ($a << 18) | ($a >> 14);
                $a = ($salsa[15] + $salsa[14]) & 0xffffffff;
                $salsa[12] ^= ($a << 7) | ($a >> 25);
                $a = ($salsa[12] + $salsa[15]) & 0xffffffff;
                $salsa[13] ^= ($a << 9) | ($a >> 23);
                $a = ($salsa[13] + $salsa[12]) & 0xffffffff;
                $salsa[14] ^= ($a << 13) | ($a >> 19);
                $a = ($salsa[14] + $salsa[13]) & 0xffffffff;
                $salsa[15] ^= ($a << 18) | ($a >> 14);
            }
            $b = pack("V*", ...(array_map(function($in, $salsa_in){ return ($in + $salsa_in) & 0xffffffff; }, $b32, $salsa)));
            $out[$n % 2] .= $b;
        }
        $x = implode($out);
    }
    //print_r((microtime(true)-$start_time).' sec.');
}

// example use
$password = 'some_password';
$salt = openssl_random_pseudo_bytes(32);
$n = 262144;
$r = 8;
$p = 1;
$length = 32;

$salt_calc = str_split(hash_pbkdf2('sha256', $password, $salt, 1, $p * 128 * $r, true), 128 * $r);
array_walk($salt_calc, 'gen_salt', [$n, $r]); //wait 2 minutes here
echo hash_pbkdf2('sha256', $password, implode($salt_calc), 1, $length*2, false); 

This function works, in that it gives me the correct hash, and doesn't require installing extensions; the problem is it takes 2 minutes to generate a single hash and uses a lot of memory doing so. I've tried simplifying and optimizing it by hand, although without much success... So I'm looking for suggestions on how to accomplish this.

0

There are 0 answers