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-1 Crypt Module
20: *
21: * This module provides the SHA-1 Crypt algorithm which was introduced by
22: * NetBSD in 2004.
23: *
24: * Supported parameters:
25: *
26: * <ul>
27: * <li><b>rounds:</b> Optional number of rounds to use. Must be an integer
28: * between 1 and 4294967295 inclusive. Defaults to 40000.</li>
29: *
30: * <li><b>salt:</b> Optional salt string. If provided, it must be a string
31: * 0 - 64 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 SHA1Crypt 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' => 40000,
56: 'salt' => Utilities::encode64(Utilities::genRandomBytes(6)),
57: );
58: $config = array_merge($defaults, array_change_key_case($config, CASE_LOWER));
59:
60: $string = '*1';
61: if (self::validateOptions($config)) {
62: $string = sprintf('$sha1$%d$%s', $config['rounds'], $config['salt']);
63: }
64:
65: return $string;
66: }
67:
68: /**
69: * Parse a config string into an array.
70: *
71: * @param string $config Configuration string.
72: * @return array Array of configuration options or false on failure.
73: */
74: public static function parseConfig($config)
75: {
76: $options = false;
77: $matches = array ();
78: if (preg_match('/^\$sha1\$(\d+)\$([\.\/0-9A-Za-z]{0,64})\$?/', $config, $matches)) {
79: $options = array (
80: 'rounds' => (int) $matches[1],
81: 'salt' => $matches[2],
82: );
83:
84: try {
85: self::validateOptions($options);
86: } catch (InvalidArgumentException $e) {
87: $options = false;
88: }
89: }
90:
91: return $options;
92: }
93:
94: /**
95: * Generate a password hash using a config string.
96: *
97: * @param string $password Password string.
98: * @param string $config Configuration string.
99: * @return string Returns the hash string on success. On failure, one of
100: * *0 or *1 is returned.
101: */
102: public static function genHash($password, $config)
103: {
104: $hash = ($config == '*0') ? '*1' : '*0';
105:
106: $config = self::parseConfig($config);
107: if (is_array($config)) {
108: $rounds = $config['rounds'];
109: $checksum = hash_hmac('sha1', $config['salt'] . '$sha1$' . $rounds--, $password, true);
110: if ($rounds) {
111: do {
112: $checksum = hash_hmac('sha1', $checksum, $password, true);
113: } while (--$rounds);
114: }
115:
116: $tmp = '';
117: 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) {
118: $tmp .= $checksum[$offset];
119: }
120: $checksum = Utilities::encode64($tmp);
121:
122: $hash = self::genConfig($config) . '$' . $checksum;
123: }
124:
125: return $hash;
126: }
127:
128: /**
129: * Generate a password hash using a config string or array.
130: *
131: * @param string $password Password string.
132: * @param string|array $config Optional config string or array of options.
133: * @return string Returns the hash string on success. On failure, one of
134: * *0 or *1 is returned.
135: * @throws InvalidArgumentException Throws an InvalidArgumentException if
136: * any passed-in configuration options are invalid.
137: */
138: public static function hash($password, $config = array ())
139: {
140: if (is_array($config)) {
141: $config = self::genConfig($config);
142: }
143:
144: return self::genHash($password, $config);
145: }
146:
147: /**
148: * Verify a password against a hash string.
149: *
150: * @param string $password Password string.
151: * @param string $hash Hash string.
152: * @return boolean Returns true if the password matches, false otherwise.
153: */
154: public static function verify($password, $hash)
155: {
156: return ($hash === self::hash($password, $hash));
157: }
158:
159: /**
160: * @param array $options
161: * @return boolean
162: * @throws InvalidArgumentException
163: */
164: protected static function validateOptions(array $options)
165: {
166: $options = array_change_key_case($options, CASE_LOWER);
167: foreach ($options as $option => $value) switch ($option) {
168:
169: case 'rounds':
170: if ($value < 1 || $value > 4294967295) {
171: throw new InvalidArgumentException('Invalid rounds parameter');
172: }
173: break;
174:
175: case 'salt':
176: if (!preg_match('/^[\.\/0-9A-Za-z]{0,64}$/', $value)) {
177: throw new InvalidArgumentException('Invalid salt parameter');
178: }
179: break;
180:
181: default:
182: break;
183:
184: }
185:
186: return true;
187: }
188:
189: }
190: