├── .travis.yml ├── .gitignore ├── src ├── Phpass.php └── Phpass │ ├── Exception │ ├── RuntimeException.php │ ├── InvalidArgumentException.php │ └── UnexpectedValueException.php │ ├── Exception.php │ ├── Strength │ ├── Adapter.php │ └── Adapter │ │ ├── Nist.php │ │ ├── Base.php │ │ └── Wolfram.php │ ├── Hash │ ├── Adapter │ │ ├── Sha512Crypt.php │ │ ├── Md5Crypt.php │ │ ├── Sha256Crypt.php │ │ ├── ExtDes.php │ │ ├── Portable.php │ │ ├── Bcrypt.php │ │ ├── Base.php │ │ ├── Sha1Crypt.php │ │ └── Pbkdf2.php │ └── Adapter.php │ ├── Loader.php │ ├── Strength.php │ └── Hash.php ├── phpunit.xml.dist ├── LICENSE ├── composer.json ├── tests └── src │ └── Phpass │ ├── Strength │ └── Adapter │ │ ├── NistTest.php │ │ └── WolframTest.php │ ├── StrengthTest.php │ ├── HashTest.php │ └── Hash │ └── Adapter │ ├── Sha1CryptTest.php │ ├── Md5CryptTest.php │ ├── PortableTest.php │ ├── ExtDesTest.php │ ├── Sha256CryptTest.php │ ├── Sha512CryptTest.php │ ├── Pbkdf2Test.php │ └── BcryptTest.php └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | script: phpunit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .settings/ 3 | .buildpath 4 | .project 5 | 6 | # PHPUnit 7 | logs/ 8 | phpunit.xml 9 | 10 | # Composer 11 | composer.phar -------------------------------------------------------------------------------- /src/Phpass.php: -------------------------------------------------------------------------------- 1 | 10 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 | * @link https://github.com/rchouinard/phpass Project at GitHub 12 | */ 13 | 14 | use Phpass\Loader; 15 | 16 | require_once 'Phpass/Loader.php'; 17 | Loader::registerAutoloader(); 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./src 5 | 6 | 7 | 8 | ./tests/src 9 | 10 | 11 | 12 | 13 | ./src 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Phpass/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Exception; 13 | 14 | use Phpass\Exception; 15 | 16 | /** 17 | * Runtime exception 18 | * 19 | * Exception thrown if an error which can only be found on runtime occurs. 20 | * 21 | * @package PHPass\Exceptions 22 | * @category Cryptography 23 | * @author Ryan Chouinard 24 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 25 | * @link https://github.com/rchouinard/phpass Project at GitHub 26 | */ 27 | class RuntimeException extends \RuntimeException implements Exception 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/Phpass/Exception.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass; 13 | 14 | /** 15 | * Exception marker interface 16 | * 17 | * All internal library exception classes implement this interface. This allows 18 | * the exception classes to extend other exceptions and still be recognized as 19 | * instances of Phpass\Exception. 20 | * 21 | * @package PHPass\Exceptions 22 | * @category Cryptography 23 | * @author Ryan Chouinard 24 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 25 | * @link https://github.com/rchouinard/phpass Project at GitHub 26 | */ 27 | interface Exception 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/Phpass/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Exception; 13 | 14 | use Phpass\Exception; 15 | 16 | /** 17 | * Invalid argument exception 18 | * 19 | * Exception thrown thrown if an argument does not match with the expected 20 | * value. 21 | * 22 | * @package PHPass\Exceptions 23 | * @category Cryptography 24 | * @author Ryan Chouinard 25 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 26 | * @link https://github.com/rchouinard/phpass Project at GitHub 27 | */ 28 | class InvalidArgumentException extends \InvalidArgumentException implements Exception 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/Phpass/Strength/Adapter.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Strength; 13 | 14 | /** 15 | * Strength adapter interface 16 | * 17 | * @package PHPass\Strength 18 | * @category Cryptography 19 | * @author Ryan Chouinard 20 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 21 | * @link https://github.com/rchouinard/phpass Project at GitHub 22 | */ 23 | interface Adapter 24 | { 25 | 26 | /** 27 | * Return the calculated entropy. 28 | * 29 | * @param string $password 30 | * The string to check. 31 | * @return integer 32 | * Returns the calculated string entropy. 33 | */ 34 | public function check($password); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/Phpass/Exception/UnexpectedValueException.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Exception; 13 | 14 | use Phpass\Exception; 15 | 16 | /** 17 | * Unexpected value exception 18 | * 19 | * Exception thrown if a value does not match with a set of values. This 20 | * typically happens when a function calls another function and expects the 21 | * return value to be of a certian type or value, not including arithmetic or 22 | * buffer related errors. 23 | * 24 | * @package PHPass\Exceptions 25 | * @category Cryptography 26 | * @author Ryan Chouinard 27 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 28 | * @link https://github.com/rchouinard/phpass Project at GitHub 29 | */ 30 | class UnexpectedValueException extends \UnexpectedValueException implements Exception 31 | { 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2012, Ryan Chouinard 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rych/phpass", 3 | "type": "library", 4 | "description": "PHP Password Library: Easy, secure password management for PHP", 5 | "keywords": ["cryptography", "password"], 6 | "homepage": "https://github.com/rchouinard/phpass", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Ryan Chouinard", 11 | "email": "rchouinard@gmail.com", 12 | "homepage": "http://ryanchouinard.com" 13 | } 14 | ], 15 | "support": { 16 | "issues": "https://github.com/rchouinard/phpass/issues", 17 | "wiki": "https://github.com/rchouinard/phpass/wiki" 18 | }, 19 | "require": { 20 | "php": ">=5.3.7" 21 | }, 22 | "suggest": { 23 | "ext-hash": "Some modules and features may require the HASH Message Digest Framework extension in order to work.", 24 | "ext-openssl": "The OpenSSL extension is used as a secure source of random data." 25 | }, 26 | "autoload": { 27 | "psr-0" : { 28 | "Phpass" : "src/" 29 | } 30 | }, 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "2.1-dev" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /tests/src/Phpass/Strength/Adapter/NistTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Strength\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class NistTest extends TestCase 26 | { 27 | 28 | /** 29 | * @return array 30 | */ 31 | public function passwordScoreProvider() 32 | { 33 | return array ( 34 | array ('', 0), 35 | array ('M', 4), 36 | array ('My', 6), 37 | array ('MySuperS', 18), 38 | array ('MySuperSecretPasswor', 36), 39 | array ('MySuperSecretPassword', 37), 40 | array ('Super!Secret*Password', 43) 41 | ); 42 | } 43 | 44 | /** 45 | * @test 46 | * @dataProvider passwordScoreProvider 47 | */ 48 | public function checkMethodCalculatesExpectedResult($password, $expectedScore) 49 | { 50 | $adapter = new Nist; 51 | $this->assertEquals($expectedScore, $adapter->check($password)); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Sha512Crypt.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | /** 15 | * SHA512 crypt hash adapter 16 | * 17 | * @package PHPass\Hashes 18 | * @category Cryptography 19 | * @author Ryan Chouinard 20 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 21 | * @link https://github.com/rchouinard/phpass Project at GitHub 22 | * @since 2.1.0 23 | */ 24 | class Sha512Crypt extends Sha256Crypt 25 | { 26 | 27 | /** 28 | * Number of rounds used to generate new hashes. 29 | * 30 | * @var integer 31 | */ 32 | protected $_iterationCount = 60000; 33 | 34 | /** 35 | * String identifier used to generate new hash values. 36 | * 37 | * @var string 38 | */ 39 | protected $_identifier = '6'; 40 | 41 | /** 42 | * Check if a hash string is valid for the current adapter. 43 | * 44 | * @since 2.1.0 45 | * @param string $input 46 | * Hash string to verify. 47 | * @return boolean 48 | * Returns true if the input string is a valid hash value, false 49 | * otherwise. 50 | */ 51 | public function verifyHash($input) 52 | { 53 | return ($this->verifySalt(substr($input, 0, -86)) && 1 === preg_match('/^[\.\/0-9A-Za-z]{86}$/', substr($input, -86))); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tests/src/Phpass/Strength/Adapter/WolframTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass PHPass project at GitHub. 10 | */ 11 | 12 | namespace Phpass\Strength\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass PHPass project at GitHub. 24 | */ 25 | class WolframTest extends TestCase 26 | { 27 | 28 | /** 29 | * @return array 30 | */ 31 | public function passwordScoreProvider() 32 | { 33 | return array ( 34 | array ('', 0), 35 | array ('MySuperSecretPassword', 78), 36 | array ('MySup3rS3cr3tP4ssw0rd', 155), 37 | array ('Super!Secret*Password', 119), 38 | array ('password32PASSWORD23password32PASSWORD23', 236), 39 | array ('123456', 8), 40 | array ('abcdef', 0) 41 | ); 42 | } 43 | 44 | /** 45 | * @test 46 | * @dataProvider passwordScoreProvider 47 | */ 48 | public function checkMethodCalculatesExpectedResult($password, $expectedScore) 49 | { 50 | $adapter = new Wolfram; 51 | $this->assertEquals($expectedScore, $adapter->check($password)); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /tests/src/Phpass/StrengthTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | use Phpass\Strength\Adapter\Wolfram; 16 | 17 | /** 18 | * PHP Password Library 19 | * 20 | * @package PHPass\Tests 21 | * @category Cryptography 22 | * @author Ryan Chouinard 23 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 24 | * @link https://github.com/rchouinard/phpass Project at GitHub 25 | */ 26 | class StrengthTest extends TestCase 27 | { 28 | 29 | /** 30 | * @test 31 | */ 32 | public function defaultInstanceUsesNistAdapter() 33 | { 34 | $hash = new Strength; 35 | $this->assertInstanceOf( 36 | 'Phpass\\Strength\\Adapter\\Nist', // Expected 37 | $hash->getAdapter() // Actual 38 | ); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function passingAdapterViaConstructorCorrectlySetsInstance() 45 | { 46 | $hash = new Strength(new Wolfram); 47 | $this->assertInstanceOf( 48 | 'Phpass\\Strength\\Adapter\\Wolfram', // Expected 49 | $hash->getAdapter() // Actual 50 | ); 51 | } 52 | 53 | /** 54 | * @test 55 | */ 56 | public function passingOptionsViaConstructorCorrectlySetsProperties() 57 | { 58 | $hash = new Strength(array ( 59 | 'adapter' => new Wolfram, 60 | )); 61 | 62 | $this->assertInstanceOf( 63 | 'Phpass\\Strength\\Adapter\\Wolfram', // Expected 64 | $hash->getAdapter() // Actual 65 | ); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/Phpass/Loader.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass; 13 | 14 | /** 15 | * Class loader 16 | * 17 | * This class provides static methods for loading and autoloading library 18 | * classes. The most common use for this class is to simply call 19 | * \Phpass\Loader::registerAutoloader() before using any library components. 20 | * 21 | * 31 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 32 | * @link https://github.com/rchouinard/phpass Project at GitHub 33 | */ 34 | class Loader 35 | { 36 | 37 | /** 38 | * Load a library class. 39 | * 40 | * Performs checks to make sure only local library classes are loaded, and 41 | * the class file exists within the library path. 42 | * 43 | * @param string $class 44 | * The fully qualified class name to load. 45 | * @return void 46 | */ 47 | public static function load($class) 48 | { 49 | if (stripos($class, 'Phpass') === 0) { 50 | $file = str_replace('\\', '/', $class); 51 | 52 | if (file_exists(dirname(__FILE__) . '/../' . $file . '.php')) { 53 | require_once(dirname(__FILE__) . '/../' . $file . '.php'); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Register an autoloader for the library. 60 | * 61 | * @return boolean 62 | * Returns true on success, false on failure. 63 | */ 64 | public static function registerAutoloader() 65 | { 66 | return spl_autoload_register(array ('Phpass\\Loader', 'load')); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Phpass/Strength/Adapter/Nist.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Strength\Adapter; 13 | 14 | /** 15 | * Strength adapter for NIST recommendations 16 | * 17 | * @package PHPass\Strength 18 | * @category Cryptography 19 | * @author Ryan Chouinard 20 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 21 | * @link https://github.com/rchouinard/phpass Project at GitHub 22 | */ 23 | class Nist extends Base 24 | { 25 | 26 | /** 27 | * Return the calculated entropy. 28 | * 29 | * @param string $password 30 | * The string to check. 31 | * @return integer 32 | * Returns the calculated string entropy. 33 | * @see Adapter::check() 34 | */ 35 | public function check($password) 36 | { 37 | $this->_analyze($password); 38 | 39 | $this->_score = 0; 40 | 41 | // First character is 4 bits of entropy 42 | if ($this->_length > 0) { 43 | $this->_score += 4; 44 | } 45 | 46 | // The next seven characters are 2 bits of entropy 47 | if ($this->_length > 1) { 48 | $this->_score += strlen(substr($password, 1, 7)) * 2; 49 | } 50 | 51 | // Characters 9 through 20 are 1.5 bits of entropy 52 | if ($this->_length > 8) { 53 | $this->_score += strlen(substr($password, 8, 12)) * 1.5; 54 | } 55 | 56 | // Characters 21 and beyond are 1 bit of entropy 57 | if ($this->_length > 20) { 58 | $this->_score += strlen(substr($password, 20)); 59 | } 60 | 61 | // Bonus of 6 bits if upper, lower, and non-alpha characters are used 62 | if ($this->_getClassCount(self::CLASS_UPPER) > 0 && $this->_getClassCount(self::CLASS_LOWER)) { 63 | if ($this->_getClassCount(self::CLASS_NUMBER) > 0 || $this->_getClassCount(self::CLASS_SYMBOL)) { 64 | $this->_score += 6; 65 | } 66 | } 67 | 68 | return $this->_score; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Md5Crypt.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | /** 15 | * MD5 crypt hash adapter 16 | * 17 | * @package PHPass\Hashes 18 | * @category Cryptography 19 | * @author Ryan Chouinard 20 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 21 | * @link https://github.com/rchouinard/phpass Project at GitHub 22 | * @since 2.1.0 23 | */ 24 | class Md5Crypt extends Base 25 | { 26 | 27 | /** 28 | * Generate a salt string compatible with this adapter. 29 | * 30 | * @param string $input 31 | * Optional random 48-bit string to use when generating the salt. 32 | * @return string 33 | * Returns the generated salt string. 34 | */ 35 | public function genSalt($input = null) 36 | { 37 | if (!$input) { 38 | $input = $this->_getRandomBytes(6); 39 | } 40 | 41 | $identifier = '1'; 42 | $salt = $this->_encode64($input, 6); 43 | 44 | return '$' . $identifier . '$' . $salt . '$'; 45 | } 46 | 47 | /** 48 | * Check if a hash string is valid for the current adapter. 49 | * 50 | * @since 2.1.0 51 | * @param string $input 52 | * Hash string to verify. 53 | * @return boolean 54 | * Returns true if the input string is a valid hash value, false 55 | * otherwise. 56 | */ 57 | public function verifyHash($input) 58 | { 59 | return ($this->verifySalt(substr($input, 0, -22)) && 1 === preg_match('/^[\.\/0-9A-Za-z]{22}$/', substr($input, -22))); 60 | } 61 | 62 | /** 63 | * Check if a salt string is valid for the current adapter. 64 | * 65 | * @since 2.1.0 66 | * @param string $input 67 | * Salt string to verify. 68 | * @return boolean 69 | * Returns true if the input string is a valid salt value, false 70 | * otherwise. 71 | */ 72 | public function verifySalt($input) 73 | { 74 | return (1 === preg_match('/^\$1\$[\.\/0-9A-Za-z]{0,8}\$?$/', $input)); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /tests/src/Phpass/HashTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | use Phpass\Hash\Adapter\Pbkdf2; 16 | 17 | /** 18 | * PHP Password Library 19 | * 20 | * @package PHPass\Tests 21 | * @category Cryptography 22 | * @author Ryan Chouinard 23 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 24 | * @link https://github.com/rchouinard/phpass Project at GitHub 25 | */ 26 | class HashTest extends TestCase 27 | { 28 | 29 | /** 30 | * @test 31 | */ 32 | public function defaultInstanceUsesBcryptAdapter() 33 | { 34 | $hash = new Hash; 35 | $this->assertInstanceOf( 36 | 'Phpass\\Hash\\Adapter\\Bcrypt', // Expected 37 | $hash->getAdapter() // Actual 38 | ); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function passingAdapterViaConstructorCorrectlySetsInstance() 45 | { 46 | $hash = new Hash(new Pbkdf2); 47 | $this->assertInstanceOf( 48 | 'Phpass\\Hash\\Adapter\\Pbkdf2', // Expected 49 | $hash->getAdapter() // Actual 50 | ); 51 | } 52 | 53 | /** 54 | * @test 55 | */ 56 | public function passingOptionsViaConstructorCorrectlySetsProperties() 57 | { 58 | $hash = new Hash(array ( 59 | 'adapter' => new Pbkdf2, 60 | 'hmacKey' => 'My53cr3tK3y', 61 | 'hmacAlgo' => 'sha512', 62 | )); 63 | 64 | $this->assertInstanceOf( 65 | 'Phpass\\Hash\\Adapter\\Pbkdf2', // Expected 66 | $hash->getAdapter() // Actual 67 | ); 68 | 69 | $property = new \ReflectionProperty('Phpass\\Hash', '_hmacKey'); 70 | $property->setAccessible(true); 71 | $this->assertEquals( 72 | 'My53cr3tK3y', // Expected 73 | $property->getValue($hash) // Actual 74 | ); 75 | 76 | $property = new \ReflectionProperty('Phpass\\Hash', '_hmacAlgo'); 77 | $property->setAccessible(true); 78 | $this->assertEquals( 79 | 'sha512', // Expected 80 | $property->getValue($hash) // Actual 81 | ); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/Sha1CryptTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class Sha1CryptTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Sha1Crypt; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | array ("password", '$sha1$40000$jtNX3nZ2$hBNaIXkt4wBI2o5rsi8KejSjNqIq'), 49 | array ("password", '$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a'), 50 | array ("password", '$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH'), 51 | array ("test", '$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHimExLaiSFlGkAe'), 52 | ); 53 | 54 | return $vectors; 55 | } 56 | 57 | /** 58 | * @return array 59 | */ 60 | public function invalidTestVectorProvider() 61 | { 62 | $vectors = array ( 63 | array ("", '*0', '*1'), 64 | array ("", '*1', '*0'), 65 | ); 66 | 67 | return $vectors; 68 | } 69 | 70 | /** 71 | * @test 72 | * @dataProvider validTestVectorProvider 73 | */ 74 | public function validTestVectorsProduceExpectedResults($password, $hash) 75 | { 76 | $this->assertEquals($hash, $this->_adapter->crypt($password, $hash)); 77 | } 78 | 79 | /** 80 | * @test 81 | * @dataProvider invalidTestVectorProvider 82 | */ 83 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 84 | { 85 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $hash)); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash; 13 | 14 | /** 15 | * Hash adapter interface 16 | * 17 | * @package PHPass\Hashes 18 | * @category Cryptography 19 | * @author Ryan Chouinard 20 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 21 | * @link https://github.com/rchouinard/phpass Project at GitHub 22 | */ 23 | interface Adapter 24 | { 25 | 26 | /** 27 | * Return a hashed string. 28 | * 29 | * @param string $password 30 | * The string to be hashed. 31 | * @param string $salt 32 | * An optional salt string to base the hashing on. If not provided, a 33 | * suitable string is generated by the adapter. 34 | * @return string 35 | * Returns the hashed string. On failure, a standard crypt error string 36 | * is returned which is guaranteed to differ from the salt. 37 | */ 38 | public function crypt($password, $salt = null); 39 | 40 | /** 41 | * Generate a salt string compatible with this adapter. 42 | * 43 | * @param string $input 44 | * Optional random data to use when generating the salt. 45 | * @return string 46 | * Returns the generated salt string. 47 | */ 48 | public function genSalt($input = null); 49 | 50 | /** 51 | * Check if a salt or hash string is valid for the current adapter. 52 | * 53 | * @since 2.1.0 54 | * @param string $input 55 | * Salt or hash string to verify. 56 | * @return boolean 57 | * Returns true if the input string is either a valid salt or hash 58 | * string, false otherwise. 59 | */ 60 | public function verify($input); 61 | 62 | /** 63 | * Check if a hash string is valid for the current adapter. 64 | * 65 | * @since 2.1.0 66 | * @param string $input 67 | * Hash string to verify. 68 | * @return boolean 69 | * Returns true if the input string is a valid hash value, false 70 | * otherwise. 71 | */ 72 | public function verifyHash($input); 73 | 74 | /** 75 | * Check if a salt string is valid for the current adapter. 76 | * 77 | * @since 2.1.0 78 | * @param string $input 79 | * Salt string to verify. 80 | * @return boolean 81 | * Returns true if the input string is a valid salt value, false 82 | * otherwise. 83 | */ 84 | public function verifySalt($input); 85 | 86 | } 87 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/Md5CryptTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class Md5CryptTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Md5Crypt; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | // From John the Ripper 1.7.9 49 | array ("0123456789ABCDE", '$1$12345678$aIccj83HRDBo6ux1bVx7D1'), 50 | array ("12345678", '$1$12345678$f8QoJuo0DpBRfQSD0vglc1'), 51 | array ("", '$1$$qRPK7m23GJusamGpoGLby/'), 52 | array ("no salt", '$1$$AuJCr07mI7DSew03TmBIv/'), 53 | array ("", '$1$12345678$xek.CpjQUVgdf/P2N9KQf/'), 54 | array ("1234", '$1$1234$BdIMOAWFOV2AQlLsrN/Sw.'), 55 | ); 56 | 57 | return $vectors; 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function invalidTestVectorProvider() 64 | { 65 | // TODO: Find a good source of test vectors 66 | $vectors = array ( 67 | array ("invalid salt", '$1$`!@#%^&*$E6hD76/pKTS8qToBCkux30', '*0'), 68 | array ("", '*0', '*1'), 69 | array ("", '*1', '*0'), 70 | ); 71 | 72 | return $vectors; 73 | } 74 | 75 | /** 76 | * @test 77 | * @dataProvider validTestVectorProvider 78 | */ 79 | public function validTestVectorsProduceExpectedResults($password, $hash) 80 | { 81 | $this->assertEquals($hash, $this->_adapter->crypt($password, $hash)); 82 | } 83 | 84 | /** 85 | * @test 86 | * @dataProvider invalidTestVectorProvider 87 | */ 88 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 89 | { 90 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $hash)); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/PortableTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class PortableTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Portable; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | // From John the Ripper 1.7.9 49 | array ("test1", '$H$9aaaaaSXBjgypwqm.JsMssPLiS8YQ00'), 50 | array ("123456", '$H$9PE8jEklgZhgLmZl5.HYJAzfGCQtzi1'), 51 | array ("123456", '$H$9pdx7dbOW3Nnt32sikrjAxYFjX8XoK1'), 52 | array ("thisisalongertestPW", '$P$912345678LIjjb6PhecupozNBmDndU0'), 53 | array ("JohnRipper", '$P$612345678si5M0DDyPpmRCmcltU/YW/'), 54 | array ("JohnRipper", '$H$712345678WhEyvy1YWzT4647jzeOmo0'), 55 | array ("JohnRipper", '$P$B12345678L6Lpt4BxNotVIMILOa9u81'), 56 | ); 57 | 58 | return $vectors; 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function invalidTestVectorProvider() 65 | { 66 | $vectors = array ( 67 | array ("", '*0', '*1'), 68 | array ("", '*1', '*0'), 69 | ); 70 | 71 | return $vectors; 72 | } 73 | 74 | /** 75 | * @test 76 | * @dataProvider validTestVectorProvider 77 | */ 78 | public function validTestVectorsProduceExpectedResults($password, $hash) 79 | { 80 | $config = substr($hash, 0, 12); 81 | $this->assertEquals($hash, $this->_adapter->crypt($password, $config)); 82 | } 83 | 84 | /** 85 | * @test 86 | * @dataProvider invalidTestVectorProvider 87 | */ 88 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 89 | { 90 | $config = substr($hash, 0, 12); 91 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $config)); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/ExtDesTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class ExtDesTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new ExtDes; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | // From John the Ripper 1.7.9 49 | array ("U*U*U*U*", '_J9..CCCCXBrJUJV154M'), 50 | array ("U*U***U", '_J9..CCCCXUhOBTXzaiE'), 51 | array ("U*U***U*", '_J9..CCCC4gQ.mB/PffM'), 52 | array ("*U*U*U*U", '_J9..XXXXvlzQGqpPPdk'), 53 | array ("*U*U*U*U*", '_J9..XXXXsqM/YSSP..Y'), 54 | array ("*U*U*U*U*U*U*U*U", '_J9..XXXXVL7qJCnku0I'), 55 | array ("*U*U*U*U*U*U*U*U*", '_J9..XXXXAj8cFbP5scI'), 56 | array ("ab1234567", '_J9..SDizh.vll5VED9g'), 57 | array ("cr1234567", '_J9..SDizRjWQ/zePPHc'), 58 | array ("zxyDPWgydbQjgq", '_J9..SDizxmRI1GjnQuE'), 59 | array ("726 even", '_K9..SaltNrQgIYUAeoY'), 60 | array ("", '_J9..SDSD5YGyRCr4W4c'), 61 | ); 62 | 63 | return $vectors; 64 | } 65 | 66 | /** 67 | * @return array 68 | */ 69 | public function invalidTestVectorProvider() 70 | { 71 | $vectors = array ( 72 | array ("", '_K1.!crsmZxOLzfJH8iw', '*0'), 73 | array ("", '*0', '*1'), 74 | array ("", '*1', '*0'), 75 | ); 76 | 77 | return $vectors; 78 | } 79 | 80 | /** 81 | * @test 82 | * @dataProvider validTestVectorProvider 83 | */ 84 | public function validTestVectorsProduceExpectedResults($password, $hash) 85 | { 86 | $config = substr($hash, 0, 9); 87 | $this->assertEquals($hash, $this->_adapter->crypt($password, $config)); 88 | } 89 | 90 | /** 91 | * @test 92 | * @dataProvider invalidTestVectorProvider 93 | */ 94 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 95 | { 96 | $config = substr($hash, 0, 9); 97 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $config)); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/Sha256CryptTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class Sha256CryptTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Sha256Crypt; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | // http://www.akkadia.org/drepper/SHA-crypt.txt 49 | array ("Hello world!", '$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5'), 50 | array ("Hello world!", '$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA'), 51 | array ("This is just a test", '$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8mGRcvxa5'), 52 | array ("a very much longer text to encrypt. This one even stretches over morethan one line.", '$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12oP84Bnq1'), 53 | array ("we have a short salt string but not a short password", '$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/'), 54 | array ("a short string", '$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/cZKmF/wJvD'), 55 | array ("the minimum number is still observed", '$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL972bIC'), 56 | ); 57 | 58 | return $vectors; 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function invalidTestVectorProvider() 65 | { 66 | $vectors = array ( 67 | array ("", '*0', '*1'), 68 | array ("", '*1', '*0'), 69 | ); 70 | 71 | return $vectors; 72 | } 73 | 74 | /** 75 | * @test 76 | * @dataProvider validTestVectorProvider 77 | */ 78 | public function validTestVectorsProduceExpectedResults($password, $hash) 79 | { 80 | $this->assertEquals($hash, $this->_adapter->crypt($password, $hash)); 81 | } 82 | 83 | /** 84 | * @test 85 | * @dataProvider invalidTestVectorProvider 86 | */ 87 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 88 | { 89 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $hash)); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/Sha512CryptTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class Sha512CryptTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Sha512Crypt; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | // http://www.akkadia.org/drepper/SHA-crypt.txt 49 | array ("Hello world!", '$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1'), 50 | array ("Hello world!", '$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.'), 51 | array ("This is just a test", '$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0'), 52 | array ("a very much longer text to encrypt. This one even stretches over morethan one line.", '$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1'), 53 | array ("we have a short salt string but not a short password", '$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0'), 54 | array ("a short string", '$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1'), 55 | array ("the minimum number is still observed", '$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.'), 56 | ); 57 | 58 | return $vectors; 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function invalidTestVectorProvider() 65 | { 66 | $vectors = array ( 67 | array ("", '*0', '*1'), 68 | array ("", '*1', '*0'), 69 | ); 70 | 71 | return $vectors; 72 | } 73 | 74 | /** 75 | * @test 76 | * @dataProvider validTestVectorProvider 77 | */ 78 | public function validTestVectorsProduceExpectedResults($password, $hash) 79 | { 80 | $this->assertEquals($hash, $this->_adapter->crypt($password, $hash)); 81 | } 82 | 83 | /** 84 | * @test 85 | * @dataProvider invalidTestVectorProvider 86 | */ 87 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 88 | { 89 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $hash)); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP Password Library 2 | ==================== 3 | 4 | The PHP Password Library is designed to ease the tasks associated with working with passwords in PHP. It is capable of generating strong cryptographic password hashes, verifying supplied password strings against those hashes, and calculating the strength of a password string using various algorithms. 5 | 6 | This project was inspired by [Openwall's portable hashing library for PHP](http://openwall.com/phpass/) and [PassLib for Python](http://packages.python.org/passlib/). 7 | 8 | Features 9 | -------- 10 | 11 | * Create and verify secure password hashes with only a few lines of code. 12 | * Supports bcrypt and PBKDF2 out of the box. 13 | * Easily extend to support additional hashing methods. 14 | * Additional password strength component based on well-known algorithms. 15 | * Follows the [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) standard for autoloader compatibility. 16 | 17 | Installation 18 | ------------ 19 | 20 | ### PEAR 21 | 22 | Installing via PEAR is a simple matter of including the [PEAR channel](http://rchouinard.github.com/pear/) and installing the `rych/PHPass` package. 23 | 24 | ```bash 25 | pear channel-discover rchouinard.github.com/pear 26 | pear install rych/PHPass-2.1.0-alpha 27 | ``` 28 | 29 | ### Composer 30 | 31 | [Composer](http://getcomposer.org/) is an easy way to manage dependencies in your PHP projects. The PHP Password Library can be found in the default [Packagist](http://packagist.org/) repository. 32 | 33 | After installing Composer into your project, the PHP Password Library can be installed by adding the following lines to your `composer.json` file and running the Composer command line tool: 34 | 35 | ```json 36 | { 37 | "require": { 38 | "rych/phpass": "2.1.0-dev" 39 | } 40 | } 41 | ``` 42 | 43 | Usage 44 | ----- 45 | 46 | ### Hashing passwords 47 | 48 | The library provides the ability to generate strong cryptographic hashes of user passwords using a variety of methods. Each method may be customized as needed, and may also be combined with HMAC hashing when using the base class. 49 | 50 | #### Examples 51 | 52 | Use the default bcrypt adapter: 53 | 54 | ```php 55 | 15000 67 | )); 68 | $phpassHash = new \Phpass\Hash($adapter); 69 | ``` 70 | 71 | Create and verify a password hash: 72 | 73 | ```php 74 | hashPassword($password); 77 | if ($phpassHash->checkPassword($password, $passwordHash)) { 78 | // Password matches... 79 | } else { 80 | // Password doesn't match... 81 | } 82 | ``` 83 | 84 | ### Calculating password strength 85 | 86 | There are many different ways to calculate the relative strength of a given password, and this library supports a few of the most common. Each method returns a number which represents the estimated entropy for the given password. It's up to the developer to determine the minimum calculated entropy to accept. Combined with a sensible password policy, this can be a valuable tool in selecting strong passwords. 87 | 88 | #### Examples 89 | 90 | Calculate a password's entropy using [NIST recommendations](http://en.wikipedia.org/wiki/Password_strength#NIST_Special_Publication_800-63): 91 | 92 | ```php 93 | calculate('MySecretPassword'); 99 | ``` 100 | 101 | Calculate a password's entropy using [Wolfram Alpha's algorithm](http://www.wolframalpha.com/input/?i=password+strength+for+qwerty2345#): 102 | 103 | ```php 104 | calculate('MySecretPassword'); 111 | ``` -------------------------------------------------------------------------------- /src/Phpass/Strength.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass; 13 | 14 | use Phpass\Strength\Adapter; 15 | use Phpass\Strength\Adapter\Nist; 16 | use Phpass\Exception\InvalidArgumentException; 17 | 18 | /** 19 | * Strength class 20 | * 21 | * Provides a simple API for working with the various strength calculator 22 | * adapters. If the class is constructed with no arguments, it will construct 23 | * an NIST adapter with default settings for use internally. 24 | * 25 | * calculate($password); 30 | * 31 | * @package PHPass\Strength 32 | * @category Cryptography 33 | * @author Ryan Chouinard 34 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 35 | * @link https://github.com/rchouinard/phpass Project at GitHub 36 | */ 37 | class Strength 38 | { 39 | 40 | /** 41 | * Instance of the adapter to use for calculating string entropy. 42 | * 43 | * @var Adapter 44 | */ 45 | protected $_adapter; 46 | 47 | /** 48 | * Class constructor. 49 | * 50 | * new \Phpass\Strength\Adapter\Wolfram 61 | * ); 62 | * $phpassStrength = new \Phpass\Strength($options); 63 | * 64 | * @param Array|Adapter $options 65 | * Either an associative array of options, or an instance of Adapter. 66 | * @return void 67 | * @throws InvalidArgumentException 68 | * An InvalidArgumentException is thrown if a value other than an Adapter 69 | * instance or options array is passed to the constructor. 70 | */ 71 | public function __construct($options = array ()) 72 | { 73 | $this->_adapter = new Nist; 74 | if ($options instanceof Adapter) { 75 | $options = array ('adapter' => $options); 76 | } 77 | 78 | if (!is_array($options)) { 79 | throw new InvalidArgumentException('Expected an instance of Phpass\\Strength\\Adapter or an associative array of options.'); 80 | } 81 | 82 | $this->setOptions($options); 83 | } 84 | 85 | /** 86 | * Set the adapter to use for calculating string entropy. 87 | * 88 | * @param Adapter $adapter 89 | * An instance of a class implementing the Adapter interface. 90 | * @return Strength 91 | */ 92 | public function setAdapter(Adapter $adapter) 93 | { 94 | $this->_adapter = $adapter; 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Retrieve the adapter used for calculating string entropy. 101 | * 102 | * @return Adapter 103 | */ 104 | public function getAdapter() 105 | { 106 | return $this->_adapter; 107 | } 108 | 109 | /** 110 | * Set options. 111 | * 112 | *
113 | *
adapter
114 | *
Instance of a class implementing the Adapter interface.
115 | *
116 | * 117 | * @param Array $options 118 | * An associative array of options. 119 | * @return Strength 120 | * @throws InvalidArgumentException 121 | * An InvalidArgumentException is thrown if a value does not match what 122 | * is expected for the option key. 123 | */ 124 | public function setOptions(Array $options) 125 | { 126 | $options = array_change_key_case($options, CASE_LOWER); 127 | foreach ($options as $option => $value) { 128 | switch ($option) { 129 | case 'adapter': 130 | if (!$value instanceof Adapter) { 131 | throw new InvalidArgumentException("Value of key 'adapter' must be an instance of Phpass\\Strength\\Adapter."); 132 | } 133 | $this->setAdapter($value); 134 | break; 135 | default: 136 | break; 137 | } 138 | } 139 | 140 | return $this; 141 | } 142 | 143 | /** 144 | * Return the calculated entropy. 145 | * 146 | * @param string $password 147 | * The string to check. 148 | * @return integer 149 | * Returns the calculated string entropy. 150 | */ 151 | public function calculate($password) 152 | { 153 | return $this->_adapter->check($password); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Sha256Crypt.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Exception\InvalidArgumentException; 15 | 16 | /** 17 | * SHA256 crypt hash adapter 18 | * 19 | * @package PHPass\Hashes 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | * @since 2.1.0 25 | */ 26 | class Sha256Crypt extends Base 27 | { 28 | 29 | /** 30 | * Number of rounds used to generate new hashes. 31 | * 32 | * @var integer 33 | */ 34 | protected $_iterationCount = 80000; 35 | 36 | /** 37 | * String identifier used to generate new hash values. 38 | * 39 | * @var string 40 | */ 41 | protected $_identifier = '5'; 42 | 43 | /** 44 | * Generate a salt string compatible with this adapter. 45 | * 46 | * @param string $input 47 | * Optional random 96-bit string to use when generating the salt. 48 | * @return string 49 | * Returns the generated salt string. 50 | */ 51 | public function genSalt($input = null) 52 | { 53 | if (!$input) { 54 | $input = $this->_getRandomBytes(12); 55 | } 56 | 57 | $identifier = $this->_identifier; 58 | 59 | $rounds = ''; 60 | if ($this->_iterationCount != 5000) { 61 | $rounds = 'rounds=' . $this->_iterationCount . '$'; 62 | } 63 | 64 | $salt = $this->_encode64($input, 12); 65 | 66 | return '$' . $identifier . '$' . $rounds . $salt . '$'; 67 | } 68 | 69 | /** 70 | * Set adapter options. 71 | * 72 | * Expects an associative array of option keys and values used to configure 73 | * the hash adapter instance. 74 | * 75 | *
76 | *
iterationCount
77 | *
An integer value between 1,000 and 999,999,999, inclusive. This 78 | * value determines the cost factor associated with generating a new 79 | * hash value. A higher number means a higher cost. Defaults to 80 | * 40,000.
81 | *
82 | * 83 | * @param Array $options 84 | * Associative array of adapter options. 85 | * @return Bcrypt 86 | * @see Base::setOptions() 87 | */ 88 | public function setOptions(Array $options) 89 | { 90 | parent::setOptions($options); 91 | 92 | $options = array_change_key_case($options, CASE_LOWER); 93 | foreach ($options as $key => $value) { 94 | switch ($key) { 95 | case 'iterationcountlog2': 96 | $value = (1 << (int) $value); 97 | // Fall through 98 | case 'iterationcount': 99 | $value = (int) $value; 100 | if ($value < 1000 || $value > 999999) { 101 | throw new InvalidArgumentException('Iteration count must be between 1000 and 999999'); 102 | } 103 | $this->_iterationCount = $value; 104 | break; 105 | default: 106 | break; 107 | } 108 | } 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * Check if a hash string is valid for the current adapter. 115 | * 116 | * @since 2.1.0 117 | * @param string $input 118 | * Hash string to verify. 119 | * @return boolean 120 | * Returns true if the input string is a valid hash value, false 121 | * otherwise. 122 | */ 123 | public function verifyHash($input) 124 | { 125 | return ($this->verifySalt(substr($input, 0, -43)) && 1 === preg_match('/^[\.\/0-9A-Za-z]{43}$/', substr($input, -43))); 126 | } 127 | 128 | /** 129 | * Check if a salt string is valid for the current adapter. 130 | * 131 | * @since 2.1.0 132 | * @param string $input 133 | * Salt string to verify. 134 | * @return boolean 135 | * Returns true if the input string is a valid salt value, false 136 | * otherwise. 137 | */ 138 | public function verifySalt($input) 139 | { 140 | $regex = '/^\$' . $this->_identifier . '\$(?:rounds=(\d{4,9})\$)?([\.\/0-9A-Za-z]{0,16})\$?$/'; 141 | $matches = array (); 142 | 143 | $appearsValid = (1 === preg_match($regex, $input, $matches)); 144 | if ($appearsValid) { 145 | $rounds = (int) $matches[1]; 146 | $salt = $matches[2]; 147 | 148 | // If rounds parameter is in the salt position, the configuration 149 | // is probably not what the user intends. We could let it pass and 150 | // it'll "work", but we'll fail it for now. 151 | if (strpos($salt, 'rounds=') === 0) { 152 | $appearsValid = false; 153 | } 154 | 155 | if (!empty ($matches[1]) && ($rounds < 1000 || $rounds > 999999999)) { 156 | $appearsValid = false; 157 | } 158 | } 159 | 160 | return $appearsValid; 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/Pbkdf2Test.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class Pbkdf2Test extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Pbkdf2; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function rfc6070TestVectorProvider() 46 | { 47 | return array ( 48 | array ( 49 | array ( 50 | 'P' => 'password', 51 | 'S' => 'salt', 52 | 'c' => 1, 53 | 'dkLen' => 20 54 | ), 55 | '0c60c80f961f0e71f3a9b524af6012062fe037a6' 56 | ), 57 | array ( 58 | array ( 59 | 'P' => 'password', 60 | 'S' => 'salt', 61 | 'c' => 2, 62 | 'dkLen' => 20 63 | ), 64 | 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957' 65 | ), 66 | array ( 67 | array ( 68 | 'P' => 'password', 69 | 'S' => 'salt', 70 | 'c' => 4096, 71 | 'dkLen' => 20 72 | ), 73 | '4b007901b765489abead49d926f721d065a429c1' 74 | ), 75 | // Takes a long time to run :-) 76 | //array ( 77 | // array ( 78 | // 'P' => 'password', 79 | // 'S' => 'salt', 80 | // 'c' => 16777216, 81 | // 'dkLen' => 20 82 | // ), 83 | // 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984' 84 | //), 85 | array ( 86 | array ( 87 | 'P' => 'passwordPASSWORDpassword', 88 | 'S' => 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 89 | 'c' => 4096, 90 | 'dkLen' => 25 91 | ), 92 | '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038' 93 | ), 94 | array ( 95 | array ( 96 | 'P' => "pass\0word", 97 | 'S' => "sa\0lt", 98 | 'c' => 4096, 99 | 'dkLen' => 16 100 | ), 101 | '56fa6aa75548099dcc37d7f03425e0c3' 102 | ) 103 | ); 104 | } 105 | 106 | /** 107 | * @return array 108 | */ 109 | public function validTestVectorProvider() 110 | { 111 | $vectors = array ( 112 | // Generated using the Python PassLib 113 | array ("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'), 114 | array ("password", '$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ'), 115 | array ("password", '$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa17k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'), 116 | ); 117 | 118 | return $vectors; 119 | } 120 | 121 | /** 122 | * @return array 123 | */ 124 | public function invalidTestVectorProvider() 125 | { 126 | $vectors = array ( 127 | array ("", '$pbkdf2$01212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc', '*0'), 128 | array ("", '*0', '*1'), 129 | array ("", '*1', '*0'), 130 | ); 131 | 132 | return $vectors; 133 | } 134 | 135 | /** 136 | * @test 137 | * @dataProvider rfc6070TestVectorProvider 138 | */ 139 | public function pbkdf2MethodPassesUsingRfc6070TestVectors($input, $output) 140 | { 141 | $class = new \ReflectionClass('Phpass\\Hash\\Adapter\\Pbkdf2'); 142 | $method = $class->getMethod('_pbkdf2'); 143 | $method->setAccessible(true); 144 | 145 | $adapter = new Pbkdf2; 146 | 147 | $this->assertEquals( 148 | $output, // Expected 149 | bin2hex($method->invokeArgs($adapter, $input)) // Actual 150 | ); 151 | } 152 | 153 | /** 154 | * @test 155 | * @dataProvider validTestVectorProvider 156 | */ 157 | public function validTestVectorsProduceExpectedResults($password, $hash) 158 | { 159 | $this->assertEquals($hash, $this->_adapter->crypt($password, $hash)); 160 | } 161 | 162 | /** 163 | * @test 164 | * @dataProvider invalidTestVectorProvider 165 | */ 166 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 167 | { 168 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $hash)); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/Phpass/Strength/Adapter/Base.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Strength\Adapter; 13 | use Phpass\Strength\Adapter; 14 | 15 | /** 16 | * Strength adapter base class 17 | * 18 | * @package PHPass\Strength 19 | * @category Cryptography 20 | * @author Ryan Chouinard 21 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 22 | * @link https://github.com/rchouinard/phpass Project at GitHub 23 | */ 24 | abstract class Base implements Adapter 25 | { 26 | 27 | const CLASS_LETTER = 'letter'; 28 | const CLASS_UPPER = 'upper'; 29 | const CLASS_LOWER = 'lower'; 30 | const CLASS_NUMBER = 'number'; 31 | const CLASS_SYMBOL = 'symbol'; 32 | 33 | /** 34 | * The string to analyze. 35 | * 36 | * @var string 37 | */ 38 | protected $_password; 39 | 40 | /** 41 | * The calculated entropy. 42 | * 43 | * @var integer 44 | */ 45 | protected $_score; 46 | 47 | /** 48 | * The string length in bytes. 49 | * 50 | * @var integer 51 | */ 52 | protected $_length; 53 | 54 | /** 55 | * Map of the number of times tokens appear in the string. 56 | * 57 | * @var array 58 | */ 59 | protected $_tokens; 60 | 61 | /** 62 | * Map of indices pointing to token classes in the string. 63 | * 64 | * @var array 65 | */ 66 | protected $_tokenIndices; 67 | 68 | /** 69 | * Map of the number of times a token class occurs in the string. 70 | * 71 | * @var array 72 | */ 73 | protected $_tokenCounts; 74 | 75 | /** 76 | * Analyze a string and store relevant metadata. 77 | * 78 | * @param string $password 79 | * The string to analyze. 80 | * @return void 81 | */ 82 | protected function _analyze($password) 83 | { 84 | // Reset the class 85 | $this->_password = $password; 86 | $this->_score = 0; 87 | $this->_length = strlen($password); 88 | $this->_tokens = array (); 89 | $this->_tokenCounts = array ( 90 | self::CLASS_LETTER => 0, 91 | self::CLASS_UPPER => 0, 92 | self::CLASS_LOWER => 0, 93 | self::CLASS_NUMBER => 0, 94 | self::CLASS_SYMBOL => 0 95 | ); 96 | $this->_tokenIndices = array ( 97 | self::CLASS_LETTER => array (), 98 | self::CLASS_UPPER => array (), 99 | self::CLASS_LOWER => array (), 100 | self::CLASS_NUMBER => array (), 101 | self::CLASS_SYMBOL => array () 102 | ); 103 | 104 | $this->_parseTokens(); 105 | } 106 | 107 | /** 108 | * Tokenize the password string. 109 | * 110 | * @return void 111 | */ 112 | protected function _parseTokens() 113 | { 114 | for ($index = 0; $index < $this->_length; ++$index) { 115 | $token = $this->_password[$index]; 116 | $tokenAsciiValue = ord($token); 117 | 118 | if ($tokenAsciiValue >= 48 && $tokenAsciiValue <= 57) { 119 | $tokenClass = self::CLASS_NUMBER; 120 | } elseif ($tokenAsciiValue >= 65 && $tokenAsciiValue <= 90) { 121 | $tokenClass = self::CLASS_UPPER; 122 | } elseif ($tokenAsciiValue >= 97 && $tokenAsciiValue <= 122) { 123 | $tokenClass = self::CLASS_LOWER; 124 | } else { 125 | $tokenClass = self::CLASS_SYMBOL; 126 | } 127 | 128 | // Track the number and index of tokens belonging to class 129 | ++$this->_tokenCounts[$tokenClass]; 130 | $this->_tokenIndices[$tokenClass][] = $index; 131 | 132 | // Members of UPPER and LOWER also belong to LETTER 133 | if ($tokenClass == self::CLASS_UPPER || $tokenClass == self::CLASS_LOWER) { 134 | ++$this->_tokenCounts[self::CLASS_LETTER]; 135 | $this->_tokenIndices[self::CLASS_LETTER][] = $index; 136 | } 137 | 138 | // Track the number of times this token appears 139 | if (array_key_exists($token, $this->_tokens)) { 140 | $this->_tokens[$token] += 1; 141 | } else { 142 | $this->_tokens[$token] = 1; 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * Return a map of token indices within the string for a given class. 149 | * 150 | * @param string $class 151 | * Token class to map. 152 | * @return array 153 | * Returns a numerically indexed array of indicies where members of a 154 | * given class may be found in the string. 155 | */ 156 | protected function _getClassIndices($class) 157 | { 158 | $indices = array (); 159 | if ($class == self::CLASS_LETTER) { 160 | $indices = array_merge( 161 | $this->_getClassIndices(self::CLASS_LOWER), 162 | $this->_getClassIndices(self::CLASS_UPPER) 163 | ); 164 | sort($indices); 165 | } else { 166 | if (isset ($this->_tokenIndices[$class])) { 167 | $indices = $this->_tokenIndices[$class]; 168 | } 169 | } 170 | 171 | return $indices; 172 | } 173 | 174 | /** 175 | * Return the number of times members of a token class appear in the string. 176 | * 177 | * @param string $class 178 | * Token class to count. 179 | * @return integer 180 | * Returns the number of times members of the token class appear in the 181 | * string. 182 | */ 183 | protected function _getClassCount($class) 184 | { 185 | $count = 0; 186 | if ($class == self::CLASS_LETTER) { 187 | $count = $this->_getClassCount(self::CLASS_LOWER) 188 | + $this->_getClassCount(self::CLASS_UPPER); 189 | } else { 190 | if (isset ($this->_tokenCounts[$class])) { 191 | $count = $this->_tokenCounts[$class]; 192 | } 193 | } 194 | 195 | return $count; 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /tests/src/Phpass/Hash/Adapter/BcryptTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use \PHPUnit_Framework_TestCase as TestCase; 15 | 16 | /** 17 | * PHP Password Library 18 | * 19 | * @package PHPass\Tests 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class BcryptTest extends TestCase 26 | { 27 | 28 | /** 29 | * @var Phpass\Hash\Adapter 30 | */ 31 | protected $_adapter; 32 | 33 | /** 34 | * (non-PHPdoc) 35 | * @see PHPUnit_Framework_TestCase::setUp() 36 | */ 37 | protected function setUp() 38 | { 39 | $this->_adapter = new Bcrypt; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function validTestVectorProvider() 46 | { 47 | $vectors = array ( 48 | // From John the Ripper 1.7.9 49 | array ("U*U", '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW'), 50 | array ("U*U*", '$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK'), 51 | array ("U*U*U", '$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a'), 52 | array ("", '$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy'), 53 | array ("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789chars after 72 are ignored", '$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui'), 54 | array ("\xa3", '$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e'), 55 | array ("\xa3", '$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), 56 | array ("\xd1\x91", '$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS'), 57 | array ("\xd0\xc1\xd2\xcf\xcc\xd8", '$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS'), 58 | array ("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaachars after 72 are ignored as usual", '$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6'), 59 | array ("\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55", '$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy'), 60 | array ("", '$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy'), 61 | array ("\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff", '$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe'), 62 | ); 63 | 64 | return $vectors; 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function invalidTestVectorProvider() 71 | { 72 | $vectors = array ( 73 | // From Openwall's crypt v1.2 74 | array ("", '$2a$03$CCCCCCCCCCCCCCCCCCCCC.', '*0'), 75 | array ("", '$2a$32$CCCCCCCCCCCCCCCCCCCCC.', '*0'), 76 | array ("", '$2z$05$CCCCCCCCCCCCCCCCCCCCC.', '*0'), 77 | 78 | // PHP's crypt actually fails the following tests, so the adapter 79 | // works around them. 80 | // 81 | // See https://bugs.php.net/bug.php?id=61852 82 | array ("", '$2`$05$CCCCCCCCCCCCCCCCCCCCC.', '*0'), 83 | array ("", '$2{$05$CCCCCCCCCCCCCCCCCCCCC.', '*0'), 84 | array ("", '*0', '*1'), 85 | array ("", '*1', '*0'), 86 | ); 87 | 88 | return $vectors; 89 | } 90 | 91 | /** 92 | * @test 93 | * @dataProvider validTestVectorProvider 94 | */ 95 | public function validTestVectorsProduceExpectedResults($password, $hash) 96 | { 97 | $config = substr($hash, 0, 29); 98 | $this->assertEquals($hash, $this->_adapter->crypt($password, $config)); 99 | } 100 | 101 | /** 102 | * @test 103 | * @dataProvider invalidTestVectorProvider 104 | */ 105 | public function invalidTestVectorsProduceExpectedResults($password, $hash, $errorString) 106 | { 107 | $config = substr($hash, 0, 29); 108 | $this->assertEquals($errorString, $this->_adapter->crypt($password, $config)); 109 | } 110 | 111 | /** 112 | * @test 113 | * @return void 114 | */ 115 | public function modifyingOptionsUpdatesAdapterBehavior() 116 | { 117 | $adapter = $this->_adapter; 118 | 119 | $adapter->setOptions(array ('identifier' => '2a', 'iterationCountLog2' => 5)); 120 | $this->assertStringStartsWith('$2a$05$', $adapter->genSalt()); 121 | 122 | $adapter->setOptions(array ('iterationCountLog2' => 8)); 123 | $this->assertStringStartsWith('$2a$08$', $adapter->genSalt()); 124 | 125 | $adapter->setOptions(array ('identifier' => '2x')); 126 | $this->assertStringStartsWith('$2x$08$', $adapter->genSalt()); 127 | 128 | $adapter->setOptions(array ('identifier' => '2y')); 129 | $this->assertStringStartsWith('$2y$08$', $adapter->genSalt()); 130 | 131 | try { 132 | $adapter->setOptions(array ('identifier' => 'invalid')); 133 | } catch (\Exception $e) {} 134 | $this->assertInstanceOf('Phpass\\Exception\\InvalidArgumentException', $e); 135 | unset($e); 136 | 137 | try { 138 | $adapter->setOptions(array ('iterationCountLog2' => '0')); 139 | } catch (\Exception $e) {} 140 | $this->assertInstanceOf('Phpass\\Exception\\InvalidArgumentException', $e); 141 | unset($e); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/ExtDes.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Exception\InvalidArgumentException; 15 | 16 | /** 17 | * Extended DES hash adapter 18 | * 19 | * @package PHPass\Hashes 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class ExtDes extends Base 26 | { 27 | 28 | /** 29 | * Number of rounds used to generate new hashes. 30 | * 31 | * @var integer 32 | */ 33 | protected $_iterationCount = 5001; 34 | 35 | /** 36 | * Generate a salt string compatible with this adapter. 37 | * 38 | * @param string $input 39 | * Optional random 24-bit string to use when generating the salt. 40 | * @return string 41 | * Returns the generated salt string. 42 | */ 43 | public function genSalt($input = null) 44 | { 45 | if (!$input) { 46 | $input = $this->_getRandomBytes(3); 47 | } 48 | 49 | // Hash identifier 50 | $identifier = '_'; 51 | 52 | // Cost factor - must be between 1 and 16777215 53 | $costFactor = min(max($this->_iterationCount, 1), 0xffffff); 54 | // Should be odd to avoid revealing weak DES keys 55 | if (($costFactor % 2) == 0) { 56 | --$costFactor; 57 | } 58 | 59 | // Salt string 60 | $salt = $this->_encode64($input, 3); 61 | 62 | // _CCCCSSSS 63 | return $identifier . $this->_encodeInt24($costFactor) . $salt; 64 | } 65 | 66 | /** 67 | * Set adapter options. 68 | * 69 | * Expects an associative array of option keys and values used to configure 70 | * this adapter. 71 | * 72 | *
73 | *
iterationCount
74 | *
Number of rounds to use when generating new hashes. Must be 75 | * between 1 and 16777215. Defaults to 5001.
76 | *
77 | * 78 | * @param Array $options 79 | * Associative array of adapter options. 80 | * @return self 81 | * Returns an instance of self to support method chaining. 82 | * @throws InvalidArgumentException 83 | * Throws an InvalidArgumentException if a provided option key contains 84 | * an invalid value. 85 | * @see Base::setOptions() 86 | */ 87 | public function setOptions(Array $options) 88 | { 89 | parent::setOptions($options); 90 | $options = array_change_key_case($options, CASE_LOWER); 91 | 92 | foreach ($options as $key => $value) { 93 | switch ($key) { 94 | case 'iterationcountlog2': 95 | $value = (1 << (int) $value); 96 | // Fall through 97 | case 'iterationcount': 98 | $value = (int) $value; 99 | if ($value < 1 || $value > (1 << 24) - 1) { 100 | throw new InvalidArgumentException('Iteration count must be between 1 and 16777215'); 101 | } 102 | $this->_iterationCount = $value; 103 | break; 104 | default: 105 | break; 106 | } 107 | } 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * Check if a hash string is valid for the current adapter. 114 | * 115 | * @since 2.1.0 116 | * @param string $input 117 | * Hash string to verify. 118 | * @return boolean 119 | * Returns true if the input string is a valid hash value, false 120 | * otherwise. 121 | */ 122 | public function verifyHash($input) 123 | { 124 | return ($this->verifySalt(substr($input, 0, -11)) && 1 === preg_match('/^[\.\/0-9A-Za-z]{11}$/', substr($input, -11))); 125 | } 126 | 127 | /** 128 | * Check if a salt string is valid for the current adapter. 129 | * 130 | * @since 2.1.0 131 | * @param string $input 132 | * Salt string to verify. 133 | * @return boolean 134 | * Returns true if the input string is a valid salt value, false 135 | * otherwise. 136 | */ 137 | public function verifySalt($input) 138 | { 139 | $appearsValid = (1 === preg_match('/^_[\.\/0-9A-Za-z]{8}$/', $input)); 140 | if ($appearsValid) { 141 | $costFactor = $this->_decodeInt24(substr($input, 1, 4)); 142 | if ($costFactor < 1 || $costFactor > (1 << 24) - 1) { 143 | $appearsValid = false; 144 | } 145 | } 146 | 147 | return $appearsValid; 148 | } 149 | 150 | /** 151 | * Encode a 24-bit integer as a 4-byte string. 152 | * 153 | * @param integer $integer 154 | * The integer to encode. Must be between 0 and 16777215. 155 | * @return string 156 | * Returns the encoded string. 157 | * @throws InvalidArgumentException 158 | * Throws an InvalidArgumentException if the integer is outside of the 159 | * range 0 - 16777215. 160 | */ 161 | protected function _encodeInt24($integer) 162 | { 163 | $integer = (int) $integer; 164 | if ($integer < 0 || $integer > 0xffffff) { 165 | throw new InvalidArgumentException('Integer is out of range'); 166 | } 167 | 168 | $string = $this->_itoa64[$integer & 0x3f]; 169 | $string .= $this->_itoa64[($integer >> 0x06) & 0x3f]; 170 | $string .= $this->_itoa64[($integer >> 0x0c) & 0x3f]; 171 | $string .= $this->_itoa64[($integer >> 0x12) & 0x3f]; 172 | 173 | return $string; 174 | } 175 | 176 | /** 177 | * Decode a 24-bit integer encoded as a 4-byte string. 178 | * 179 | * @param string $source 180 | * The source string to decode. 181 | * @return integer 182 | * Returns the decoded integer. 183 | * @throws InvalidArgumentException 184 | * Throws an InvalidArgumentException if the source string is not exactly 185 | * 4 bytes. 186 | */ 187 | protected function _decodeInt24($source) 188 | { 189 | if (strlen($source) != 4) { 190 | throw new InvalidArgumentException('Source must be exactly 4 bytes'); 191 | } 192 | 193 | $integer = strpos($this->_itoa64, $source{0}); 194 | $integer += (strpos($this->_itoa64, $source{1}) << 0x06); 195 | $integer += (strpos($this->_itoa64, $source{2}) << 0x0c); 196 | $integer += (strpos($this->_itoa64, $source{3}) << 0x12); 197 | 198 | return $integer; 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Portable.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Exception\InvalidArgumentException; 15 | use Phpass\Exception\RuntimeException; 16 | 17 | /** 18 | * PHPass portable hash adapter 19 | * 20 | * Implements a hashing algorithm compatible with the original Openwall phpass 21 | * portable hash. 22 | * 23 | * @package PHPass\Hashes 24 | * @category Cryptography 25 | * @author Ryan Chouinard 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/rchouinard/phpass Project at GitHub 28 | */ 29 | class Portable extends Base 30 | { 31 | 32 | /** 33 | * Logarithmic cost value used when generating new hash values. 34 | * 35 | * @var integer 36 | */ 37 | protected $_iterationCountLog2 = 12; 38 | 39 | /** 40 | * Flag indicating if new hashes should use phpBB hash identifiers. 41 | * 42 | * By default, new hashes will use the $P$ identifier. If this flag is set 43 | * to true, new hashes will use the $H$ identifier. 44 | * 45 | * @var boolean 46 | */ 47 | protected $_phpBBCompat = false; 48 | 49 | /** 50 | * Return a hashed string. 51 | * 52 | * @param string $password 53 | * The string to be hashed. 54 | * @param string $salt 55 | * An optional salt string to base the hashing on. If not provided, a 56 | * suitable string is generated by the adapter. 57 | * @return string 58 | * Returns the hashed string. On failure, a standard crypt error string 59 | * is returned which is guaranteed to differ from the salt. 60 | * @throws RuntimeException 61 | * A RuntimeException is thrown on failure if 62 | * self::$_throwExceptionOnFailure is true. 63 | */ 64 | public function crypt($password, $salt = null) 65 | { 66 | if (!$salt) { 67 | $salt = $this->genSalt(); 68 | } 69 | 70 | $hash = '*0'; 71 | if ($this->verify($salt)) { 72 | $count = 1 << strpos($this->_itoa64, $salt[3]); 73 | $checksum = md5(substr($salt, 4, 8) . $password, true); 74 | do { 75 | $checksum = md5($checksum . $password, true); 76 | } while (--$count); 77 | $hash = substr($salt, 0, 12) . $this->_encode64($checksum, 16); 78 | } 79 | 80 | if (!$this->verifyHash($hash)) { 81 | $hash = ($salt != '*0') ? '*0' : '*1'; 82 | if ($this->_throwExceptionOnFailure) { 83 | throw new RuntimeException('Failed generating a valid hash', $hash); 84 | } 85 | } 86 | 87 | return $hash; 88 | } 89 | 90 | /** 91 | * Generate a salt string compatible with this adapter. 92 | * 93 | * @param string $input 94 | * Optional random 48-bit string to use when generating the salt. 95 | * @return string 96 | * Returns the generated salt string. 97 | */ 98 | public function genSalt($input = null) 99 | { 100 | if (!$input) { 101 | $input = $this->_getRandomBytes(6); 102 | } 103 | 104 | // Hash identifier 105 | $identifier = $this->_phpBBCompat ? 'H' : 'P'; 106 | 107 | // Cost factor 108 | $costFactor = $this->_itoa64[min($this->_iterationCountLog2 + 5, 30)]; 109 | 110 | // Salt string 111 | $salt = $this->_encode64($input, 6); 112 | 113 | return '$' . $identifier . '$' . $costFactor . $salt; 114 | } 115 | 116 | /** 117 | * Set adapter options. 118 | * 119 | * Expects an associative array of option keys and values used to configure 120 | * this adapter. 121 | * 122 | *
123 | *
iterationCountLog2
124 | *
Base-2 logarithm of the iteration count for the underlying 125 | * Blowfish-based hashing algorithm. Must be in range 7 - 30. 126 | * Defaults to 12.
127 | *
phpBBCompat
128 | *
If true, new hashes will use the phpBB identifier $H$ instead of 129 | * the standard $P$. Defaults to false.
130 | *
131 | * 132 | * @param Array $options 133 | * Associative array of adapter options. 134 | * @return self 135 | * Returns an instance of self to support method chaining. 136 | * @throws InvalidArgumentException 137 | * Throws an InvalidArgumentException if a provided option key contains 138 | * an invalid value. 139 | * @see Base::setOptions() 140 | */ 141 | public function setOptions(Array $options) 142 | { 143 | parent::setOptions($options); 144 | $options = array_change_key_case($options, CASE_LOWER); 145 | 146 | foreach ($options as $key => $value) { 147 | switch ($key) { 148 | case 'iterationcountlog2': 149 | $value = (int) $value; 150 | if ($value < 7 || $value > 30) { 151 | throw new InvalidArgumentException('Iteration count must be between 7 and 30'); 152 | } 153 | $this->_iterationCountLog2 = $value; 154 | break; 155 | case 'phpbbcompat': 156 | $this->_phpBBCompat = (bool) $value; 157 | break; 158 | default: 159 | break; 160 | } 161 | } 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * Check if a hash string is valid for the current adapter. 168 | * 169 | * @since 2.1.0 170 | * @param string $input 171 | * Hash string to verify. 172 | * @return boolean 173 | * Returns true if the input string is a valid hash value, false 174 | * otherwise. 175 | */ 176 | public function verifyHash($input) 177 | { 178 | return ($this->verifySalt(substr($input, 0, -22)) && 1 === preg_match('/^[\.\/0-9A-Za-z]{22}$/', substr($input, -22))); 179 | } 180 | 181 | /** 182 | * Check if a salt string is valid for the current adapter. 183 | * 184 | * @since 2.1.0 185 | * @param string $input 186 | * Salt string to verify. 187 | * @return boolean 188 | * Returns true if the input string is a valid salt value, false 189 | * otherwise. 190 | */ 191 | public function verifySalt($input) 192 | { 193 | $appearsValid = (1 === preg_match('/^\$[PH]{1}\$[\.\/0-9A-Za-z]{1}[\.\/0-9A-Za-z]{8}$/', $input)); 194 | if ($appearsValid) { 195 | $costFactor = strpos($this->_itoa64, $input[3]); 196 | if ($costFactor < 7 || $costFactor > 30) { 197 | $appearsValid = false; 198 | } 199 | } 200 | 201 | return $appearsValid; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Bcrypt.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Exception\InvalidArgumentException; 15 | 16 | /** 17 | * Bcrypt hash adapter 18 | * 19 | * @package PHPass\Hashes 20 | * @category Cryptography 21 | * @author Ryan Chouinard 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/rchouinard/phpass Project at GitHub 24 | */ 25 | class Bcrypt extends Base 26 | { 27 | 28 | /** 29 | * String identifier used to generate new hash values. 30 | * 31 | * @var string 32 | */ 33 | protected $_identifier = '2y'; 34 | 35 | /** 36 | * Logarithmic cost value used to generate new hash values. 37 | * 38 | * @var integer 39 | */ 40 | protected $_iterationCountLog2 = 12; 41 | 42 | /** 43 | * Alphabet used in itoa64 conversions. 44 | * 45 | * @var string 46 | */ 47 | protected $_itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 48 | 49 | /** 50 | * List of valid hash identifier strings. 51 | * 52 | * @var array 53 | */ 54 | protected $_validIdentifiers = array ('2a', '2x', '2y'); 55 | 56 | /** 57 | * Class constructor. 58 | * 59 | * @param Array $options 60 | * Associative array of adapter options. 61 | * @return void 62 | * Returns nothing; it's a constructor. 63 | * @see self::setOptions() 64 | * @see Base::__construct() 65 | */ 66 | public function __construct(Array $options = array ()) 67 | { 68 | // Versions of PHP < 5.3.7 only support the 2a identifier 69 | if (version_compare(PHP_VERSION, '5.3.7', '<')) { 70 | $this->_identifier = '2a'; 71 | $this->_validIdentifiers = array ('2a'); 72 | } 73 | 74 | parent::__construct($options); 75 | } 76 | 77 | /** 78 | * Generate a salt string compatible with this adapter. 79 | * 80 | * @param string $input 81 | * Optional random 128-bit string to use when generating the salt. 82 | * @return string 83 | * Returns the generated salt string. 84 | */ 85 | public function genSalt($input = null) 86 | { 87 | if (!$input) { 88 | $input = $this->_getRandomBytes(16); 89 | } 90 | 91 | // Hash identifier 92 | $identifier = $this->_identifier; 93 | 94 | // Cost factor - "4" to "04" 95 | $costFactor = chr(ord('0') + $this->_iterationCountLog2 / 10); 96 | $costFactor .= chr(ord('0') + $this->_iterationCountLog2 % 10); 97 | 98 | // Salt string 99 | $salt = $this->_encode64($input, 16); 100 | 101 | // $II$CC$SSSSSSSSSSSSSSSSSSSSSS 102 | return '$' . $identifier . '$' . $costFactor . '$' . $salt; 103 | } 104 | 105 | /** 106 | * Set adapter options. 107 | * 108 | * Expects an associative array of option keys and values used to configure 109 | * this adapter. 110 | * 111 | *
112 | *
iterationCountLog2
113 | *
Base-2 logarithm of the iteration count for the underlying 114 | * Blowfish-based hashing algorithm. Must be in range 4 - 31. 115 | * Defaults to 12.
116 | *
identifier
117 | *
Hash identifier to use when generating new hash values. 118 | * Supported identifiers are 2a, 2x, and 2y. Defaults to 2y in PHP 119 | * versions 5.3.7 and above, 2a otherwise.
120 | *
121 | * 122 | * @param Array $options 123 | * Associative array of adapter options. 124 | * @return self 125 | * Returns an instance of self to support method chaining. 126 | * @throws InvalidArgumentException 127 | * Throws an InvalidArgumentException if a provided option key contains 128 | * an invalid value. 129 | * @see Base::setOptions() 130 | */ 131 | public function setOptions(Array $options) 132 | { 133 | parent::setOptions($options); 134 | $options = array_change_key_case($options, CASE_LOWER); 135 | 136 | foreach ($options as $key => $value) { 137 | switch ($key) { 138 | case 'iterationcountlog2': 139 | $value = (int) $value; 140 | if ($value < 4 || $value > 31) { 141 | throw new InvalidArgumentException('Iteration count must be between 4 and 31'); 142 | } 143 | $this->_iterationCountLog2 = $value; 144 | break; 145 | case 'identifier': 146 | $value = strtolower($value); 147 | if (!in_array($value, $this->_validIdentifiers)) { 148 | throw new InvalidArgumentException('Invalid hash identifier'); 149 | } 150 | $this->_identifier = $value; 151 | break; 152 | default: 153 | break; 154 | } 155 | } 156 | 157 | return $this; 158 | } 159 | 160 | /** 161 | * Check if a hash string is valid for the current adapter. 162 | * 163 | * @since 2.1.0 164 | * @param string $input 165 | * Hash string to verify. 166 | * @return boolean 167 | * Returns true if the input string is a valid hash value, false 168 | * otherwise. 169 | */ 170 | public function verifyHash($input) 171 | { 172 | return ($this->verifySalt(substr($input, 0, -31)) && 1 === preg_match('/^[\.\/0-9A-Za-z]{31}$/', substr($input, -31))); 173 | } 174 | 175 | /** 176 | * Check if a salt string is valid for the current adapter. 177 | * 178 | * @since 2.1.0 179 | * @param string $input 180 | * Salt string to verify. 181 | * @return boolean 182 | * Returns true if the input string is a valid salt value, false 183 | * otherwise. 184 | */ 185 | public function verifySalt($input) 186 | { 187 | $appearsValid = (1 === preg_match('/^\$2[axy]{1}\$\d{2}\$[\.\/0-9A-Za-z]{22}$/', $input)); 188 | if ($appearsValid) { 189 | $costFactor = (int) substr($input, 4, 2); 190 | if ($costFactor < 4 || $costFactor > 31) { 191 | $appearsValid = false; 192 | } 193 | } 194 | 195 | return $appearsValid; 196 | } 197 | 198 | /** 199 | * Encode raw data to characters in the itoa64 alphabet. 200 | * 201 | * @param string $input 202 | * Raw binary data to encode. 203 | * @param integer $count 204 | * Number of bytes to encode. 205 | * @return string 206 | * Returns the encoded data as a string. 207 | */ 208 | protected function _encode64($input, $count) 209 | { 210 | $output = ''; 211 | $i = 0; 212 | do { 213 | $c1 = ord($input[$i++]); 214 | $output .= $this->_itoa64[$c1 >> 2]; 215 | $c1 = ($c1 & 0x03) << 4; 216 | if ($i >= $count) { 217 | $output .= $this->_itoa64[$c1]; 218 | break; 219 | } 220 | 221 | $c2 = ord($input[$i++]); 222 | $c1 |= $c2 >> 4; 223 | $output .= $this->_itoa64[$c1]; 224 | $c1 = ($c2 & 0x0f) << 2; 225 | 226 | $c2 = ord($input[$i++]); 227 | $c1 |= $c2 >> 6; 228 | $output .= $this->_itoa64[$c1]; 229 | $output .= $this->_itoa64[$c2 & 0x3f]; 230 | } while (1); 231 | 232 | return $output; 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Base.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Hash\Adapter; 15 | use Phpass\Exception\InvalidArgumentException; 16 | use Phpass\Exception\RuntimeException; 17 | 18 | /** 19 | * Hash adapter base class 20 | * 21 | * @package PHPass\Hashes 22 | * @category Cryptography 23 | * @author Ryan Chouinard 24 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 25 | * @link https://github.com/rchouinard/phpass Project at GitHub 26 | */ 27 | abstract class Base implements Adapter 28 | { 29 | 30 | /** 31 | * Flag indicating whether an exception should be thrown on failure. 32 | * 33 | * @var unknown_type 34 | */ 35 | protected $_throwExceptionOnFailure = false; 36 | 37 | /** 38 | * Alphabet used in itoa64 conversions. 39 | * 40 | * @var string 41 | */ 42 | protected $_itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 43 | 44 | /** 45 | * Cached random data. 46 | * 47 | * This value is used when better methods of generating random data are 48 | * unavailable. 49 | * 50 | * @var string 51 | */ 52 | protected $_randomState; 53 | 54 | /** 55 | * Class constructor. 56 | * 57 | * @param Array $options 58 | * Associative array of adapter options. 59 | * @return void 60 | * Returns nothing; it's a constructor. 61 | * @see self::setOptions() 62 | */ 63 | public function __construct(Array $options = array ()) 64 | { 65 | $this->_randomState = microtime(); 66 | if (function_exists('getmypid')) { 67 | $this->_randomState .= getmypid(); 68 | } 69 | 70 | $this->setOptions($options); 71 | } 72 | 73 | /** 74 | * Return a hashed string. 75 | * 76 | * @param string $password 77 | * The string to be hashed. 78 | * @param string $salt 79 | * An optional salt string to base the hashing on. If not provided, a 80 | * suitable string is generated by the adapter. 81 | * @return string 82 | * Returns the hashed string. On failure, a standard crypt error string 83 | * is returned which is guaranteed to differ from the salt. 84 | * @throws RuntimeException 85 | * A RuntimeException is thrown on failure if 86 | * self::$_throwExceptionOnFailure is true. 87 | */ 88 | public function crypt($password, $salt = null) 89 | { 90 | if (!$salt) { 91 | $salt = $this->genSalt(); 92 | } 93 | $hash = crypt($password, $salt); 94 | 95 | // XXX: Work around https://bugs.php.net/bug.php?id=61852 96 | if (!$this->verifyHash($hash)) { 97 | $hash = ($salt != '*0') ? '*0' : '*1'; 98 | if ($this->_throwExceptionOnFailure) { 99 | throw new RuntimeException('Failed generating a valid hash', $hash); 100 | } 101 | } 102 | 103 | return $hash; 104 | } 105 | 106 | /** 107 | * Set adapter options. 108 | * 109 | * Expects an associative array of option keys and values used to configure 110 | * this adapter. 111 | * 112 | *
113 | *
throwExceptionOnFailure
114 | *
If true, the crypt() method will throw a RuntimeException on 115 | * failure instead of returning a standard crypt error string. Defaults 116 | * to false.
117 | *
118 | * 119 | * @param Array $options 120 | * Associative array of adapter options. 121 | * @return self 122 | * Returns an instance of self to support method chaining. 123 | */ 124 | public function setOptions(Array $options) 125 | { 126 | $options = array_change_key_case($options, CASE_LOWER); 127 | 128 | foreach ($options as $key => $value) { 129 | switch ($key) { 130 | case 'throwexceptiononfailure': 131 | $this->_throwExceptionOnFailure = (bool) $value; 132 | break; 133 | default: 134 | break; 135 | } 136 | } 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * Encode raw data to characters in the itoa64 alphabet. 143 | * 144 | * @param string $input 145 | * Raw binary data to encode. 146 | * @param integer $count 147 | * Number of bytes to encode. 148 | * @return string 149 | * Returns the encoded data as a string. 150 | */ 151 | protected function _encode64($input, $count) 152 | { 153 | $output = ''; 154 | $i = 0; 155 | do { 156 | $value = ord($input[$i++]); 157 | $output .= $this->_itoa64[$value & 0x3f]; 158 | if ($i < $count) { 159 | $value |= ord($input[$i]) << 0x08; 160 | } 161 | $output .= $this->_itoa64[($value >> 0x06) & 0x3f]; 162 | if ($i++ >= $count) { 163 | break; 164 | } 165 | if ($i < $count) { 166 | $value |= ord($input[$i]) << 0x10; 167 | } 168 | $output .= $this->_itoa64[($value >> 0x0c) & 0x3f]; 169 | if ($i++ >= $count) { 170 | break; 171 | } 172 | $output .= $this->_itoa64[($value >> 0x12) & 0x3f]; 173 | } while ($i < $count); 174 | 175 | return $output; 176 | } 177 | 178 | /** 179 | * Check if a salt or hash string is valid for the current adapter. 180 | * 181 | * @since 2.1.0 182 | * @param string $input 183 | * Salt or hash string to verify. 184 | * @return boolean 185 | * Returns true if the input string is either a valid salt or hash 186 | * string, false otherwise. 187 | */ 188 | public function verify($input) 189 | { 190 | return ($this->verifyHash($input) || $this->verifySalt($input)); 191 | } 192 | 193 | /** 194 | * Generate a pseudo-random string of bytes. 195 | * 196 | * @param integer $count 197 | * The length of the desired string of bytes. Must be a positive integer. 198 | * @return string 199 | * Returns the generated string of bytes. 200 | * @throws InvalidArgumentException 201 | * Thows an InvalidArgumentException if the $count parameter is not a 202 | * positive integer. 203 | */ 204 | protected function _getRandomBytes($count) 205 | { 206 | if (!is_int($count) || $count < 1) { 207 | throw new InvalidArgumentException('Argument must be a positive integer'); 208 | } 209 | 210 | // Try OpenSSL's random generator 211 | if (function_exists('openssl_random_pseudo_bytes')) { 212 | $strongCrypto = false; 213 | $output = openssl_random_pseudo_bytes($count, $strongCrypto); 214 | if ($strongCrypto && strlen($output) == $count) { 215 | return $output; 216 | } 217 | } 218 | 219 | // Try reading from /dev/urandom, if present 220 | $output = ''; 221 | if (is_readable('/dev/urandom') && ($fh = fopen('/dev/urandom', 'rb'))) { 222 | $output = fread($fh, $count); 223 | fclose($fh); 224 | } 225 | 226 | // Fall back to a locally generated "random" string 227 | if (strlen($output) < $count) { 228 | $output = ''; 229 | for ($i = 0; $i < $count; $i += 16) { 230 | $this->_randomState = md5(microtime() . $this->_randomState); 231 | $output .= md5($this->_randomState, true); 232 | } 233 | $output = substr($output, 0, $count); 234 | } 235 | 236 | return $output; 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Sha1Crypt.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Exception\InvalidArgumentException; 15 | use Phpass\Exception\RuntimeException; 16 | 17 | /** 18 | * SHA-1 crypt hash adapter 19 | * 20 | * @package PHPass\Hashes 21 | * @category Cryptography 22 | * @author Ryan Chouinard 23 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 24 | * @link https://github.com/rchouinard/phpass Project at GitHub 25 | * @since 2.1.0 26 | */ 27 | class Sha1Crypt extends Base 28 | { 29 | 30 | /** 31 | * Number of rounds used to generate new hashes. 32 | * 33 | * @var integer 34 | */ 35 | protected $_iterationCount = 40000; 36 | 37 | /** 38 | * Minimum allowed value for the iteration count. 39 | * 40 | * @var integer 41 | */ 42 | protected $_iterationCountMin = 1; 43 | 44 | /** 45 | * Maximum allowed value for the iteration count. 46 | * 47 | * @var integer 48 | */ 49 | protected $_iterationCountMax = 4294967295; 50 | 51 | /** 52 | * Return a hashed string. 53 | * 54 | * @param string $password 55 | * The string to be hashed. 56 | * @param string $salt 57 | * An optional salt string to base the hashing on. If not provided, a 58 | * suitable string is generated by the adapter. 59 | * @return string 60 | * Returns the hashed string. On failure, a standard crypt error string 61 | * is returned which is guaranteed to differ from the salt. 62 | * @throws RuntimeException 63 | * A RuntimeException is thrown on failure if 64 | * self::$_throwExceptionOnFailure is true. 65 | */ 66 | public function crypt($password, $salt = null) 67 | { 68 | if (!$salt) { 69 | $salt = $this->genSalt(); 70 | } 71 | 72 | $hash = '*0'; 73 | if ($this->verify($salt)) { 74 | $parts = $this->_getSettings($salt); 75 | $rounds = $parts['rounds']; 76 | 77 | $checksum = hash_hmac('sha1', $parts['salt'] . '$sha1$' . $parts['rounds'], $password, true); 78 | --$rounds; 79 | if ($rounds) { 80 | do { 81 | $checksum = hash_hmac('sha1', $checksum, $password, true); 82 | } while (--$rounds); 83 | } 84 | 85 | // Shuffle the bits around a bit 86 | $tmp = ''; 87 | foreach (array (2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9, 14, 13, 12, 17, 16, 15, 0, 19, 18) as $offset) { 88 | $tmp .= $checksum[$offset]; 89 | } 90 | $checksum = $tmp; 91 | 92 | $hash = '$sha1$' . $parts['rounds'] . '$' . $parts['salt'] . '$' . $this->_encode64($checksum, 21); 93 | } 94 | 95 | if (!$this->verifyHash($hash)) { 96 | $hash = ($salt != '*0') ? '*0' : '*1'; 97 | if ($this->_throwExceptionOnFailure) { 98 | throw new RuntimeException('Failed generating a valid hash', $hash); 99 | } 100 | } 101 | 102 | return $hash; 103 | } 104 | 105 | /** 106 | * Generate a salt string compatible with this adapter. 107 | * 108 | * @param string $input 109 | * Optional random 48-bit string to use when generating the salt. 110 | * @return string 111 | * Returns the generated salt string. 112 | */ 113 | public function genSalt($input = null) 114 | { 115 | if (!$input) { 116 | $input = $this->_getRandomBytes(6); 117 | } 118 | 119 | $identifier = 'sha1'; 120 | $salt = $this->_encode64($input, 6); 121 | 122 | return '$' . $identifier . '$' . $this->_iterationCount . '$' . $salt . '$'; 123 | } 124 | 125 | /** 126 | * Set adapter options. 127 | * 128 | * Expects an associative array of option keys and values used to configure 129 | * the hash adapter instance. 130 | * 131 | *
132 | *
iterationCount
133 | *
An integer value between 1 and 4294967295, inclusive. This 134 | * value determines the cost factor associated with generating a new 135 | * hash value. A higher number means a higher cost. Defaults to 136 | * 40000.
137 | *
138 | * 139 | * @param Array $options 140 | * Associative array of adapter options. 141 | * @return Bcrypt 142 | * @see Base::setOptions() 143 | */ 144 | public function setOptions(Array $options) 145 | { 146 | parent::setOptions($options); 147 | 148 | $options = array_change_key_case($options, CASE_LOWER); 149 | foreach ($options as $key => $value) { 150 | switch ($key) { 151 | case 'iterationcountlog2': 152 | $value = (int) $value; 153 | $value = bcpow(2, $value, 0); 154 | // Fall through 155 | case 'iterationcount': 156 | $value = (float) $value; 157 | if (!ctype_digit((string) $value) || $value < $this->_iterationCountMin || $value > $this->_iterationCountMax) { 158 | throw new InvalidArgumentException("Iteration count must be an integer between {$this->_iterationCountMin} and {$this->_iterationCountMax}"); 159 | } 160 | $this->_iterationCount = $value; 161 | break; 162 | default: 163 | break; 164 | } 165 | } 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Check if a hash string is valid for the current adapter. 172 | * 173 | * @since 2.1.0 174 | * @param string $input 175 | * Hash string to verify. 176 | * @return boolean 177 | * Returns true if the input string is a valid hash value, false 178 | * otherwise. 179 | */ 180 | public function verifyHash($input) 181 | { 182 | $salt = substr($input, 0, strrpos($input, '$') + 1); 183 | $checksum = substr($input, strrpos($input, '$') + 1); 184 | 185 | return ($this->verifySalt($salt) && 1 === preg_match('/^[\.\/0-9A-Za-z]{28}$/', $checksum)); 186 | } 187 | 188 | /** 189 | * Check if a salt string is valid for the current adapter. 190 | * 191 | * @since 2.1.0 192 | * @param string $input 193 | * Salt string to verify. 194 | * @return boolean 195 | * Returns true if the input string is a valid salt value, false 196 | * otherwise. 197 | */ 198 | public function verifySalt($input) 199 | { 200 | $regex = '/^\$sha1\$(\d{1,10})\$([\.\/0-9A-Za-z]{0,64})\$?$/'; 201 | $matches = array (); 202 | 203 | $appearsValid = (1 === preg_match($regex, $input, $matches)); 204 | if ($appearsValid) { 205 | $rounds = (int) $matches[1]; 206 | $salt = $matches[2]; 207 | 208 | if (!empty ($matches[1]) && ($rounds < 1 || $rounds > 4294967295)) { 209 | $appearsValid = false; 210 | } 211 | } 212 | 213 | return $appearsValid; 214 | } 215 | 216 | /** 217 | * Return an array of hash settings from a given salt string. 218 | * 219 | * @param unknown_type $input 220 | */ 221 | protected function _getSettings($input) 222 | { 223 | $parts = array (); 224 | $matches = array (); 225 | if (1 === preg_match('/^\$sha1\$(\d+)\$([\.\/0-9A-Za-z]{0,64})(?:\$([\.\/0-9A-Za-z]{28}))?$/', rtrim($input, '$'), $matches)) { 226 | $parts['rounds'] = $matches[1]; 227 | $parts['salt'] = $matches[2]; 228 | $parts['checksum'] = $matches[3] ?: null; 229 | } 230 | 231 | return $parts; 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /src/Phpass/Strength/Adapter/Wolfram.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Strength\Adapter; 13 | 14 | /** 15 | * Strength adapter for the Wolfram|Alpha algorithm 16 | * 17 | * @package PHPass\Strength 18 | * @category Cryptography 19 | * @author Ryan Chouinard 20 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 21 | * @link https://github.com/rchouinard/phpass Project at GitHub 22 | */ 23 | class Wolfram extends Base 24 | { 25 | 26 | /** 27 | * Return the calculated entropy. 28 | * 29 | * @param string $password 30 | * The string to check. 31 | * @return integer 32 | * Returns the calculated string entropy. 33 | * @see Adapter::check() 34 | */ 35 | public function check($password) 36 | { 37 | $this->_analyze($password); 38 | 39 | $this->_score = $this->_calculateBaseScore(); 40 | $this->_score += $this->_calculateLetterScore(); 41 | $this->_score += $this->_calculateNumberScore(); 42 | $this->_score += $this->_calculateSymbolScore(); 43 | $this->_score += $this->_calculateMiddleNumberOrSymbolScore(); 44 | 45 | if ($this->_getClassCount(self::CLASS_LETTER) == $this->_length || $this->_getClassCount(self::CLASS_NUMBER) == $this->_length) { 46 | $this->_score -= $this->_length; 47 | } 48 | 49 | $this->_score += $this->_calculateRepeatTokenScore(); 50 | 51 | if ($this->_length > 2) { 52 | $this->_score += $this->_calculateConsecutiveTokenScore(self::CLASS_UPPER); 53 | $this->_score += $this->_calculateConsecutiveTokenScore(self::CLASS_LOWER); 54 | $this->_score += $this->_calculateConsecutiveTokenScore(self::CLASS_NUMBER); 55 | 56 | $this->_score += $this->_calculateSequentialTokenScore(self::CLASS_LETTER); 57 | $this->_score += $this->_calculateSequentialTokenScore(self::CLASS_NUMBER); 58 | } 59 | 60 | return $this->_score; 61 | } 62 | 63 | /** 64 | * Return the base score based on string length. 65 | * 66 | * @return integer 67 | */ 68 | protected function _calculateBaseScore() 69 | { 70 | return $this->_length * 4; 71 | } 72 | 73 | /** 74 | * Return the score for letter tokens. 75 | * 76 | * @return integer 77 | */ 78 | protected function _calculateLetterScore() 79 | { 80 | $score = 0; 81 | 82 | foreach (array (self::CLASS_UPPER, self::CLASS_LOWER) as $class) { 83 | $letterCount = $this->_getClassCount($class); 84 | 85 | if ($letterCount != $this->_length) { 86 | if ($letterCount > 0) { 87 | $score += ($this->_length - $letterCount) * 2; 88 | } 89 | } 90 | } 91 | 92 | return $score; 93 | } 94 | 95 | /** 96 | * Return the score for numeric tokens. 97 | * 98 | * @return integer 99 | */ 100 | protected function _calculateNumberScore() 101 | { 102 | $score = 0; 103 | $numberCount = $this->_getClassCount(self::CLASS_NUMBER); 104 | 105 | if ($numberCount > 0 && $numberCount != $this->_length) { 106 | $score += $numberCount * 4; 107 | } 108 | 109 | return $score; 110 | } 111 | 112 | /** 113 | * Return the score for symbol tokens. 114 | * 115 | * @return integer 116 | */ 117 | protected function _calculateSymbolScore() 118 | { 119 | $score = 0; 120 | $symbolCount = $this->_getClassCount(self::CLASS_SYMBOL); 121 | 122 | if ($symbolCount > 0) { 123 | $score += $symbolCount * 6; 124 | } 125 | 126 | return $score; 127 | } 128 | 129 | /** 130 | * Return the score for special tokens in the middle of the string. 131 | * 132 | * @return integer 133 | */ 134 | protected function _calculateMiddleNumberOrSymbolScore() 135 | { 136 | $score = 0; 137 | 138 | // The Wolfram algorithm actually only accounts for numbers, despite 139 | // what the rule name implies and others have documented. 140 | // 141 | // I've decided to account for both numbers and symbols as the rule 142 | // implies, and treat the Wolfram calculator as bugged. This will mean 143 | // that the calculations of this class and the Wolfram calculator may 144 | // not always match. 145 | foreach (array (self::CLASS_NUMBER, self::CLASS_SYMBOL) as $class) { 146 | $indices = $this->_getClassIndices($class); 147 | foreach ($indices as $key => $index) { 148 | if ($index == 0 || $index == $this->_length - 1) { 149 | unset ($indices[$key]); 150 | } 151 | } 152 | $score += count($indices) * 2; 153 | } 154 | 155 | return $score; 156 | } 157 | 158 | /** 159 | * Return the score for repeated characters. 160 | * 161 | * @return integer 162 | */ 163 | protected function _calculateRepeatTokenScore() 164 | { 165 | $score = 0; 166 | $repeats = 0; 167 | 168 | foreach ($this->_tokens as $tokenCount) { 169 | if ($tokenCount > 1) { 170 | $repeats += $tokenCount - 1; 171 | } 172 | } 173 | 174 | if ($repeats > 0) { 175 | $score -= (int) ($repeats / ($this->_length - $repeats)) + 1; 176 | } 177 | 178 | return $score; 179 | } 180 | 181 | /** 182 | * Return the score for consectutive tokens of the same class. 183 | * 184 | * @param string $class 185 | * The token class on which to base the calculation. 186 | * @return integer 187 | */ 188 | protected function _calculateConsecutiveTokenScore($class) 189 | { 190 | $score = 0; 191 | $pattern = '/[^a-zA-Z0-9]{2,}/'; 192 | 193 | if ($class == self::CLASS_LETTER) { 194 | $pattern = '/[a-zA-Z]{2,}/'; 195 | } 196 | 197 | if ($class == self::CLASS_UPPER) { 198 | $pattern = '/[A-Z]{2,}/'; 199 | } 200 | 201 | if ($class == self::CLASS_LOWER) { 202 | $pattern = '/[a-z]{2,}/'; 203 | } 204 | 205 | if ($class == self::CLASS_NUMBER) { 206 | $pattern = '/[0-9]{2,}/'; 207 | } 208 | 209 | $matches = array (); 210 | preg_match_all($pattern, $this->_password, $matches); 211 | foreach ($matches[0] as $match) { 212 | $score -= (strlen($match) - 1) * 2; 213 | } 214 | 215 | return $score; 216 | } 217 | 218 | /** 219 | * Return the score for sequential tokens of the same class. 220 | * 221 | * @param string $class 222 | * The token class on which to base the calculation. 223 | * @return integer 224 | */ 225 | protected function _calculateSequentialTokenScore($class) 226 | { 227 | $score = 0; 228 | $indices = array (); 229 | $password = $this->_password; 230 | $sequences = array (); 231 | 232 | $indices = $this->_getClassIndices($class); 233 | if ($class == self::CLASS_LETTER) { 234 | $password = strtolower($password); 235 | } 236 | 237 | $sequence = ''; 238 | for ($index = 0; $index < count($indices); ++$index) { 239 | if (isset ($indices[$index + 1]) && $indices[$index + 1] - $indices[$index] == 1 && ord($password[$indices[$index + 1]]) - ord($password[$indices[$index]]) == 1) { 240 | if ($sequence == '') { 241 | $sequence = $password[$indices[$index]] . $password[$indices[$index + 1]]; 242 | } else { 243 | $sequence .= $password[$indices[$index + 1]]; 244 | } 245 | } else { 246 | if ($sequence != '') { 247 | $sequences[] = $sequence; 248 | $sequence = ''; 249 | } 250 | } 251 | } 252 | 253 | foreach ($sequences as $sequence) { 254 | if (strlen($sequence) > 2) { 255 | $score -= (strlen($sequence) - 2) *2; 256 | } 257 | } 258 | 259 | return $score; 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /src/Phpass/Hash.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass; 13 | 14 | use Phpass\Hash\Adapter; 15 | use Phpass\Hash\Adapter\Bcrypt; 16 | use Phpass\Exception\InvalidArgumentException; 17 | use Phpass\Exception\RuntimeException; 18 | 19 | /** 20 | * Hash class 21 | * 22 | * Provides a simple API for working with the various hash adapters. If the 23 | * class is constructed with no arguments, it will construct a bcrypt 24 | * adapter with default settings for use internally. 25 | * 26 | * If an optional HMAC key is provided, password strings will be hashed using 27 | * the chosen HMAC algorithm and the supplied key before being passed to the 28 | * adapter. HMAC-SHA256 is used by default. 29 | * 30 | * hashPassword($password); 36 | * 37 | * // Check a password 38 | * if ($phpassHash->checkPassword($password, $passwordHash)) { 39 | * // Passwords match! 40 | * } 41 | * 42 | * @package PHPass\Hashes 43 | * @category Cryptography 44 | * @author Ryan Chouinard 45 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 46 | * @link https://github.com/rchouinard/phpass Project at GitHub 47 | */ 48 | class Hash 49 | { 50 | 51 | /** 52 | * Instance of the adapter to use for hashing strings. 53 | * 54 | * @var Adapter 55 | */ 56 | protected $_adapter; 57 | 58 | /** 59 | * Name of selected hashing algorithm. 60 | * 61 | * See \hash_algos() for a list of supported algorithms. 62 | * 63 | * @var string 64 | */ 65 | protected $_hmacAlgo = 'sha256'; 66 | 67 | /** 68 | * Shared secret key used for generating the HMAC variant of the string. 69 | * 70 | * @var string 71 | */ 72 | protected $_hmacKey; 73 | 74 | /** 75 | * Class constructor. 76 | * 77 | * Expects either an associative array of options, or an instance of a 78 | * class implementing the Adapter interface. If neither is given, or if the 79 | * 'adapter' option key is omitted, an instance of the Bcrypt adapter is 80 | * created internally by default. 81 | * 82 | * 12 // 2^12 iterations 89 | * )); 90 | * $phpassHash = new \Phpass\Hash($adapter); 91 | * 92 | * // Customize the adapter as well as use additional HMAC hashing 93 | * $options = array ( 94 | * 'adapter' => new \Phpass\Hash\Adapter\ExtDes, 95 | * 'hmacKey' => 'mys3cr3tk3y' 96 | * ); 97 | * $phpassHash = new \Phpass\Hash($options); 98 | * 99 | * @param Array|Adapter $options 100 | * Either an associative array of options, or an instance of Adapter. 101 | * @return void 102 | * @throws InvalidArgumentException 103 | * An InvalidArgumentException is thrown if a value other than an Adapter 104 | * instance or options array is passed to the constructor. 105 | */ 106 | public function __construct($options = array ()) 107 | { 108 | $this->_adapter = new Bcrypt; 109 | if ($options instanceof Adapter) { 110 | $options = array ('adapter' => $options); 111 | } 112 | 113 | if (!is_array($options)) { 114 | throw new InvalidArgumentException('Expected an instance of Phpass\\Hash\\Adapter or an associative array of options.'); 115 | } 116 | 117 | $this->setOptions($options); 118 | } 119 | 120 | /** 121 | * Set the adapter to use for hashing strings. 122 | * 123 | * @param Adapter $adapter 124 | * An instance of a class implementing the Adapter interface. 125 | * @return Hash 126 | */ 127 | public function setAdapter(Adapter $adapter) 128 | { 129 | $this->_adapter = $adapter; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Retrieve the adapter used for hashing strings. 136 | * 137 | * @return Adapter 138 | */ 139 | public function getAdapter() 140 | { 141 | return $this->_adapter; 142 | } 143 | 144 | /** 145 | * Set options. 146 | * 147 | *
148 | *
adapter
149 | *
Instance of a class implementing the Adapter interface.
150 | *
hmacKey
151 | *
Shared secret key used for generating the HMAC variant of the 152 | * string.
153 | *
hmacAlgo
154 | *
Name of selected hashing algorithm. See \hmac_algos() for a list 155 | * of supported algorithms.
156 | *
157 | * 158 | * @param Array $options 159 | * An associative array of options. 160 | * @return Hash 161 | * @throws RuntimeException 162 | * A RuntimeException is thrown if HMAC options are passed in, but the 163 | * hash extension is not loaded. 164 | * @throws InvalidArgumentException 165 | * An InvalidArgumentException is thrown if a value does not match what 166 | * is expected for the option key. 167 | */ 168 | public function setOptions(Array $options) 169 | { 170 | $options = array_change_key_case($options, CASE_LOWER); 171 | if (array_key_exists('hmackey', $options) || array_key_exists('hmacalgo', $options)) { 172 | if (!extension_loaded('hash')) { 173 | throw new RuntimeException("Required extension 'hash' is not loaded."); 174 | } 175 | } 176 | 177 | foreach ($options as $option => $value) { 178 | switch ($option) { 179 | case 'adapter': 180 | if (!$value instanceof Adapter) { 181 | throw new InvalidArgumentException("Value of key 'adapter' must be an instance of Phpass\\Hash\\Adapter."); 182 | } 183 | $this->setAdapter($value); 184 | break; 185 | case 'hmackey': 186 | $this->_hmacKey = (string) $value; 187 | break; 188 | case 'hmacalgo': 189 | if (!in_array($value, hash_algos())) { 190 | throw new InvalidArgumentException("Given hash algorithm '${value}' is not supported by this system."); 191 | } 192 | $this->_hmacAlgo = $value; 193 | break; 194 | default: 195 | break; 196 | } 197 | } 198 | 199 | return $this; 200 | } 201 | 202 | /** 203 | * Check if a string matches a given hash value. 204 | * 205 | * @param string $password 206 | * The string to check. 207 | * @param string $storedHash 208 | * The hash string to check against. 209 | * @return boolean 210 | * Returns true if the string matches the hash string, and false 211 | * otherwise. 212 | */ 213 | public function checkPassword($password, $storedHash) 214 | { 215 | $hash = $this->_crypt($password, $storedHash); 216 | 217 | return ($hash == $storedHash); 218 | } 219 | 220 | /** 221 | * Return a hashed string using the configured adapter. 222 | * 223 | * @param string $password 224 | * The string to be hashed. 225 | * @return string 226 | * Returns the hashed string. 227 | */ 228 | public function hashPassword($password) 229 | { 230 | return $this->_crypt($password); 231 | } 232 | 233 | /** 234 | * Return a hashed string, optionally using a pre-calculated salt. 235 | * 236 | * If Hash::$_hmacKey is set, this method will generate the HMAC hash of 237 | * the password string before passing the value to the adapter. 238 | * 239 | * @param string $password 240 | * The string to be hashed. 241 | * @param string $salt 242 | * An optional salt string to base the hashing on. If not provided, the 243 | * adapter will generate a new secure salt value. 244 | * @return string 245 | * Returns the hashed string. 246 | */ 247 | protected function _crypt($password, $salt = null) 248 | { 249 | if (isset($this->_hmacKey)) { 250 | $password = hash_hmac($this->_hmacAlgo, $password, $this->_hmacKey); 251 | } 252 | $adapter = $this->getAdapter(); 253 | $hash = $adapter->crypt($password, $salt); 254 | 255 | return $hash; 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/Phpass/Hash/Adapter/Pbkdf2.php: -------------------------------------------------------------------------------- 1 | 8 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 9 | * @link https://github.com/rchouinard/phpass Project at GitHub 10 | */ 11 | 12 | namespace Phpass\Hash\Adapter; 13 | 14 | use Phpass\Exception\InvalidArgumentException; 15 | use Phpass\Exception\RuntimeException; 16 | 17 | /** 18 | * PBKDF2 hash adapter 19 | * 20 | * @package PHPass\Hashes 21 | * @category Cryptography 22 | * @author Ryan Chouinard 23 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 24 | * @link https://github.com/rchouinard/phpass Project at GitHub 25 | */ 26 | class Pbkdf2 extends Base 27 | { 28 | 29 | const DIGEST_SHA1 = 'sha1'; 30 | const DIGEST_SHA256 = 'sha256'; 31 | const DIGEST_SHA512 = 'sha512'; 32 | 33 | /** 34 | * Hashing algorithm used by the PBKDF2 implementation. 35 | * 36 | * @var string 37 | */ 38 | protected $_algo = self::DIGEST_SHA512; 39 | 40 | /** 41 | * Cost value used to generate new hash values. 42 | * 43 | * @var integer 44 | */ 45 | protected $_iterationCount = 12000; 46 | 47 | /** 48 | * Return a hashed string. 49 | * 50 | * @param string $password 51 | * The string to be hashed. 52 | * @param string $salt 53 | * An optional salt string to base the hashing on. If not provided, a 54 | * suitable string is generated by the adapter. 55 | * @return string 56 | * Returns the hashed string. On failure, a standard crypt error string 57 | * is returned which is guaranteed to differ from the salt. 58 | * @throws RuntimeException 59 | * A RuntimeException is thrown on failure if 60 | * self::$_throwExceptionOnFailure is true. 61 | */ 62 | public function crypt($password, $salt = null) 63 | { 64 | if (!$salt) { 65 | $salt = $this->genSalt(); 66 | } 67 | 68 | $hash = '*0'; 69 | if ($this->verify($salt)) { 70 | $matches = array (); 71 | preg_match('/^\$pbkdf2(?:-(?Psha256|sha512))?\$(?P\d+)\$(?P[\.\/0-9A-Za-z]{0,1366})\$?/', $salt, $matches); 72 | if ($matches['digest'] == '') { 73 | $matches['digest'] = $matches[1] = self::DIGEST_SHA1; 74 | } 75 | 76 | $keySize = 64; 77 | if ($matches['digest'] == self::DIGEST_SHA256) { 78 | $keySize = 32; 79 | } elseif ($matches['digest'] == self::DIGEST_SHA1) { 80 | $keySize = 20; 81 | } 82 | 83 | $salt = ''; 84 | if ($matches['salt'] != '') { 85 | $salt = str_replace('.', '+', $matches['salt']); 86 | switch (strlen($salt) & 0x03) { 87 | case 0: 88 | $salt = base64_decode($salt); 89 | break; 90 | case 2: 91 | $salt = base64_decode($salt . '=='); 92 | break; 93 | case 3: 94 | $salt = base64_decode($salt . '='); 95 | break; 96 | default: 97 | return $hash; 98 | } 99 | } 100 | 101 | $checksum = $this->_pbkdf2($password, $salt, $matches['rounds'], $keySize, $matches['digest']); 102 | $hash = '$pbkdf2'; 103 | if ($matches['digest'] != self::DIGEST_SHA1) { 104 | $hash .= '-' . $matches['digest']; 105 | } 106 | $hash .= '$' . $matches['rounds'] . '$' . 107 | str_replace(array ('+', '=', "\n"), array ('.', '', ''), base64_encode($salt)) . '$' . 108 | str_replace(array ('+', '=', "\n"), array ('.', '', ''), base64_encode($checksum)); 109 | } 110 | 111 | if (!$this->verifyHash($hash)) { 112 | $hash = ($salt != '*0') ? '*0' : '*1'; 113 | if ($this->_throwExceptionOnFailure) { 114 | throw new RuntimeException('Failed generating a valid hash', $hash); 115 | } 116 | } 117 | 118 | return $hash; 119 | } 120 | 121 | /** 122 | * Generate a salt string compatible with this adapter. 123 | * 124 | * @param string $input 125 | * Optional random 48-bit string to use when generating the salt. 126 | * @return string 127 | * Returns the generated salt string. 128 | */ 129 | public function genSalt($input = null) 130 | { 131 | if (!$input) { 132 | $input = $this->_getRandomBytes(16); 133 | } 134 | 135 | $identifier = 'pbkdf2'; 136 | if ($this->_algo === self::DIGEST_SHA256 || $this->_algo === self::DIGEST_SHA512) { 137 | $identifier .= '-' . $this->_algo; 138 | } 139 | 140 | $count = min(max($this->_iterationCount, 1), 4294967296); 141 | 142 | $salt = str_replace(array ('+', '=', "\n"), array ('.', '', ''), base64_encode($input)); 143 | 144 | // $pbkdf2-$$$ 145 | return '$' . $identifier . '$' . $count . '$' . $salt . '$'; 146 | } 147 | 148 | /** 149 | * Set adapter options. 150 | * 151 | * Expects an associative array of option keys and values used to configure 152 | * this adapter. 153 | * 154 | *
155 | *
digest
156 | *
Hash digest to use when calculating the checksum. Must be one 157 | * of sha1, sha256, or sha512. Defaults to sha512.
158 | *
iterationCount
159 | *
Iteration count for the underlying PBKDF2 hashing algorithm. 160 | * Must be in range 1 - 4294967296. Defaults to 12000.
161 | *
162 | * 163 | * @param Array $options 164 | * Associative array of adapter options. 165 | * @return self 166 | * Returns an instance of self to support method chaining. 167 | * @throws InvalidArgumentException 168 | * Throws an InvalidArgumentException if a provided option key contains 169 | * an invalid value. 170 | * @see Base::setOptions() 171 | */ 172 | public function setOptions(Array $options) 173 | { 174 | parent::setOptions($options); 175 | $options = array_change_key_case($options, CASE_LOWER); 176 | 177 | foreach ($options as $key => $value) { 178 | switch ($key) { 179 | case 'digest': 180 | $value = strtolower($value); 181 | if (!in_array($value, array (self::DIGEST_SHA1, self::DIGEST_SHA256, self::DIGEST_SHA512))) { 182 | throw new InvalidArgumentException('Digest must be one of sha1, sha256, or sha512'); 183 | } 184 | $this->_algo = $value; 185 | break; 186 | case 'iterationcount': 187 | if ($value < 1 || $value > 4294967296) { 188 | throw new InvalidArgumentException('Iteration count must be between 1 and 4294967296'); 189 | } 190 | $this->_iterationCount = $value; 191 | break; 192 | default: 193 | break; 194 | } 195 | } 196 | 197 | return $this; 198 | } 199 | 200 | /** 201 | * Check if a hash string is valid for the current adapter. 202 | * 203 | * @since 2.1.0 204 | * @param string $input 205 | * Hash string to verify. 206 | * @return boolean 207 | * Returns true if the input string is a valid hash value, false 208 | * otherwise. 209 | */ 210 | public function verifyHash($input) 211 | { 212 | return ($this->verifySalt($input) && 1 === preg_match('/^\$pbkdf2(?:-(?Psha256|sha512))?\$(?P\d+)\$(?P[\.\/0-9A-Za-z]{0,1366})\$(?P[\.\/0-9A-Za-z]{27,86})$/', $input)); 213 | } 214 | 215 | /** 216 | * Check if a salt string is valid for the current adapter. 217 | * 218 | * @since 2.1.0 219 | * @param string $input 220 | * Salt string to verify. 221 | * @return boolean 222 | * Returns true if the input string is a valid salt value, false 223 | * otherwise. 224 | */ 225 | public function verifySalt($input) 226 | { 227 | $valid = false; 228 | $matches = array (); 229 | if (1 === preg_match('/^\$pbkdf2(?:-(?Psha256|sha512))?\$(?P\d+)\$(?P[\.\/0-9A-Za-z]{0,1366})\$?/', $input, $matches)) { 230 | $digest = $matches['digest'] ?: self::DIGEST_SHA1; 231 | $rounds = $matches['rounds']; 232 | $salt = $matches['salt']; 233 | 234 | $digestValid = false; 235 | if (in_array($digest, array (self::DIGEST_SHA1, self::DIGEST_SHA256, self::DIGEST_SHA512))) { 236 | $digestValid = true; 237 | } 238 | 239 | $roundsValid = false; 240 | if ($rounds[0] != '0' && $rounds >= 1 && $rounds <= 4294967296) { 241 | $roundsValid = true; 242 | } 243 | 244 | if ($digestValid && $roundsValid) { 245 | $valid = true; 246 | } 247 | } 248 | 249 | return $valid; 250 | } 251 | 252 | /** 253 | * Internal implementation of PKCS #5 v2.0. 254 | * 255 | * This implementation passes tests using vectors given in RFC 6070 s.2, 256 | * PBKDF2 HMAC-SHA1 Test Vectors. Vectors given for PBKDF2 HMAC-SHA2 at 257 | * http://stackoverflow.com/questions/5130513 also pass. 258 | * 259 | * @param string $password 260 | * The string to be hashed. 261 | * @param string $salt 262 | * Salt value used by the HMAC function. 263 | * @param integer $iterationCount 264 | * Number of iterations for key stretching. 265 | * @param integer $keyLength 266 | * Length of derived key. 267 | * @param string $algo 268 | * Algorithm to use when generating HMAC digest. 269 | * @return string 270 | * Returns the raw hash string. 271 | */ 272 | protected function _pbkdf2($password, $salt, $iterationCount = 1000, $keyLength = 20, $algo = 'sha1') 273 | { 274 | $hashLength = strlen(hash($algo, null, true)); 275 | $keyBlocks = ceil($keyLength / $hashLength); 276 | $derivedKey = ''; 277 | 278 | for ($block = 1; $block <= $keyBlocks; ++$block) { 279 | $iteratedBlock = $currentBlock = hash_hmac($algo, $salt . pack('N', $block), $password, true); 280 | for ($iteration = 1; $iteration < $iterationCount; ++$iteration) { 281 | $iteratedBlock ^= $currentBlock = hash_hmac($algo, $currentBlock, $password, true); 282 | } 283 | 284 | $derivedKey .= $iteratedBlock; 285 | } 286 | 287 | return substr($derivedKey, 0, $keyLength); 288 | } 289 | 290 | } 291 | --------------------------------------------------------------------------------