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:
18: /**
19: * SHA-256 Crypt Module
20: *
21: * The SHA-256 Crypt algorithm was designed to replace MD5 Crypt by Ulrich
22: * Drepper in 2008. SHA-256 Crypt is recommended for new applications.
23: *
24: * Supported parameters:
25: *
26: * <ul>
27: * <li><b>rounds:</b> Optional number of rounds to use. Must be an integer
28: * between 1000 and 999999999 inclusive. Defaults to 80000.</li>
29: *
30: * <li><b>salt:</b> Optional salt string. If provided, it must be a string
31: * 0 - 16 characters in length, containing only characters in the regex range
32: * [./0-9A-Za-z]. It is highly recommended that this parameter be left blank,
33: * in which case the library will generate a suitable salt for you.</li>
34: * </ul>
35: *
36: * @package PHPassLib\Hashes
37: * @author Ryan Chouinard <rchouinard@gmail.com>
38: * @copyright Copyright (c) 2012, Ryan Chouinard
39: * @license MIT License - http://www.opensource.org/licenses/mit-license.php
40: */
41: class SHA256Crypt implements Hash
42: {
43:
44: /**
45: * Generate a config string from an array.
46: *
47: * @param array $config Array of configuration options.
48: * @return string Configuration string.
49: * @throws InvalidArgumentException Throws an InvalidArgumentException if
50: * any passed-in configuration options are invalid.
51: */
52: public static function genConfig(array $config = array ())
53: {
54: $defaults = array (
55: 'rounds' => 80000,
56: 'salt' => Utilities::encode64(Utilities::genRandomBytes(12)),
57: );
58: $config = array_merge($defaults, array_change_key_case($config, CASE_LOWER));
59:
60: $string = '*1';
61: if (self::validateOptions($config)) {
62: $rounds = '';
63: if ($config['rounds'] != 5000) {
64: $rounds = sprintf('rounds=%d$', $config['rounds']);
65: }
66:
67: $string = sprintf('$5$%s%s', $rounds, $config['salt']);
68: }
69:
70: return $string;
71: }
72:
73: /**
74: * Parse a config string into an array.
75: *
76: * @param string $config Configuration string.
77: * @return array Array of configuration options or false on failure.
78: */
79: public static function parseConfig($config)
80: {
81: // Cheat because regex is hard :-)
82: if (strpos($config, 'rounds=') === false) {
83: $config = str_replace('$5$', '$5$rounds=5000$', $config);
84: }
85:
86: $options = false;
87: $matches = array ();
88: if (preg_match('/^\$5\$rounds=(\d{4,9})\$([\.\/0-9A-Za-z]{0,16})\$?/', $config, $matches)) {
89: $options = array (
90: 'rounds' => (int) $matches[1],
91: 'salt' => $matches[2],
92: );
93:
94: try {
95: self::validateOptions($options);
96: } catch (InvalidArgumentException $e) {
97: $options = false;
98: }
99: }
100:
101: return $options;
102: }
103:
104: /**
105: * Generate a password hash using a config string.
106: *
107: * @param string $password Password string.
108: * @param string $config Configuration string.
109: * @return string Returns the hash string on success. On failure, one of
110: * *0 or *1 is returned.
111: */
112: public static function genHash($password, $config)
113: {
114: $hash = crypt($password, $config);
115: if (!preg_match('/^\$5\$(?:rounds=\d{4,9}\$)?[\.\/0-9A-Za-z]{0,16}\$[\.\/0-9A-Za-z]{43}$/', $hash)) {
116: $hash = ($config == '*0') ? '*1' : '*0';
117: }
118:
119: return $hash;
120: }
121:
122: /**
123: * Generate a password hash using a config string or array.
124: *
125: * @param string $password Password string.
126: * @param string|array $config Optional config string or array of options.
127: * @return string Returns the hash string on success. On failure, one of
128: * *0 or *1 is returned.
129: * @throws InvalidArgumentException Throws an InvalidArgumentException if
130: * any passed-in configuration options are invalid.
131: */
132: public static function hash($password, $config = array ())
133: {
134: if (is_array($config)) {
135: $config = self::genConfig($config);
136: }
137:
138: return self::genHash($password, $config);
139: }
140:
141: /**
142: * Verify a password against a hash string.
143: *
144: * @param string $password Password string.
145: * @param string $hash Hash string.
146: * @return boolean Returns true if the password matches, false otherwise.
147: */
148: public static function verify($password, $hash)
149: {
150: return ($hash === self::hash($password, $hash));
151: }
152:
153: /**
154: * @param array $options
155: * @return boolean
156: * @throws InvalidArgumentException
157: */
158: protected static function validateOptions(array $options)
159: {
160: $options = array_change_key_case($options, CASE_LOWER);
161: foreach ($options as $option => $value) switch ($option) {
162:
163: case 'rounds':
164: if ($value < 1000 || $value > 999999999) {
165: throw new InvalidArgumentException('Invalid rounds parameter');
166: }
167: break;
168:
169: case 'salt':
170: if (!preg_match('/^[\.\/0-9A-Za-z]{0,16}$/', $value)) {
171: throw new InvalidArgumentException('Invalid salt parameter');
172: }
173: break;
174:
175: default:
176: break;
177:
178: }
179:
180: return true;
181: }
182:
183: }
184: