Overview

Namespaces

  • PHP
  • PHPassLib
    • Application
    • Exception
    • Hash
    • Test
      • Application
      • Hash

Classes

  • BCrypt
  • BSDiCrypt
  • DESCrypt
  • MD5Crypt
  • PBKDF2
  • Portable
  • SHA1Crypt
  • SHA256Crypt
  • SHA512Crypt
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * PHP Password Library
  4:  *
  5:  * @package PHPassLib\Hashes
  6:  * @author Ryan Chouinard <rchouinard@gmail.com>
  7:  * @copyright Copyright (c) 2012, Ryan Chouinard
  8:  * @license MIT License - http://www.opensource.org/licenses/mit-license.php
  9:  * @version 3.0.0-dev
 10:  */
 11: 
 12: namespace PHPassLib\Hash;
 13: 
 14: use PHPassLib\Hash;
 15: use PHPassLib\Utilities;
 16: use PHPassLib\Exception\InvalidArgumentException;
 17: use PHPassLib\Exception\RuntimeException;
 18: 
 19: /**
 20:  * PBKDF2-SHA1/256/512 Module
 21:  *
 22:  * This module provides three hash schemes compatible with Python PassLib's
 23:  * pbkdf2_<digest> schemes. PBKDF2-SHA512 is recommended for new applications.
 24:  *
 25:  * See http://packages.python.org/passlib/lib/passlib.hash.pbkdf2_digest.html
 26:  * for more details about this hash scheme.
 27:  *
 28:  * The PBKDF2 specification uses the HMAC variant of the SHA-1 hash function,
 29:  * which is not vulnerable to any of the known SHA-1 weaknesses. Any of the
 30:  * three digests are perfectly safe to use.
 31:  *
 32:  * Supported parameters:
 33:  *
 34:  * <ul>
 35:  *   <li><b>digest:</b> Must be one of sha1, sha256, or sha512. Defaults to
 36:  *   sha512.</li>
 37:  *
 38:  *   <li><b>rounds:</b> Optional number of rounds to use. Must be an integer
 39:  *   between 1 and 4294967296 inclusive. Defaults to 12000.</li>
 40:  *
 41:  *   <li><b>saltSize:</b> Optional number of bytes to use when generating new
 42:  *   salts. Must be an integer between 0 and 1024 inclusive. Defaults to 16.</li>
 43:  *
 44:  *   <li><b>salt:</b> Optional salt string. If provided, it must be a string
 45:  *   between 0 and 1024 characters in length. It is highly recommended that
 46:  *   this parameter be left blank, in which case the library will generate a
 47:  *   suitable salt for you.</li>
 48:  * </ul>
 49:  *
 50:  * This module requires the <i>HASH Message Digest Framework</i> extension to
 51:  * be loaded in order to work.
 52:  *
 53:  * @package PHPassLib\Hashes
 54:  * @author Ryan Chouinard <rchouinard@gmail.com>
 55:  * @copyright Copyright (c) 2012, Ryan Chouinard
 56:  * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 57:  */
 58: class PBKDF2 implements Hash
 59: {
 60: 
 61:     const DIGEST_SHA1 = 'sha1';
 62:     const DIGEST_SHA256 = 'sha256';
 63:     const DIGEST_SHA512 = 'sha512';
 64: 
 65:     /**
 66:      * Generate a config string from an array.
 67:      *
 68:      * @param array $config Array of configuration options.
 69:      * @return string Configuration string.
 70:      * @throws InvalidArgumentException Throws an InvalidArgumentException if
 71:      *     any passed-in configuration options are invalid.
 72:      */
 73:     public static function genConfig(array $config = array ())
 74:     {
 75:         $defaults = array (
 76:             'digest' => self::DIGEST_SHA512,
 77:             'rounds' => 12000,
 78:             'salt' => null,
 79:             'saltsize' => 16,
 80:         );
 81:         $config = array_merge($defaults, array_change_key_case($config, CASE_LOWER));
 82: 
 83:         $string = '*1';
 84:         if (self::validateOptions($config)) {
 85:             // Generate a salt value if we need one
 86:             if ($config['salt'] === null && (int) $config['saltsize'] > 0) {
 87:                 $config['salt'] = self::genSalt(Utilities::genRandomBytes((int) $config['saltsize']));
 88:             }
 89: 
 90:             // pbkdf2-sha1 doesn't include the digest in the hash identifier
 91:             // We also have to treat the rounds parameter as a float, otherwise
 92:             // values above 2147483647 will wrap on 32-bit systems.
 93:             $string = str_replace('-sha1', '', sprintf('$pbkdf2-%s$%0.0f$%s', $config['digest'], $config['rounds'], $config['salt']));
 94:         }
 95: 
 96:         return $string;
 97:     }
 98: 
 99:     /**
100:      * Parse a config string into an array.
101:      *
102:      * @param string $config Configuration string.
103:      * @return array Array of configuration options or false on failure.
104:      */
105:     public static function parseConfig($config)
106:     {
107:         $options = false;
108:         $matches = array ();
109:         if (preg_match('/^\$pbkdf2(?:-(sha256|sha512))?\$(\d+)\$([\.\/0-9A-Za-z]{0,1366})\$?/', $config, $matches)) {
110:             $options = array (
111:                 'digest' => $matches[1] ?: 'sha1',
112:                 'rounds' => $matches[2],
113:                 'salt' => $matches[3],
114:                 'saltSize' => $matches[3] ? strlen(Utilities::altBase64Decode($matches[3])) : 0,
115:             );
116: 
117:             try {
118:                 self::validateOptions($options);
119:             } catch (InvalidArgumentException $e) {
120:                 $options = false;
121:             }
122:         }
123: 
124:         return $options;
125:     }
126: 
127:     /**
128:      * Generate a password hash using a config string.
129:      *
130:      * @param string $password Password string.
131:      * @param string $config Configuration string.
132:      * @return string Returns the hash string on success. On failure, one of
133:      *     *0 or *1 is returned.
134:      * @throws RuntimeException Throws a RuntimeException if the required
135:      *     HASH Message Digest Framework is not not loaded.
136:      */
137:     public static function genHash($password, $config)
138:     {
139:         $hash = ($config == '*0') ? '*1' : '*0';
140: 
141:         $config = self::parseConfig($config);
142:         if (is_array($config)) {
143:             $keysize = 64;
144:             if ($config['digest'] == self::DIGEST_SHA256) {
145:                 $keysize = 32;
146:             } elseif ($config['digest'] == self::DIGEST_SHA1) {
147:                 $keysize = 20;
148:             }
149: 
150:             // hashPbkdf2() will throw a runtime exception if ext-hash
151:             // is not loaded.
152:             $checksum = self::hashPbkdf2($password, Utilities::altBase64Decode($config['salt']), $config['rounds'], $keysize, $config['digest']);
153:             $hash = self::genConfig($config) . '$' . Utilities::altBase64Encode($checksum);
154:         }
155: 
156:         return $hash;
157:     }
158: 
159:     /**
160:      * Generate a password hash using a config string or array.
161:      *
162:      * @param string $password Password string.
163:      * @param string|array $config Optional config string or array of options.
164:      * @return string Returns the hash string on success. On failure, one of
165:      *     *0 or *1 is returned.
166:      * @throws InvalidArgumentException Throws an InvalidArgumentException if
167:      *     any passed-in configuration options are invalid.
168:      * @throws RuntimeException Throws a RuntimeException if the required
169:      *     HASH Message Digest Framework is not not loaded.
170:      */
171:     public static function hash($password, $config = array ())
172:     {
173:         if (is_array($config)) {
174:             $config = self::genConfig($config);
175:         }
176: 
177:         return self::genHash($password, $config);
178:     }
179: 
180:     /**
181:      * Verify a password against a hash string.
182:      *
183:      * @param string $password Password string.
184:      * @param string $hash Hash string.
185:      * @return boolean Returns true if the password matches, false otherwise.
186:      */
187:     public static function verify($password, $hash)
188:     {
189:         return ($hash === self::hash($password, $hash));
190:     }
191: 
192:     /**
193:      * @param string $input
194:      * @return string
195:      */
196:     protected static function genSalt($input = null)
197:     {
198:         if (!$input) {
199:             $input = Utilities::genRandomBytes(16);
200:         }
201: 
202:         return Utilities::altBase64Encode($input);
203:     }
204: 
205:     /**
206:      * Implementation of the PBKDF2 algorithm.
207:      *
208:      * @param string $password Password string.
209:      * @param string $salt Salt string.
210:      * @param integer $rounds Number of rounds to use.
211:      * @param integer $keyLength Desired length of key.
212:      * @param string $digest Digest to use.
213:      * @return string Returns the raw byte string of the derived key.
214:      * @throws RuntimeException Throws a RuntimeException if the required
215:      *     HASH Message Digest Framework is not not loaded.
216:      */
217:     protected static function hashPbkdf2($password, $salt, $rounds = 12000, $keyLength = 64, $digest = 'sha512')
218:     {
219:         if (!extension_loaded('hash')) {
220:             throw new RuntimeException('Required extension "hash" not loaded: PBKDF2 requires the HASH Message Digest Framework.');
221:         }
222: 
223:         $hashLength = strlen(hash($digest, null, true));
224:         $keyBlocks = ceil($keyLength / $hashLength);
225:         $derivedKey = '';
226: 
227:         for ($block = 1; $block <= $keyBlocks; ++$block) {
228:             $iteratedBlock = $currentBlock = hash_hmac($digest, $salt . pack('N', $block), $password, true);
229:             for ($iteration = 1; $iteration < $rounds; ++$iteration) {
230:                 $iteratedBlock ^= $currentBlock = hash_hmac($digest, $currentBlock, $password, true);
231:             }
232: 
233:             $derivedKey .= $iteratedBlock;
234:         }
235: 
236:         return substr($derivedKey, 0, $keyLength);
237:     }
238: 
239: 
240:     /**
241:      * @param array $options
242:      * @return boolean
243:      * @throws InvalidArgumentException
244:      */
245:     protected static function validateOptions(array $options)
246:     {
247:         $options = array_change_key_case($options, CASE_LOWER);
248:         foreach ($options as $option => $value) switch ($option) {
249: 
250:             case 'digest':
251:                 if (!in_array($value, array (self::DIGEST_SHA1, self::DIGEST_SHA256, self::DIGEST_SHA512))) {
252:                     throw new InvalidArgumentException('Invalid digest parameter');
253:                 }
254:                 break;
255: 
256:             case 'rounds':
257:                 if (substr($value, 0, 1) == 0 || $value < 1 || $value > 4294967296) {
258:                     throw new InvalidArgumentException('Invalid rounds parameter');
259:                 }
260:                 break;
261: 
262:             case 'saltsize':
263:                 if ($value > 1024) {
264:                     throw new InvalidArgumentException('Invalid salt size parameter');
265:                 }
266:                 break;
267: 
268:             case 'salt':
269:                 if (!preg_match('/^[\.\/0-9A-Za-z]{0,1366}$/', $value)) {
270:                     throw new InvalidArgumentException('Invalid salt parameter');
271:                 }
272:                 break;
273: 
274:             default:
275:                 break;
276: 
277:         }
278: 
279:         return true;
280:     }
281: 
282: }
283: 
PHP Password Library API documentation generated by ApiGen 2.8.0