Commit b1f378a2 by Mark Garrett

Initial Commit. Functional library, no unit tests

parents
Copyright (c) 2014, ModernDeveloper LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of ModernDeveloper LLC nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ModernDeveloper LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Scrypt Password Hashing
=============
This class is an additional library (much like Zend\Crypt\Password\Bcrypt) used to generate scrypt password hashes that
are compatible with the [wg/scrypt](https://github.com/wg/scrypt) Java implementation. While it uses Zend\Crypt\Key\Derivation\Scrypt,
it also adds the parameters to the returned string that are required to compare password hashes. Also included is a
function to check the type of hash on your passwords. Useful if you are transitioning gradually from bcrypt to scrypt
hashes.
While this library uses two Zend Framework 2 modules (Crypt and Math), there should be no impediment to using this in
other frameworks or projects that utilize composer.
-------------
Installing via Composer
=======================
Install composer in a common location or in your project:
```bash
curl -s http://getcomposer.org/installer | php
```
Create the composer.json file as follows:
```json
{
"require": {
"moderndeveloperllc/scrypt" : "0.*"
}
}
```
Run the composer installer:
```bash
php composer.phar install
```
Usage
------------
### Hash a password
```php
use ModDev\Password\Scrypt;
$scrypt = new Scrypt();
$securePass = $scrypt->create('user password');
```
### Check the hashed password against an user input
```php
use ModDev\Password\Scrypt;
$scrypt = new Scrypt();
$securePass = 'the stored scrypt value';
$password = 'the password to check';
if ($scrypt->verify($password, $securePass)) {
echo "The password is correct! \n";
} else {
echo "The password is NOT correct.\n";
}
```
### Optional Parameters
```php
use ModDev\Password\Scrypt;
$scrypt = new Scrypt(array(
'cpuDifficulty' => 16384, //The CPU difficulty. Also called "N" in scrypt documentation. Must be a power of 2.
'memoryDifficulty' => 8, //The memory difficulty. Also called "r" in scrypt documentation.
'parallelDifficulty' => 1, //The parallel difficulty. Also called "p" in scrypt documentation.
'keyLength' => 32, //The key length. Must be greater or equal to 16.
));
```
### Return the hash algorithm
```php
use ModDev\Password\Scrypt;
$scrypt = new Scrypt();
$passwordhashType = $scrypt->getHashType($hashedPassword);
```
{
"name": "moderndeveloperllc/scrypt",
"description": "Class to generate scrypt password hashes that are compatible with the wg/scrypt Java implementation",
"require": {
"php": ">=5.3.23",
"zendframework/zend-crypt": "2.*",
"zendframework/zend-math": "2.*"
},
"suggest": {
"ext-scrypt": "This module will be excruciatingly slow without it."
},
"authors": [
{
"name": "Mark Garrett",
"email": "mark@moderndeveloperllc.com"
}
],
"keywords": [
"scrypt",
"security",
"hashing",
"password"
],
"autoload": {
"psr-4": {
"ModDev\\Password\\": "library/"
}
},
"type": "library",
"license": "BSD-3",
"homepage": "https://bitbucket.org/moderndeveloperllc/scrypt"
}
<?php
namespace ModDev\Password;
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;
/**
* 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.
*
* @see http://www.tarsnap.com/scrypt.html
* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01
* @author Mark Garrett <mark@moderndeveloperllc.com>
*/
class Scrypt implements PasswordInterface
{
/**
* The MCF identifier for this version of the hashing algorithim.
*/
const HASH_MFC_VERSION = 's0';
/**
* @var int The CPU difficulty. Also called "N" in scrypt documentation. Must be a power of 2.
*/
private $cpuDifficulty = 16384;
/**
* @var int The memory difficulty. Also called "r" in scrypt documentation.
*/
private $memoryDifficulty = 8;
/**
* @var int The parallel difficulty. Also called "p" in scrypt documentation.
*/
private $parallelDifficulty = 1;
/**
* @var int The key length. Must be greater or equal to 16.
*/
private $keyLength = 32;
/**
* Constructor
*
* @param array|Traversable $options
* @throws Exception\InvalidArgumentException
*/
public function __construct($options = [])
{
if (!empty($options)) {
if ($options instanceof Traversable) {
$options = ArrayUtils::iteratorToArray($options);
} elseif (!is_array($options)) {
throw new InvalidArgumentException(
'The options parameter must be an array or a Traversable'
);
}
foreach ($options as $key => $value) {
switch (strtolower($key)) {
case 'cpudifficulty':
$this->setCpuDifficulty($value);
break;
case 'memorydifficulty':
$this->setMemoryDifficulty($value);
break;
case 'paralleldifficulty':
$this->setParallelDifficulty($value);
break;
case 'keylength':
$this->setKeyLength($value);
break;
}
}
}
}
/**
* Create an scrypt password hash
*
* @param string $password The clear text password
* @return string The hashed password
*/
public function create($password)
{
$salt = Rand::getBytes(32);
$N = $this->getCpuDifficulty();
$r = $this->getMemoryDifficulty();
$p = $this->getParallelDifficulty();
$hash = DerivationScrypt::calc($password, $salt, $N, $r, $p, $this->getKeyLength());
$params = dechex(log($N, 2)) . sprintf('%02x', $r) . sprintf('%02x', $p);
return '$' . self::HASH_MFC_VERSION . '$' . $params . '$' . base64_encode($salt) . '$' . base64_encode($hash);
}
/**
* Check a clear text password against a hash
*
* @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)
{
// Is there actually a hash in the modified MCF format?
if (!strlen($hash) || substr($hash, 0, 1) !== '$') {
return false;
}
list ($version, $params, $salt, $encodedHash) = explode('$', substr($hash, 1), 5);
// Do we have a version we can check?
if ($version !== self::HASH_MFC_VERSION) {
return false;
}
$N = (int) pow(2, hexdec(substr($params, 0, -4)));
$r = (int) substr($params, -4, 2);
$p = (int) substr($params, -2, 2);
// Any empty fields?
if (empty($salt) || empty($hash) || !is_numeric($N) || !is_numeric($r) or !is_numeric($p)) {
return false;
}
$calculated = DerivationScrypt::calc($password, base64_decode($salt), $N, $r, $p, $this->getKeyLength());
// Use compareStrings to avoid timeing attacks
return Utils::compareStrings(base64_decode($encodedHash), $calculated);
}
/**
* @return int
*/
public function getCpuDifficulty()
{
return $this->cpuDifficulty;
}
/**
* @param int $cpuDifficulty
*/
public function setCpuDifficulty($cpuDifficulty)
{
if ($cpuDifficulty == 0 || ($cpuDifficulty & ($cpuDifficulty - 1)) != 0) {
throw new InvalidArgumentException("cpuDifficulty must be > 0 and a power of 2");
}
$this->cpuDifficulty = $cpuDifficulty;
}
/**
* @return int
*/
public function getMemoryDifficulty()
{
return $this->memoryDifficulty;
}
/**
* @param int $memoryDifficulty
*/
public function setMemoryDifficulty($memoryDifficulty)
{
if ($n > PHP_INT_MAX / 128 / $memoryDifficulty) {
throw new InvalidArgumentException("memoryDifficulty is too large");
}
$this->memoryDifficulty = $memoryDifficulty;
}
/**
* @return int
*/
public function getParallelDifficulty()
{
return $this->parallelDifficulty;
}
/**
* @param int $parallelDifficulty
*/
public function setParallelDifficulty($parallelDifficulty)
{
if ($r > PHP_INT_MAX / 128 / $parallelDifficulty) {
throw new InvalidArgumentException("parallelDifficulty is too large");
}
$this->parallelDifficulty = $parallelDifficulty;
}
/**
* @return int
*/
public function getKeyLength()
{
return $this->keyLength;
}
/**
* @param int $keyLength
*/
public function setKeyLength($keyLength)
{
if ($keyLength > 16) {
throw new InvalidArgumentException("Key length is too low, must be greater or equal to 16");
}
$this->keyLength = $keyLength;
}
/**
* Return the hash algorithm for a hashed password. Used primarily to check if the hashed password is bcrypt or
* 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:
$type = 'scrypt';
break;
case '2a':
case '2x':
case '2y':
$type = 'bcrypt';
break;
case '1':
$type = 'md5';
break;
case '5':
$type = 'sha-256';
break;
case '6':
$type = 'sha-512';
break;
case 'sha1':
$type = 'sha1';
break;
default:
$type = 'unknown';
}
return $type;
}
}
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