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 <?php
namespace ModDev\Password; namespace ModDev\Password;
use Traversable;
use Zend\Crypt\Exception\InvalidArgumentException; use Zend\Crypt\Exception\InvalidArgumentException;
use Zend\Crypt\Key\Derivation\Scrypt as DerivationScrypt; use Zend\Crypt\Key\Derivation\Scrypt as DerivationScrypt;
use Zend\Crypt\Password\PasswordInterface; use Zend\Crypt\Password\PasswordInterface;
use Zend\Crypt\Utils; use Zend\Crypt\Utils;
use Zend\Math\Rand; use Zend\Math\Rand;
use Zend\Stdlib\ArrayUtils;
/** /**
* Class to implement scrypt hashing in the Phalcon framework. This provides hashes that are * Class to implement scrypt hashing in PHP. This provides hashes that use the same MCF as
* in the same MCF as {@see https://github.com/wg/scrypt} - a Java implementation of scrypt. * {@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 http://www.tarsnap.com/scrypt.html
* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01 * @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01
...@@ -19,7 +22,7 @@ class Scrypt implements PasswordInterface ...@@ -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'; const HASH_MFC_VERSION = 's0';
...@@ -47,9 +50,9 @@ class Scrypt implements PasswordInterface ...@@ -47,9 +50,9 @@ class Scrypt implements PasswordInterface
* Constructor * Constructor
* *
* @param array|Traversable $options * @param array|Traversable $options
* @throws Exception\InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function __construct($options = []) public function __construct($options = array())
{ {
if (!empty($options)) { if (!empty($options)) {
if ($options instanceof Traversable) { if ($options instanceof Traversable) {
...@@ -59,6 +62,9 @@ class Scrypt implements PasswordInterface ...@@ -59,6 +62,9 @@ class Scrypt implements PasswordInterface
'The options parameter must be an array or a Traversable' 'The options parameter must be an array or a Traversable'
); );
} }
krsort($options);
foreach ($options as $key => $value) { foreach ($options as $key => $value) {
switch (strtolower($key)) { switch (strtolower($key)) {
case 'cpudifficulty': case 'cpudifficulty':
...@@ -101,7 +107,6 @@ class Scrypt implements PasswordInterface ...@@ -101,7 +107,6 @@ class Scrypt implements PasswordInterface
* *
* @param string $password The clear text password * @param string $password The clear text password
* @param string $hash The hashed password * @param string $hash The hashed password
*
* @return boolean If the clear text matches * @return boolean If the clear text matches
*/ */
public function verify($password, $hash) public function verify($password, $hash)
...@@ -118,9 +123,7 @@ class Scrypt implements PasswordInterface ...@@ -118,9 +123,7 @@ class Scrypt implements PasswordInterface
return false; return false;
} }
$N = (int) pow(2, hexdec(substr($params, 0, -4))); list($N, $r, $p) = $this->splitParams($params);
$r = (int) substr($params, -4, 2);
$p = (int) substr($params, -2, 2);
// Any empty fields? // Any empty fields?
if (empty($salt) || empty($hash) || !is_numeric($N) || !is_numeric($r) or !is_numeric($p)) { if (empty($salt) || empty($hash) || !is_numeric($N) || !is_numeric($r) or !is_numeric($p)) {
...@@ -129,11 +132,13 @@ class Scrypt implements PasswordInterface ...@@ -129,11 +132,13 @@ class Scrypt implements PasswordInterface
$calculated = DerivationScrypt::calc($password, base64_decode($salt), $N, $r, $p, $this->getKeyLength()); $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 Utils::compareStrings(base64_decode($encodedHash), $calculated);
} }
/** /**
* Return the CPU difficulty
*
* @return int * @return int
*/ */
public function getCpuDifficulty() public function getCpuDifficulty()
...@@ -142,6 +147,8 @@ class Scrypt implements PasswordInterface ...@@ -142,6 +147,8 @@ class Scrypt implements PasswordInterface
} }
/** /**
* Set the CPU difficulty. This also checks validity of the input.
*
* @param int $cpuDifficulty * @param int $cpuDifficulty
*/ */
public function setCpuDifficulty($cpuDifficulty) public function setCpuDifficulty($cpuDifficulty)
...@@ -150,10 +157,16 @@ class Scrypt implements PasswordInterface ...@@ -150,10 +157,16 @@ class Scrypt implements PasswordInterface
throw new InvalidArgumentException("cpuDifficulty must be > 0 and a power of 2"); 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; $this->cpuDifficulty = $cpuDifficulty;
} }
/** /**
* Return the memory difficulty
*
* @return int * @return int
*/ */
public function getMemoryDifficulty() public function getMemoryDifficulty()
...@@ -162,18 +175,25 @@ class Scrypt implements PasswordInterface ...@@ -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 * @param int $memoryDifficulty
*/ */
public function setMemoryDifficulty($memoryDifficulty) public function setMemoryDifficulty($memoryDifficulty)
{ {
if ($n > PHP_INT_MAX / 128 / $memoryDifficulty) { if ($memoryDifficulty > (PHP_INT_MAX / 128 / $this->getParallelDifficulty())) {
throw new InvalidArgumentException("memoryDifficulty is too large"); throw new InvalidArgumentException("parallelDifficulty is too large");
} }
$this->memoryDifficulty = $memoryDifficulty; $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 * @return int
*/ */
public function getParallelDifficulty() public function getParallelDifficulty()
...@@ -182,15 +202,17 @@ class Scrypt implements PasswordInterface ...@@ -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 * @param int $parallelDifficulty
*/ */
public function setParallelDifficulty($parallelDifficulty) public function setParallelDifficulty($parallelDifficulty)
{ {
if ($r > PHP_INT_MAX / 128 / $parallelDifficulty) {
throw new InvalidArgumentException("parallelDifficulty is too large");
}
$this->parallelDifficulty = $parallelDifficulty; $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 ...@@ -206,7 +228,7 @@ class Scrypt implements PasswordInterface
*/ */
public function setKeyLength($keyLength) 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"); throw new InvalidArgumentException("Key length is too low, must be greater or equal to 16");
} }
...@@ -218,16 +240,14 @@ class Scrypt implements PasswordInterface ...@@ -218,16 +240,14 @@ class Scrypt implements PasswordInterface
* scrypt. * scrypt.
* *
* @param string $hash The hashed password * @param string $hash The hashed password
*
* @return string The hash algorithm. Returns 'unknown' if hash type not found. * @return string The hash algorithm. Returns 'unknown' if hash type not found.
*/ */
public function getHashType($hash) public function getHashType($hash)
{ {
$type = '';
$hashParts = explode('$', $hash); $hashParts = explode('$', $hash);
switch ($hashParts[1]) { switch ($hashParts[1]) {
case self::$hashMcfVersion: case self::HASH_MFC_VERSION:
$type = 'scrypt'; $type = 'scrypt';
break; break;
...@@ -259,4 +279,46 @@ class Scrypt implements PasswordInterface ...@@ -259,4 +279,46 @@ class Scrypt implements PasswordInterface
return $type; 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