Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
question2answer
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
outils
question2answer
Commits
713d9618
Commit
713d9618
authored
Jan 24, 2016
by
Scott
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'pr/353' into 1.8
Use password_hash functions.
parents
3fd0654f
1442ef15
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
432 additions
and
56 deletions
+432
-56
install.php
qa-include/db/install.php
+12
-7
selects.php
qa-include/db/selects.php
+1
-1
users.php
qa-include/db/users.php
+28
-13
account.php
qa-include/pages/account.php
+24
-9
login.php
qa-include/pages/login.php
+25
-7
qa-base.php
qa-include/qa-base.php
+25
-19
password_compat.php
qa-include/vendor/password_compat.php
+317
-0
No files found.
qa-include/db/install.php
View file @
713d9618
...
...
@@ -25,7 +25,7 @@
exit
;
}
define
(
'QA_DB_VERSION_CURRENT'
,
6
1
);
define
(
'QA_DB_VERSION_CURRENT'
,
6
2
);
function
qa_db_user_column_type_verify
()
...
...
@@ -107,7 +107,8 @@
'avatarwidth'
=>
'SMALLINT UNSIGNED'
,
// pixel width of stored avatar
'avatarheight'
=>
'SMALLINT UNSIGNED'
,
// pixel height of stored avatar
'passsalt'
=>
'BINARY(16)'
,
// salt used to calculate passcheck - null if no password set for direct login
'passcheck'
=>
'BINARY(20)'
,
// checksum from password and passsalt - null if no passowrd set for direct login
'passcheck'
=>
'BINARY(20)'
,
// checksum from password and passsalt - null if no password set for direct login
'passhash'
=>
'VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL'
,
// password_hash
'level'
=>
'TINYINT UNSIGNED NOT NULL'
,
// basic, editor, admin, etc...
'loggedin'
=>
'DATETIME NOT NULL'
,
// time of last login
'loginip'
=>
'INT UNSIGNED NOT NULL'
,
// INET_ATON of IP address of last login
...
...
@@ -1430,13 +1431,13 @@
$keyrecalc
[
'dorecalcpoints'
]
=
true
;
break
;
// Up to here: Ver
is
on 1.7
// Up to here: Ver
si
on 1.7
case
59
:
// upgrade from alpha version removed
break
;
// Up to here: Ver
is
on 1.7.1
// Up to here: Ver
si
on 1.7.1
case
60
:
// add new category widget - note title must match that from qa_register_core_modules()
...
...
@@ -1459,8 +1460,13 @@
break
;
// Up to here: Verison 1.8 alpha
case
62
:
// add column to qa_users to handle new bcrypt passwords
qa_db_upgrade_query
(
'ALTER TABLE ^users ADD COLUMN passhash '
.
$definitions
[
'users'
][
'passhash'
]
.
' AFTER passcheck'
);
qa_db_upgrade_query
(
$locktablesquery
);
break
;
// Up to here: Version 1.8 alpha
}
qa_db_set_db_version
(
$newversion
);
...
...
@@ -1523,4 +1529,4 @@
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
*/
qa-include/db/selects.php
View file @
713d9618
...
...
@@ -1181,7 +1181,7 @@
{
return
array
(
'columns'
=>
array
(
'^users.userid'
,
'passsalt'
,
'passcheck'
=>
'HEX(passcheck)'
,
'email'
,
'level'
,
'emailcode'
,
'handle'
,
'^users.userid'
,
'passsalt'
,
'passcheck'
=>
'HEX(passcheck)'
,
'
passhash'
,
'
email'
,
'level'
,
'emailcode'
,
'handle'
,
'created'
=>
'UNIX_TIMESTAMP(created)'
,
'sessioncode'
,
'sessionsource'
,
'flags'
,
'loggedin'
=>
'UNIX_TIMESTAMP(loggedin)'
,
'loginip'
=>
'INET_NTOA(loginip)'
,
'written'
=>
'UNIX_TIMESTAMP(written)'
,
'writeip'
=>
'INET_NTOA(writeip)'
,
'avatarblobid'
=>
'BINARY avatarblobid'
,
// cast to BINARY due to MySQL bug which renders it signed in a union
...
...
qa-include/db/users.php
View file @
713d9618
...
...
@@ -44,13 +44,22 @@
{
require_once
QA_INCLUDE_DIR
.
'util/string.php'
;
$salt
=
isset
(
$password
)
?
qa_random_alphanum
(
16
)
:
null
;
if
(
QA_PASSWORD_HASH
)
{
qa_db_query_sub
(
'INSERT INTO ^users (created, createip, email, passhash, level, handle, loggedin, loginip) '
.
'VALUES (NOW(), COALESCE(INET_ATON($), 0), $, $, #, $, NOW(), COALESCE(INET_ATON($), 0))'
,
$ip
,
$email
,
isset
(
$password
)
?
password_hash
(
$password
,
PASSWORD_BCRYPT
)
:
null
,
(
int
)
$level
,
$handle
,
$ip
);
}
else
{
$salt
=
isset
(
$password
)
?
qa_random_alphanum
(
16
)
:
null
;
qa_db_query_sub
(
'INSERT INTO ^users (created, createip, email, passsalt, passcheck, level, handle, loggedin, loginip) '
.
'VALUES (NOW(), COALESCE(INET_ATON($), 0), $, $, UNHEX($), #, $, NOW(), COALESCE(INET_ATON($), 0))'
,
$ip
,
$email
,
$salt
,
isset
(
$password
)
?
qa_db_calc_passcheck
(
$password
,
$salt
)
:
null
,
(
int
)
$level
,
$handle
,
$ip
);
}
qa_db_query_sub
(
'INSERT INTO ^users (created, createip, email, passsalt, passcheck, level, handle, loggedin, loginip) '
.
'VALUES (NOW(), COALESCE(INET_ATON($), 0), $, $, UNHEX($), #, $, NOW(), COALESCE(INET_ATON($), 0))'
,
$ip
,
$email
,
$salt
,
isset
(
$password
)
?
qa_db_calc_passcheck
(
$password
,
$salt
)
:
null
,
(
int
)
$level
,
$handle
,
$ip
);
return
qa_db_last_insert_id
();
}
...
...
@@ -154,12 +163,19 @@
require_once
QA_INCLUDE_DIR
.
'util/string.php'
;
$salt
=
qa_random_alphanum
(
16
);
if
(
QA_PASSWORD_HASH
)
{
qa_db_query_sub
(
'UPDATE ^users SET passhash=$, passsalt=NULL, passcheck=NULL WHERE userid=$'
,
password_hash
(
$password
,
PASSWORD_BCRYPT
),
$userid
);
}
else
{
$salt
=
qa_random_alphanum
(
16
);
qa_db_query_sub
(
'UPDATE ^users SET passsalt=$, passcheck=UNHEX($) WHERE userid=$'
,
$salt
,
qa_db_calc_passcheck
(
$password
,
$salt
),
$userid
);
qa_db_query_sub
(
'UPDATE ^users SET passsalt=$, passcheck=UNHEX($) WHERE userid=$'
,
$salt
,
qa_db_calc_passcheck
(
$password
,
$salt
),
$userid
);
}
}
...
...
@@ -335,4 +351,4 @@
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
*/
qa-include/pages/account.php
View file @
713d9618
...
...
@@ -37,7 +37,7 @@
if
(
QA_FINAL_EXTERNAL_USERS
)
qa_fatal_error
(
'User accounts are handled by external code'
);
$userid
=
qa_get_logged_in_userid
();
$userid
=
qa_get_logged_in_userid
();
if
(
!
isset
(
$userid
))
qa_redirect
(
'login'
);
...
...
@@ -52,10 +52,16 @@
qa_db_userfields_selectspec
()
);
$changehandle
=
qa_opt
(
'allow_change_usernames'
)
||
((
!
$userpoints
[
'qposts'
])
&&
(
!
$userpoints
[
'aposts'
])
&&
(
!
$userpoints
[
'cposts'
]));
$doconfirms
=
qa_opt
(
'confirm_user_emails'
)
&&
(
$useraccount
[
'level'
]
<
QA_USER_LEVEL_EXPERT
);
$isconfirmed
=
(
$useraccount
[
'flags'
]
&
QA_USER_FLAGS_EMAIL_CONFIRMED
)
?
true
:
false
;
$haspassword
=
isset
(
$useraccount
[
'passsalt'
])
&&
isset
(
$useraccount
[
'passcheck'
]);
$changehandle
=
qa_opt
(
'allow_change_usernames'
)
||
(
!
$userpoints
[
'qposts'
]
&&
!
$userpoints
[
'aposts'
]
&&
!
$userpoints
[
'cposts'
]);
$doconfirms
=
qa_opt
(
'confirm_user_emails'
)
&&
$useraccount
[
'level'
]
<
QA_USER_LEVEL_EXPERT
;
$isconfirmed
=
(
$useraccount
[
'flags'
]
&
QA_USER_FLAGS_EMAIL_CONFIRMED
)
?
true
:
false
;
$haspasswordold
=
isset
(
$useraccount
[
'passsalt'
])
&&
isset
(
$useraccount
[
'passcheck'
]);
if
(
QA_PASSWORD_HASH
)
{
$haspassword
=
isset
(
$useraccount
[
'passhash'
]);
}
else
{
$haspassword
=
$haspasswordold
;
}
$permit_error
=
qa_user_permit_error
();
$isblocked
=
$permit_error
!==
false
;
$pending_confirmation
=
$doconfirms
&&
$permit_error
==
'confirm'
;
...
...
@@ -204,9 +210,18 @@
else
{
$errors
=
array
();
if
(
$haspassword
&&
(
strtolower
(
qa_db_calc_passcheck
(
$inoldpassword
,
$useraccount
[
'passsalt'
]))
!=
strtolower
(
$useraccount
[
'passcheck'
])))
$errors
[
'oldpassword'
]
=
qa_lang
(
'users/password_wrong'
);
$legacyPassError
=
strtolower
(
qa_db_calc_passcheck
(
$inoldpassword
,
$useraccount
[
'passsalt'
]))
!=
strtolower
(
$useraccount
[
'passcheck'
]);
if
(
QA_PASSWORD_HASH
)
{
$passError
=
!
password_verify
(
$inoldpassword
,
$useraccount
[
'passhash'
]);
if
((
$haspasswordold
&&
$legacyPassError
)
||
(
!
$haspasswordold
&&
$haspassword
&&
$passError
))
{
$errors
[
'oldpassword'
]
=
qa_lang
(
'users/password_wrong'
);
}
}
else
{
if
(
$haspassword
&&
$legacyPassError
)
{
$errors
[
'oldpassword'
]
=
qa_lang
(
'users/password_wrong'
);
}
}
$useraccount
[
'password'
]
=
$inoldpassword
;
$errors
=
$errors
+
qa_password_validate
(
$innewpassword1
,
$useraccount
);
// array union
...
...
@@ -463,7 +478,7 @@
),
);
if
(
!
$haspassword
)
{
if
(
!
$haspassword
&&
!
$haspasswordold
)
{
$qa_content
[
'form_password'
][
'fields'
][
'old'
][
'type'
]
=
'static'
;
$qa_content
[
'form_password'
][
'fields'
][
'old'
][
'value'
]
=
qa_lang_html
(
'users/password_none'
);
}
...
...
qa-include/pages/login.php
View file @
713d9618
...
...
@@ -68,9 +68,30 @@
$inuserid
=
$matchusers
[
0
];
$userinfo
=
qa_db_select_with_pending
(
qa_db_user_account_selectspec
(
$inuserid
,
true
));
if
(
strtolower
(
qa_db_calc_passcheck
(
$inpassword
,
$userinfo
[
'passsalt'
]))
==
strtolower
(
$userinfo
[
'passcheck'
]))
{
// login and redirect
$legacyPassOk
=
strtolower
(
qa_db_calc_passcheck
(
$inpassword
,
$userinfo
[
'passsalt'
]))
==
strtolower
(
$userinfo
[
'passcheck'
]);
if
(
QA_PASSWORD_HASH
)
{
$haspassword
=
isset
(
$userinfo
[
'passhash'
]);
$haspasswordold
=
isset
(
$userinfo
[
'passsalt'
])
&&
isset
(
$userinfo
[
'passcheck'
]);
$passOk
=
password_verify
(
$inpassword
,
$userinfo
[
'passhash'
]);
if
((
$haspasswordold
&&
$legacyPassOk
)
||
(
$haspassword
&&
$passOk
))
{
// upgrade password or rehash, when options like the cost parameter changed
if
(
$haspasswordold
||
password_needs_rehash
(
$userinfo
[
'passhash'
],
PASSWORD_BCRYPT
))
{
qa_db_user_set_password
(
$inuserid
,
$inpassword
);
}
}
else
{
$errors
[
'password'
]
=
qa_lang
(
'users/password_wrong'
);
}
}
else
{
if
(
!
$legacyPassOk
)
{
$errors
[
'password'
]
=
qa_lang
(
'users/password_wrong'
);
}
}
if
(
!
isset
(
$errors
[
'password'
]))
{
// login and redirect
require_once
QA_INCLUDE_DIR
.
'app/users.php'
;
qa_set_logged_in_user
(
$inuserid
,
$userinfo
[
'handle'
],
!
empty
(
$inremember
));
$topath
=
qa_get
(
'to'
);
...
...
@@ -81,9 +102,7 @@
qa_redirect
(
'account'
);
else
qa_redirect
(
''
);
}
else
$errors
[
'password'
]
=
qa_lang
(
'users/password_wrong'
);
}
}
else
$errors
[
'emailhandle'
]
=
qa_lang
(
'users/user_not_found'
);
...
...
@@ -174,4 +193,4 @@
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
*/
qa-include/qa-base.php
View file @
713d9618
...
...
@@ -49,25 +49,6 @@
qa_initialize_php
();
qa_initialize_constants_1
();
/**
* JSON compatibility layer for PHP 5.1
*/
if
(
!
function_exists
(
'json_encode'
)
&&
!
function_exists
(
'json_decode'
))
{
require_once
QA_INCLUDE_DIR
.
'vendor/JSON.php'
;
function
json_encode
(
$json
)
{
$service
=
new
Services_JSON
();
return
$service
->
encode
(
$json
);
}
function
json_decode
(
$json
,
$assoc
=
false
)
{
$service
=
new
Services_JSON
(
$assoc
?
SERVICES_JSON_LOOSE_TYPE
:
0
);
return
$service
->
decode
(
$json
);
}
}
if
(
defined
(
'QA_WORDPRESS_LOAD_FILE'
))
// if relevant, load WordPress integration in global scope
require_once
QA_WORDPRESS_LOAD_FILE
;
...
...
@@ -196,6 +177,31 @@
if
(
!
is_readable
(
QA_WORDPRESS_LOAD_FILE
))
qa_fatal_error
(
'Could not find wp-load.php file for WordPress integration - please check QA_WORDPRESS_INTEGRATE_PATH in qa-config.php'
);
}
// Polyfills
// JSON compatibility layer for PHP 5.1
if
(
!
function_exists
(
'json_encode'
)
&&
!
function_exists
(
'json_decode'
))
{
require_once
QA_INCLUDE_DIR
.
'vendor/JSON.php'
;
function
json_encode
(
$json
)
{
$service
=
new
Services_JSON
();
return
$service
->
encode
(
$json
);
}
function
json_decode
(
$json
,
$assoc
=
false
)
{
$service
=
new
Services_JSON
(
$assoc
?
SERVICES_JSON_LOOSE_TYPE
:
0
);
return
$service
->
decode
(
$json
);
}
}
// password_hash compatibility for 5.3-5.4
define
(
'QA_PASSWORD_HASH'
,
!
qa_php_version_below
(
'5.3.7'
));
if
(
QA_PASSWORD_HASH
)
{
require_once
QA_INCLUDE_DIR
.
'vendor/password_compat.php'
;
}
}
...
...
qa-include/vendor/password_compat.php
0 → 100644
View file @
713d9618
<?php
/**
* A Compatibility library with PHP 5.5's simplified password hashing API.
*
* @author Anthony Ferrara <ircmaxell@php.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2012 The Authors
*/
namespace
{
if
(
!
defined
(
'PASSWORD_BCRYPT'
))
{
/**
* PHPUnit Process isolation caches constants, but not function declarations.
* So we need to check if the constants are defined separately from
* the functions to enable supporting process isolation in userland
* code.
*/
define
(
'PASSWORD_BCRYPT'
,
1
);
define
(
'PASSWORD_DEFAULT'
,
PASSWORD_BCRYPT
);
define
(
'PASSWORD_BCRYPT_DEFAULT_COST'
,
10
);
}
if
(
!
function_exists
(
'password_hash'
))
{
/**
* Hash the password using the specified algorithm
*
* @param string $password The password to hash
* @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
* @param array $options The options for the algorithm to use
*
* @return string|false The hashed password, or false on error.
*/
function
password_hash
(
$password
,
$algo
,
array
$options
=
array
())
{
if
(
!
function_exists
(
'crypt'
))
{
trigger_error
(
"Crypt must be loaded for password_hash to function"
,
E_USER_WARNING
);
return
null
;
}
if
(
is_null
(
$password
)
||
is_int
(
$password
))
{
$password
=
(
string
)
$password
;
}
if
(
!
is_string
(
$password
))
{
trigger_error
(
"password_hash(): Password must be a string"
,
E_USER_WARNING
);
return
null
;
}
if
(
!
is_int
(
$algo
))
{
trigger_error
(
"password_hash() expects parameter 2 to be long, "
.
gettype
(
$algo
)
.
" given"
,
E_USER_WARNING
);
return
null
;
}
$resultLength
=
0
;
switch
(
$algo
)
{
case
PASSWORD_BCRYPT
:
$cost
=
PASSWORD_BCRYPT_DEFAULT_COST
;
if
(
isset
(
$options
[
'cost'
]))
{
$cost
=
(
int
)
$options
[
'cost'
];
if
(
$cost
<
4
||
$cost
>
31
)
{
trigger_error
(
sprintf
(
"password_hash(): Invalid bcrypt cost parameter specified: %d"
,
$cost
),
E_USER_WARNING
);
return
null
;
}
}
// The length of salt to generate
$raw_salt_len
=
16
;
// The length required in the final serialization
$required_salt_len
=
22
;
$hash_format
=
sprintf
(
"$2y$%02d$"
,
$cost
);
// The expected length of the final crypt() output
$resultLength
=
60
;
break
;
default
:
trigger_error
(
sprintf
(
"password_hash(): Unknown password hashing algorithm: %s"
,
$algo
),
E_USER_WARNING
);
return
null
;
}
$salt_req_encoding
=
false
;
if
(
isset
(
$options
[
'salt'
]))
{
switch
(
gettype
(
$options
[
'salt'
]))
{
case
'NULL'
:
case
'boolean'
:
case
'integer'
:
case
'double'
:
case
'string'
:
$salt
=
(
string
)
$options
[
'salt'
];
break
;
case
'object'
:
if
(
method_exists
(
$options
[
'salt'
],
'__tostring'
))
{
$salt
=
(
string
)
$options
[
'salt'
];
break
;
}
case
'array'
:
case
'resource'
:
default
:
trigger_error
(
'password_hash(): Non-string salt parameter supplied'
,
E_USER_WARNING
);
return
null
;
}
if
(
PasswordCompat\binary\_strlen
(
$salt
)
<
$required_salt_len
)
{
trigger_error
(
sprintf
(
"password_hash(): Provided salt is too short: %d expecting %d"
,
PasswordCompat\binary\_strlen
(
$salt
),
$required_salt_len
),
E_USER_WARNING
);
return
null
;
}
elseif
(
0
==
preg_match
(
'#^[a-zA-Z0-9./]+$#D'
,
$salt
))
{
$salt_req_encoding
=
true
;
}
}
else
{
$buffer
=
''
;
$buffer_valid
=
false
;
if
(
function_exists
(
'mcrypt_create_iv'
)
&&
!
defined
(
'PHALANGER'
))
{
$buffer
=
mcrypt_create_iv
(
$raw_salt_len
,
MCRYPT_DEV_URANDOM
);
if
(
$buffer
)
{
$buffer_valid
=
true
;
}
}
if
(
!
$buffer_valid
&&
function_exists
(
'openssl_random_pseudo_bytes'
))
{
$strong
=
false
;
$buffer
=
openssl_random_pseudo_bytes
(
$raw_salt_len
,
$strong
);
if
(
$buffer
&&
$strong
)
{
$buffer_valid
=
true
;
}
}
if
(
!
$buffer_valid
&&
@
is_readable
(
'/dev/urandom'
))
{
$file
=
fopen
(
'/dev/urandom'
,
'r'
);
$read
=
0
;
$local_buffer
=
''
;
while
(
$read
<
$raw_salt_len
)
{
$local_buffer
.=
fread
(
$file
,
$raw_salt_len
-
$read
);
$read
=
PasswordCompat\binary\_strlen
(
$local_buffer
);
}
fclose
(
$file
);
if
(
$read
>=
$raw_salt_len
)
{
$buffer_valid
=
true
;
}
$buffer
=
str_pad
(
$buffer
,
$raw_salt_len
,
"
\0
"
)
^
str_pad
(
$local_buffer
,
$raw_salt_len
,
"
\0
"
);
}
if
(
!
$buffer_valid
||
PasswordCompat\binary\_strlen
(
$buffer
)
<
$raw_salt_len
)
{
$buffer_length
=
PasswordCompat\binary\_strlen
(
$buffer
);
for
(
$i
=
0
;
$i
<
$raw_salt_len
;
$i
++
)
{
if
(
$i
<
$buffer_length
)
{
$buffer
[
$i
]
=
$buffer
[
$i
]
^
chr
(
mt_rand
(
0
,
255
));
}
else
{
$buffer
.=
chr
(
mt_rand
(
0
,
255
));
}
}
}
$salt
=
$buffer
;
$salt_req_encoding
=
true
;
}
if
(
$salt_req_encoding
)
{
// encode string with the Base64 variant used by crypt
$base64_digits
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
;
$bcrypt64_digits
=
'./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
;
$base64_string
=
base64_encode
(
$salt
);
$salt
=
strtr
(
rtrim
(
$base64_string
,
'='
),
$base64_digits
,
$bcrypt64_digits
);
}
$salt
=
PasswordCompat\binary\_substr
(
$salt
,
0
,
$required_salt_len
);
$hash
=
$hash_format
.
$salt
;
$ret
=
crypt
(
$password
,
$hash
);
if
(
!
is_string
(
$ret
)
||
PasswordCompat\binary\_strlen
(
$ret
)
!=
$resultLength
)
{
return
false
;
}
return
$ret
;
}
/**
* Get information about the password hash. Returns an array of the information
* that was used to generate the password hash.
*
* array(
* 'algo' => 1,
* 'algoName' => 'bcrypt',
* 'options' => array(
* 'cost' => PASSWORD_BCRYPT_DEFAULT_COST,
* ),
* )
*
* @param string $hash The password hash to extract info from
*
* @return array The array of information about the hash.
*/
function
password_get_info
(
$hash
)
{
$return
=
array
(
'algo'
=>
0
,
'algoName'
=>
'unknown'
,
'options'
=>
array
(),
);
if
(
PasswordCompat\binary\_substr
(
$hash
,
0
,
4
)
==
'$2y$'
&&
PasswordCompat\binary\_strlen
(
$hash
)
==
60
)
{
$return
[
'algo'
]
=
PASSWORD_BCRYPT
;
$return
[
'algoName'
]
=
'bcrypt'
;
list
(
$cost
)
=
sscanf
(
$hash
,
"$2y$%d$"
);
$return
[
'options'
][
'cost'
]
=
$cost
;
}
return
$return
;
}
/**
* Determine if the password hash needs to be rehashed according to the options provided
*
* If the answer is true, after validating the password using password_verify, rehash it.
*
* @param string $hash The hash to test
* @param int $algo The algorithm used for new password hashes
* @param array $options The options array passed to password_hash
*
* @return boolean True if the password needs to be rehashed.
*/
function
password_needs_rehash
(
$hash
,
$algo
,
array
$options
=
array
())
{
$info
=
password_get_info
(
$hash
);
if
(
$info
[
'algo'
]
!==
(
int
)
$algo
)
{
return
true
;
}
switch
(
$algo
)
{
case
PASSWORD_BCRYPT
:
$cost
=
isset
(
$options
[
'cost'
])
?
(
int
)
$options
[
'cost'
]
:
PASSWORD_BCRYPT_DEFAULT_COST
;
if
(
$cost
!==
$info
[
'options'
][
'cost'
])
{
return
true
;
}
break
;
}
return
false
;
}
/**
* Verify a password against a hash using a timing attack resistant approach
*
* @param string $password The password to verify
* @param string $hash The hash to verify against
*
* @return boolean If the password matches the hash
*/
function
password_verify
(
$password
,
$hash
)
{
if
(
!
function_exists
(
'crypt'
))
{
trigger_error
(
"Crypt must be loaded for password_verify to function"
,
E_USER_WARNING
);
return
false
;
}
$ret
=
crypt
(
$password
,
$hash
);
if
(
!
is_string
(
$ret
)
||
PasswordCompat\binary\_strlen
(
$ret
)
!=
PasswordCompat\binary\_strlen
(
$hash
)
||
PasswordCompat\binary\_strlen
(
$ret
)
<=
13
)
{
return
false
;
}
$status
=
0
;
for
(
$i
=
0
;
$i
<
PasswordCompat\binary\_strlen
(
$ret
);
$i
++
)
{
$status
|=
(
ord
(
$ret
[
$i
])
^
ord
(
$hash
[
$i
]));
}
return
$status
===
0
;
}
}
}
namespace
PasswordCompat\binary
{
if
(
!
function_exists
(
'PasswordCompat\\binary\\_strlen'
))
{
/**
* Count the number of bytes in a string
*
* We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
* In this case, strlen() will count the number of *characters* based on the internal encoding. A
* sequence of bytes might be regarded as a single multibyte character.
*
* @param string $binary_string The input string
*
* @internal
* @return int The number of bytes
*/
function
_strlen
(
$binary_string
)
{
if
(
function_exists
(
'mb_strlen'
))
{
return
mb_strlen
(
$binary_string
,
'8bit'
);
}
return
strlen
(
$binary_string
);
}
/**
* Get a substring based on byte limits
*
* @see _strlen()
*
* @param string $binary_string The input string
* @param int $start
* @param int $length
*
* @internal
* @return string The substring
*/
function
_substr
(
$binary_string
,
$start
,
$length
)
{
if
(
function_exists
(
'mb_substr'
))
{
return
mb_substr
(
$binary_string
,
$start
,
$length
,
'8bit'
);
}
return
substr
(
$binary_string
,
$start
,
$length
);
}
/**
* Check if current PHP version is compatible with the library
*
* @return boolean the check result
*/
function
check
()
{
static
$pass
=
NULL
;
if
(
is_null
(
$pass
))
{
if
(
function_exists
(
'crypt'
))
{
$hash
=
'$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'
;
$test
=
crypt
(
"password"
,
$hash
);
$pass
=
$test
==
$hash
;
}
else
{
$pass
=
false
;
}
}
return
$pass
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment