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