/library/Phpass/Hash/Adapter/Pbkdf2.php
PHP | 159 lines | 57 code | 21 blank | 81 comment | 12 complexity | f518519fc537e956e3138fa15fff9e4c MD5 | raw file
1<?php
2/**
3 * PHP Password Library
4 *
5 * @package PHPass\Hashes
6 * @category Cryptography
7 * @author Ryan Chouinard <rchouinard at gmail.com>
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/**
13 * @namespace
14 */
15namespace Phpass\Hash\Adapter;
16
17/**
18 * PBKDF2 hash adapter
19 *
20 * @package PHPass\Hashes
21 * @category Cryptography
22 * @author Ryan Chouinard <rchouinard at gmail.com>
23 * @license http://www.opensource.org/licenses/mit-license.html MIT License
24 * @link https://github.com/rchouinard/phpass Project at GitHub
25 */
26class Pbkdf2 extends Base
27{
28
29 /**
30 * Hashing algorithm used by the PBKDF2 implementation.
31 *
32 * @var string
33 */
34 protected $_algo = 'sha256';
35
36 /**
37 * Return a hashed string.
38 *
39 * @param string $password
40 * The string to be hashed.
41 * @param string $salt
42 * An optional salt string to base the hashing on. If not provided, the
43 * adapter will generate a new secure salt value.
44 * @return string
45 * Returns the hashed string.
46 * @see Adapter::crypt()
47 */
48 public function crypt($password, $salt = null)
49 {
50 $setting = $salt;
51 if (!$setting) {
52 $setting = $this->genSalt();
53 }
54
55 // Return blowfish error string *0 or *1 on failure
56 // Portable adapter does this, so we do it here to remain consistent
57 $output = '*0';
58 if (substr($setting, 0, 2) == $output) {
59 $output = '*1';
60 }
61
62 if (substr($setting, 0, 6) != '$p5v2$') {
63 return $output;
64 }
65
66 $countLog2 = $countLog2 = strpos($this->_itoa64, $setting[6]);
67 if ($countLog2 < 0 || $countLog2 > 30) {
68 return $output;
69 }
70 $count = 1 << $countLog2;
71
72 $salt = substr($setting, 7, 8);
73 if (strlen($salt) != 8) {
74 return $output;
75 }
76
77 $hash = $this->_pbkdf2($password, $salt, $count, 24, $this->_algo);
78
79 $output = substr($setting, 0, 16);
80 $output .= $this->_encode64($hash, 24);
81
82 return $output;
83 }
84
85 /**
86 * Generate a salt string suitable for the crypt() method.
87 *
88 * Pbkdf2::genSalt() generates a 16-character salt string which can be
89 * passed to crypt(). The salt consists of a string beginning with a
90 * compatible hash identifier, one byte of iteration count, and an
91 * 8-byte encoded salt followed by "$".
92 *
93 * @param string $input
94 * Optional random data to be used when generating the salt. Must contain
95 * at least 6 bytes of data.
96 * @return string
97 * Returns the generated salt string.
98 * @see Adapter::genSalt()
99 */
100 public function genSalt($input = null)
101 {
102 if (!$input) {
103 $input = $this->_getRandomBytes(6);
104 }
105
106 // PKCS #5, version 2
107 // Python implementation uses $p5k2$, but we're not using a compatible
108 // string. https://www.dlitz.net/software/python-pbkdf2/
109 $output = '$p5v2$';
110
111 // Iteration count between 1 and 1,073,741,824
112 $output .= $this->_itoa64[min(max($this->_iterationCountLog2, 0), 30)];
113
114 // 8-byte (64-bit) salt value, as recommended by the standard
115 $output .= $this->_encode64($input, 6);
116
117 // $p5v2$CSSSSSSSS$
118 return $output . '$';
119 }
120
121 /**
122 * Internal implementation of PKCS #5 v2.0.
123 *
124 * This implementation passes tests using vectors given in RFC 6070 s.2,
125 * PBKDF2 HMAC-SHA1 Test Vectors. Vectors given for PBKDF2 HMAC-SHA2 at
126 * http://stackoverflow.com/questions/5130513 also pass.
127 *
128 * @param string $password
129 * The string to be hashed.
130 * @param string $salt
131 * Salt value used by the HMAC function.
132 * @param integer $iterationCount
133 * Number of iterations for key stretching.
134 * @param integer $keyLength
135 * Length of derived key.
136 * @param string $algo
137 * Algorithm to use when generating HMAC digest.
138 * @return string
139 * Returns the raw hash string.
140 */
141 protected function _pbkdf2($password, $salt, $iterationCount = 1000, $keyLength = 20, $algo = 'sha1')
142 {
143 $hashLength = strlen(hash($algo, null, true));
144 $keyBlocks = ceil($keyLength / $hashLength);
145 $derivedKey = '';
146
147 for ($block = 1; $block <= $keyBlocks; ++$block) {
148 $iteratedBlock = $currentBlock = hash_hmac($algo, $salt . pack('N', $block), $password, true);
149 for ($iteration = 1; $iteration < $iterationCount; ++$iteration) {
150 $iteratedBlock ^= $currentBlock = hash_hmac($algo, $currentBlock, $password, true);
151 }
152
153 $derivedKey .= $iteratedBlock;
154 }
155
156 return substr($derivedKey, 0, $keyLength);
157 }
158
159}