安全三部曲 之 密码与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);
}

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容