Contents
Framework
Laravel
composer create-project laravel/laravel laravel-password --prefer-dist "5.5.*" && cd laravel-password
php artisan make:auth
// app/Http/Controllers/Auth/RegisterController.php
class RegisterController extends Controller
{
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' = b> $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
// vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
if (! function_exists('bcrypt')) {
function bcrypt($value, $options = [])
{
return app('hash')->make($value, $options);
}
}
Provider
// config/app.php
return [
'providers' => [
Illuminate\Hashing\HashServiceProvider::class,
],
];
// vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php
class HashServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new BcryptHasher;
});
}
}
PHP Password
password_hash
<?php
$hash = password_hash('test', 1);
echo $hash . "\n";
password_verify
<?php
$hash = password_hash('test', 1);
echo $hash . "\n";
if (password_verify('test', $hash)) {
echo 'right';
}
Design Password
Clear text
服务商知悉
攻击者攻击
MD5 Hash
MD5: Message-Digest Algorithm / 128Bit / irreversible
- 破解速度
已有软件能够在单个GPU上以每秒36.5亿次计算的速率破解MD5的哈希值 根据密码的复杂性和长度 可以在不到半小时内破解一个哈希值
- 彩虹表
彩虹表是哈希值的逆向查找表 该表的创建者预先计算所有常见单词、短语、修改的单词 甚至随机字符串的MD5哈希值 能够访问某个哈希值的人可在查找表中输入它 发现用于生成它的密码 从而有效地反转这个单向过程
Algo & Salt & Cost
Algo: Blowfish / 60Bytes / irreversible
Salt: a random salt will be generated by password_hash() or each password hashed
Cost: 使用我自己的Macbook Air作为测试环境 生成一个cost为10(默认值)的password_hash大约会花0.082秒的时间 将该cost调高到14 会将该时间更改为每次计算1.286秒
<?php
$t1 = microtime(true);
$hash = password_hash('test', 1);
$t2 = microtime(true);
echo (($t2-$t1)).'s'."\n";
$t1 = microtime(true);
$hash = password_hash('test', 1, [ 'cost' => 14]);
$t2 = microtime(true);
echo (($t2-$t1)).'s';
Source Code
PHP源码详细参考php-src/ext/standard/password.c
PHP_FUNCTION(password_hash)
{
zend_string *password, *digest = NULL;
zval *zalgo;
const php_password_algo *algo;
zend_array *options = NULL;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(password)
Z_PARAM_ZVAL(zalgo)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_OR_OBJECT_HT(options)
ZEND_PARSE_PARAMETERS_END();
algo = php_password_algo_find_zval(zalgo);
if (!algo) {
zend_string *algostr = zval_get_string(zalgo);
php_error_docref(NULL, E_WARNING, "Unknown password hashing algorithm: %s", ZSTR_VAL(algostr));
zend_string_release(algostr);
RETURN_NULL();
}
digest = algo->hash(password, options);
if (!digest) {
/* algo->hash should have raised an error. */
RETURN_NULL();
}
RETURN_NEW_STR(digest);
}
static const php_password_algo* php_password_algo_find_zval(zval *arg) {
return php_password_algo_find_zval_ex(arg, php_password_algo_default());
}
static const php_password_algo* php_password_algo_find_zval_ex(zval *arg, const php_password_algo* default_algo) {
if (!arg || (Z_TYPE_P(arg) == IS_NULL)) {
return default_algo;
}
if (Z_TYPE_P(arg) == IS_LONG) {
switch (Z_LVAL_P(arg)) {
case 0: return default_algo;
case 1: return &php_password_algo_bcrypt;
#if HAVE_ARGON2LIB
case 2: return &php_password_algo_argon2i;
case 3: return &php_password_algo_argon2id;
#endif
}
return NULL;
}
if (Z_TYPE_P(arg) != IS_STRING) {
return NULL;
}
return php_password_algo_find(Z_STR_P(arg));
}
const php_password_algo* php_password_algo_find(const zend_string *ident) {
zval *tmp;
if (!ident) {
return NULL;
}
tmp = zend_hash_find(&php_password_algos, (zend_string*)ident);
if (!tmp || (Z_TYPE_P(tmp) != IS_PTR)) {
return NULL;
}
return Z_PTR_P(tmp);
}
const php_password_algo php_password_algo_bcrypt = {
"bcrypt",
php_password_bcrypt_hash,
php_password_bcrypt_verify,
php_password_bcrypt_needs_rehash,
php_password_bcrypt_get_info,
php_password_bcrypt_valid,
};
static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_array *options) {
char hash_format[10];
size_t hash_format_len;
zend_string *result, *hash, *salt;
zval *zcost;
zend_long cost = PHP_PASSWORD_BCRYPT_COST;
if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
cost = zval_get_long(zcost);
}
if (cost < 4 || cost > 31) {
php_error_docref(NULL, E_WARNING, "Invalid bcrypt cost parameter specified: " ZEND_LONG_FMT, cost);
return NULL;
}
hash_format_len = snprintf(hash_format, sizeof(hash_format), "$2y$%02" ZEND_LONG_FMT_SPEC "$", cost);
if (!(salt = php_password_get_salt(NULL, Z_UL(22), options))) {
return NULL;
}
ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
hash = zend_string_alloc(ZSTR_LEN(salt) + hash_format_len, 0);
sprintf(ZSTR_VAL(hash), "%s%s", hash_format, ZSTR_VAL(salt));
ZSTR_VAL(hash)[hash_format_len + ZSTR_LEN(salt)] = 0;
zend_string_release_ex(salt, 0);
/* This cast is safe, since both values are defined here in code and cannot overflow */
result = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
zend_string_release_ex(hash, 0);
if (!result) {
return NULL;
}
if (ZSTR_LEN(result) < 13) {
zend_string_free(result);
return NULL;
}
return result;
}
static zend_string* php_password_get_salt(zval *unused_, size_t required_salt_len, HashTable *options) {
if (options && zend_hash_str_exists(options, "salt", sizeof("salt") - 1)) {
php_error_docref(NULL, E_WARNING, "The 'salt' option is no longer supported. The provided salt has been been ignored");
}
return php_password_make_salt(required_salt_len);
}