Commit f3c771a2 by Mark Garrett

Actually get it working right. Added needsRehash() function to mimic password_needs_rehash()

parent 3c69cdd3
.idea
vendor
composer.lock
<?php
namespace ModDev\Password;
use Traversable;
use Zend\Crypt\Exception\InvalidArgumentException;
use Zend\Crypt\Key\Derivation\Scrypt as DerivationScrypt;
use Zend\Crypt\Password\PasswordInterface;
use Zend\Crypt\Utils;
use Zend\Math\Rand;
use Zend\Stdlib\ArrayUtils;
/**
* Class to implement scrypt hashing in the Phalcon framework. This provides hashes that are
* in the same MCF as {@see https://github.com/wg/scrypt} - a Java implementation of scrypt.
* Class to implement scrypt hashing in PHP. This provides hashes that use the same MCF as
* {@see https://github.com/wg/scrypt} - a Java implementation of scrypt. While it utilizes a handful of Zend Framework
* classes, this is not restricted to ZF2 projects.
*
* @see http://www.tarsnap.com/scrypt.html
* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01
......@@ -19,7 +22,7 @@ class Scrypt implements PasswordInterface
{
/**
* The MCF identifier for this version of the hashing algorithim.
* The MCF identifier for this version of the hashing algorithm.
*/
const HASH_MFC_VERSION = 's0';
......@@ -47,9 +50,9 @@ class Scrypt implements PasswordInterface
* Constructor
*
* @param array|Traversable $options
* @throws Exception\InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __construct($options = [])
public function __construct($options = array())
{
if (!empty($options)) {
if ($options instanceof Traversable) {
......@@ -59,6 +62,9 @@ class Scrypt implements PasswordInterface
'The options parameter must be an array or a Traversable'
);
}
krsort($options);
foreach ($options as $key => $value) {
switch (strtolower($key)) {
case 'cpudifficulty':
......@@ -101,7 +107,6 @@ class Scrypt implements PasswordInterface
*
* @param string $password The clear text password
* @param string $hash The hashed password
*
* @return boolean If the clear text matches
*/
public function verify($password, $hash)
......@@ -118,9 +123,7 @@ class Scrypt implements PasswordInterface
return false;
}
$N = (int) pow(2, hexdec(substr($params, 0, -4)));
$r = (int) substr($params, -4, 2);
$p = (int) substr($params, -2, 2);
list($N, $r, $p) = $this->splitParams($params);
// Any empty fields?
if (empty($salt) || empty($hash) || !is_numeric($N) || !is_numeric($r) or !is_numeric($p)) {
......@@ -129,11 +132,13 @@ class Scrypt implements PasswordInterface
$calculated = DerivationScrypt::calc($password, base64_decode($salt), $N, $r, $p, $this->getKeyLength());
// Use compareStrings to avoid timeing attacks
// Use compareStrings to avoid timing attacks
return Utils::compareStrings(base64_decode($encodedHash), $calculated);
}
/**
* Return the CPU difficulty
*
* @return int
*/
public function getCpuDifficulty()
......@@ -142,6 +147,8 @@ class Scrypt implements PasswordInterface
}
/**
* Set the CPU difficulty. This also checks validity of the input.
*
* @param int $cpuDifficulty
*/
public function setCpuDifficulty($cpuDifficulty)
......@@ -150,10 +157,16 @@ class Scrypt implements PasswordInterface
throw new InvalidArgumentException("cpuDifficulty must be > 0 and a power of 2");
}
if ($cpuDifficulty > (PHP_INT_MAX / 128 / $this->getMemoryDifficulty())) {
throw new InvalidArgumentException("cpuDifficulty is too large");
}
$this->cpuDifficulty = $cpuDifficulty;
}
/**
* Return the memory difficulty
*
* @return int
*/
public function getMemoryDifficulty()
......@@ -162,18 +175,25 @@ class Scrypt implements PasswordInterface
}
/**
* Set the memory difficulty. This also checks the validity of the input and validity of the CPU difficulty based on
* the new memory difficulty number.
*
* @param int $memoryDifficulty
*/
public function setMemoryDifficulty($memoryDifficulty)
{
if ($n > PHP_INT_MAX / 128 / $memoryDifficulty) {
throw new InvalidArgumentException("memoryDifficulty is too large");
if ($memoryDifficulty > (PHP_INT_MAX / 128 / $this->getParallelDifficulty())) {
throw new InvalidArgumentException("parallelDifficulty is too large");
}
$this->memoryDifficulty = $memoryDifficulty;
//CPU Difficulty has a check that relies on memory difficulty, so we need to check that we are still fine.
$this->setCpuDifficulty($this->cpuDifficulty);
}
/**
* Return the parallel difficulty
*
* @return int
*/
public function getParallelDifficulty()
......@@ -182,15 +202,17 @@ class Scrypt implements PasswordInterface
}
/**
* Set the parallel difficulty. This also checks the validity of the memory difficulty based on the new parallel
* difficulty number.
*
* @param int $parallelDifficulty
*/
public function setParallelDifficulty($parallelDifficulty)
{
if ($r > PHP_INT_MAX / 128 / $parallelDifficulty) {
throw new InvalidArgumentException("parallelDifficulty is too large");
}
$this->parallelDifficulty = $parallelDifficulty;
//Memory Difficulty has a check that relies on parallel difficulty, so we need to check that we are still fine.
$this->setMemoryDifficulty($this->memoryDifficulty);
}
/**
......@@ -206,7 +228,7 @@ class Scrypt implements PasswordInterface
*/
public function setKeyLength($keyLength)
{
if ($keyLength > 16) {
if ($keyLength < 16) {
throw new InvalidArgumentException("Key length is too low, must be greater or equal to 16");
}
......@@ -218,16 +240,14 @@ class Scrypt implements PasswordInterface
* scrypt.
*
* @param string $hash The hashed password
*
* @return string The hash algorithm. Returns 'unknown' if hash type not found.
*/
public function getHashType($hash)
{
$type = '';
$hashParts = explode('$', $hash);
switch ($hashParts[1]) {
case self::$hashMcfVersion:
case self::HASH_MFC_VERSION:
$type = 'scrypt';
break;
......@@ -259,4 +279,46 @@ class Scrypt implements PasswordInterface
return $type;
}
/**
* Split out the algorithm parameters from a hashed password's parameter string
*
* @param string $params The parameter string
* @return array Array of parameters used to make this hash
*/
private function splitParams($params)
{
$N = (int)pow(2, hexdec(substr($params, 0, -4)));
$r = (int)hexdec(substr($params, -4, 2));
$p = (int)hexdec(substr($params, -2, 2));
return array($N, $r, $p);
}
/**
* Functionality similar to password_needs_rehash() to checks to see if current hash is using the latest parameters.
*
* @param $hash string The hash to check
* @return bool
*/
public function needsRehash($hash)
{
$hashParts = explode('$', $hash);
if ($hashParts[1] !== self::HASH_MFC_VERSION) {
return true;
}
list($N, $r, $p) = $this->splitParams($hashParts[2]);
if ($N !== $this->getCpuDifficulty()
|| $r !== $this->getMemoryDifficulty()
|| $p !== $this->getParallelDifficulty()
) {
return true;
}
return false;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment