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