安全三部曲 之 密码与Hash

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);
}

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容