New onsite patient portal, take 4.
[openemr.git] / portal / patient / fwk / libs / util / password.php
blob9e51ff1e39e5cf2e0a5e680959eb3890448d3eeb
1 <?php
2 /**
3 * This is a compatibility file for password_hash and password_verify for
4 * php systems prior to 5.5 that do not have password hashing built-in.
5 * This will export these two functions to the global namespace
6 */
7 defined ( 'PASSWORD_BCRYPT' ) or define ( 'PASSWORD_BCRYPT', 1 );
9 defined ( 'PASSWORD_DEFAULT' ) or define ( 'PASSWORD_DEFAULT', PASSWORD_BCRYPT );
11 if (! function_exists ( 'password_hash' )) {
12 /**
13 * Hash the password using the specified algorithm
15 * @param string $password
16 * The password to hash
17 * @param int $algo
18 * The algorithm to use (Defined by PASSWORD_* constants)
19 * @param array $options
20 * The options for the algorithm to use
22 * @return s string|false The hashed password, or false on error.
24 function password_hash($password, $algo, $options = array()) {
25 if (! function_exists ( 'crypt' )) {
26 trigger_error ( "Crypt must be loaded for password_hash to function", E_USER_WARNING );
27 return null;
29 if (! is_string ( $password )) {
30 trigger_error ( "password_hash(): Password must be a string", E_USER_WARNING );
31 return null;
33 if (! is_int ( $algo )) {
34 trigger_error ( "password_hash() expects parameter 2 to be long, " . gettype ( $algo ) . " given", E_USER_WARNING );
35 return null;
37 switch ($algo) {
38 case PASSWORD_BCRYPT :
39 // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
40 $cost = 10;
41 if (isset ( $options ['cost'] )) {
42 $cost = $options ['cost'];
43 if ($cost < 4 || $cost > 31) {
44 trigger_error ( sprintf ( "password_hash(): Invalid bcrypt cost parameter specified: %d", $cost ), E_USER_WARNING );
45 return null;
48 $required_salt_len = 22;
49 $hash_format = sprintf ( "$2y$%02d$", $cost );
50 break;
51 default :
52 trigger_error ( sprintf ( "password_hash(): Unknown password hashing algorithm: %s", $algo ), E_USER_WARNING );
53 return null;
55 if (isset ( $options ['salt'] )) {
56 switch (gettype ( $options ['salt'] )) {
57 case 'NULL' :
58 case 'boolean' :
59 case 'integer' :
60 case 'double' :
61 case 'string' :
62 $salt = ( string ) $options ['salt'];
63 break;
64 case 'object' :
65 if (method_exists ( $options ['salt'], '__tostring' )) {
66 $salt = ( string ) $options ['salt'];
67 break;
69 case 'array' :
70 case 'resource' :
71 default :
72 trigger_error ( 'password_hash(): Non-string salt parameter supplied', E_USER_WARNING );
73 return null;
75 if (strlen ( $salt ) < $required_salt_len) {
76 trigger_error ( sprintf ( "password_hash(): Provided salt is too short: %d expecting %d", strlen ( $salt ), $required_salt_len ), E_USER_WARNING );
77 return null;
78 } elseif (0 == preg_match ( '#^[a-zA-Z0-9./]+$#D', $salt )) {
79 $salt = str_replace ( '+', '.', base64_encode ( $salt ) );
81 } else {
82 $salt = __password_make_salt ( $required_salt_len );
84 $salt = substr ( $salt, 0, $required_salt_len );
86 $hash = $hash_format . $salt;
88 $ret = crypt ( $password, $hash );
90 if (! is_string ( $ret ) || strlen ( $ret ) < 13) {
91 return false;
94 return $ret;
98 if (! function_exists ( 'password_get_info' )) {
99 /**
100 * Get information about the password hash.
101 * Returns an array of the information
102 * that was used to generate the password hash.
104 * array(
105 * 'algo' => 1,
106 * 'algoName' => 'bcrypt',
107 * 'options' => array(
108 * 'cost' => 10,
109 * ),
112 * @param string $hash
113 * The password hash to extract info from
115 * @return array The array of information about the hash.
117 function password_get_info($hash) {
118 $return = array (
119 'algo' => 0,
120 'algoName' => 'unknown',
121 'options' => array ()
123 if (substr ( $hash, 0, 4 ) == '$2y$' && strlen ( $hash ) == 60) {
124 $return ['algo'] = PASSWORD_BCRYPT;
125 $return ['algoName'] = 'bcrypt';
126 list ( $cost ) = sscanf ( $hash, "$2y$%d$" );
127 $return ['options'] ['cost'] = $cost;
129 return $return;
133 if (! function_exists ( 'password_needs_rehash' )) {
135 * Determine if the password hash needs to be rehashed according to the options provided
137 * If the answer is true, after validating the password using password_verify, rehash it.
139 * @param string $hash
140 * The hash to test
141 * @param int $algo
142 * The algorithm used for new password hashes
143 * @param array $options
144 * The options array passed to password_hash
146 * @return boolean True if the password needs to be rehashed.
148 function password_needs_rehash($hash, $algo, array $options = array()) {
149 $info = password_get_info ( $hash );
150 if ($info ['algo'] != $algo) {
151 return true;
153 switch ($algo) {
154 case PASSWORD_BCRYPT :
155 $cost = isset ( $options ['cost'] ) ? $options ['cost'] : 10;
156 if ($cost != $info ['options'] ['cost']) {
157 return true;
159 break;
161 return false;
165 if (! function_exists ( 'password_verify' )) {
167 * Verify a password against a hash using a timing attack resistant approach
169 * @param string $password
170 * The password to verify
171 * @param string $hash
172 * The hash to verify against
174 * @return boolean If the password matches the hash
176 function password_verify($password, $hash) {
177 if (! function_exists ( 'crypt' )) {
178 trigger_error ( "Crypt must be loaded for password_create to function", E_USER_WARNING );
179 return false;
181 $ret = crypt ( $password, $hash );
182 if (! is_string ( $ret ) || strlen ( $ret ) != strlen ( $hash )) {
183 return false;
186 $status = 0;
187 for($i = 0; $i < strlen ( $ret ); $i ++) {
188 $status |= (ord ( $ret [$i] ) ^ ord ( $hash [$i] ));
191 return $status === 0;
196 * Function to make a salt
198 * DO NOT USE THIS FUNCTION DIRECTLY
200 * @internal
203 function __password_make_salt($length) {
204 if ($length <= 0) {
205 trigger_error ( sprintf ( "Length cannot be less than or equal zero: %d", $length ), E_USER_WARNING );
206 return false;
208 $buffer = '';
209 $raw_length = ( int ) ($length * 3 / 4 + 1);
210 $buffer_valid = false;
211 if (function_exists ( 'mcrypt_create_iv' )) {
212 $buffer = mcrypt_create_iv ( $raw_length, MCRYPT_DEV_URANDOM );
213 if ($buffer) {
214 $buffer_valid = true;
217 if (! $buffer_valid && function_exists ( 'openssl_random_pseudo_bytes' )) {
218 $buffer = openssl_random_pseudo_bytes ( $raw_length );
219 if ($buffer) {
220 $buffer_valid = true;
223 if (! $buffer_valid && file_exists ( '/dev/urandom' )) {
224 $f = @fopen ( '/dev/urandom', 'r' );
225 if ($f) {
226 $read = strlen ( $buffer );
227 while ( $read < $raw_length ) {
228 $buffer .= fread ( $f, $raw_length - $read );
229 $read = strlen ( $buffer );
231 fclose ( $f );
232 if ($read >= $raw_length) {
233 $buffer_valid = true;
237 if (! $buffer_valid) {
238 for($i = 0; $i < $raw_length; $i ++) {
239 $buffer .= chr ( mt_rand ( 0, 255 ) );
242 $buffer = str_replace ( '+', '.', base64_encode ( $buffer ) );
243 return substr ( $buffer, 0, $length );