├── .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 |
--------------------------------------------------------------------------------