0×00 前言 首先如果你对密码学的概念以及使用并不熟悉,或者你正需要进行一些密码学的引导,那么我推荐你阅读一下这篇内容。
此前我们就曾明确的表示,即使是安全建议也应该有个保质期。因此和我们过去发布的大多数博客文章不同,上面那篇内容实际上是处在一种“随时更新”的状态:当对安全的需求变化以及新的攻击形式被发现时,我们都会做出相应的变更。
这里我们提出一个密码安全观点: 不要存储明文密码,而是存储密码的哈希值。
事实上现在,生成安全的密码哈希值非常简单。
但这里有个问题就是,你可能希望别人可以设置一个带密码的帐号,通过这个帐号和密码,别人可以登录到你的程序中,那么这一功能要怎样才能安全的实现?
而解决这个问题也很简单——使用libsodium。它可以为大多数语言提供一个安全的密码哈希api。在1.0.8版本之前它使用的是scrypt算法,但从1.0.9版本开始,它还会提供argon2,这是从最近的哈希密码对比中精心挑选出来的一个算法。libsodium会提供对大多数编程语言的绑定。
libsodium文件
libsodium源码
注意:这里公布了一个对argon2i的攻击——argon2通用密码哈希的变种表示。实际上它的影响并不严重,但它可能会导致一个新的变种(也许是argon2x,因为它可能会使用xor而不是覆盖内存来缓解这些攻击)。
如果你因为种种原因无法在安装libsodium和你的要求之间进行调和的话,你还有其他的选择。在准备这篇博客时,我们的团队已经研究了多种编程语言的几个密码哈希库,下面就为大家利用示例代码介绍下。
目前可接受的密码哈希算法主要有:
argon2 密码哈希比赛的冠军
bcrypt
scrypt
以及密码哈希比赛的其他参赛算法 (catena, lyra2, makwa, and yescrypt)
pbkdf2 最糟糕的一种选择
0×01 php php第一种方案 首先确认你使用的版本是否支持php。如果支持,那么php密码api将能够使用。如果不支持,那么可以尝试进行升级,如果还是不行,检查一下password_compat。
$hash = password_hash($userpassword, password_default);
显然 password_hash() 使用的是加盐计算的哈希值。根据自己实际情况进行调整,如果硬件支持的话,使用最小绝对值10.12是很好的,其默认的10。
$hash = password_hash($userpassword, password_default, ['cost' => 12]);
事实上验证一个密码的哈希存储是很简单的:
if (password_verify($userpassword, $hash)) { // login successful. if (password_needs_rehash($userpassword, password_default, ['cost' => 12])) { // recalculate a new password_hash() and overwrite the one we stored previously }}
在php7中,password_default仍然使用bcrypt。在未来的版本中,它可能会逐渐变为argon2.
php第二种方案 如果你没办法使用libsodium(当然我们强烈建议你使用),你仍然可以用php中加密哈希,只需要通过来自pecl 的dominic black的scrypt php 扩展即可。
#if you don't have pecl installed, get that first.pecl install scryptecho extension=scrypt.so > /etc/php5/mods-available/scrypt.iniphp5enmod scrypt
接下来,把一个php包装捆绑在你的工程中。
# hashing$hash = \password::hash($userprovidedpassword);# validationif (\password::check($userprovidedpassword, $hash)) { // logged in successfully.}
0×02 java java第一种方案 在java程序中进行安全的密码哈希,除了 libsodium还有jbcrypt,能够提供bcrypt密码哈希算法。
string hash = bcrypt.hashpw(userprovidedpassword, bcrypt.gensalt());
验证一个在java中的bcrypt hash:
if (bcrypt.checkpw(userprovidedpassword, hash)) { // login successful.}
java第二种方案 有一个java实现的scrypt,但它需要你指定参数,而不会提供一个默认值给你。
# calculating a hashint n = 16384;int r = 8;int p = 1;string hashed = scryptutil.scrypt(passwd, n, r, p);# validating a hashif (scryptutil.check(passwd, hashed)) { // login successful}
0×03 c# .net c#(.net)第一种方案 我们推荐martin steel的bcrypt.net fork而不是 system.security.cryptography.rfc2898derivebytes ,它是 pbkdf2-sha1 (我们不是说 pbkdf2-sha1 不安全,但相对来说bcrypt更好一些。
// calculating a hashstring hash = bcrypt.hashpassword(userspassword, bcrypt.generatesalt());// validating a hashif (bcrypt.verify(userspassword, hash)) { // login successful}
c#(.net)第二种方案 这里同样有一个scrypt 包 在nuget中。
// this is necessary:scryptencoder encoder = new scryptencoder();// calculating a hashsecurestring hashedpassword = encoder.encode(userspassword);// validating a hashif (encoder.compare(userspassword, hashedpassword)) { // login successful}
0×04 ruby ruby 第一种方案 按照笔者一如既往的规律,这里是一个对 bcrypt密码哈希的 ruby gem 。
require bcrypt# calculating a hashmy_password = bcrypt::password.create(userspassword)# validating a hashif my_password == userspassword # login successful
要注意的是截止本文发布,该库仍然没有遵循加密编码的最佳实践。因此在他们抽出时间打补丁前都需要考虑到这一点。
ruby 第二种方案 在ruby中进行scrpyt哈希
这也是一个对 bcrypt密码哈希的 ruby gem 。
require scrypt# calculating a hashpassword = scrypt::password.create(userspassword)# validating a hashif password == userspassword # login successful
0×05 python python 第一种方案 好了你猜的没错还是使用 bcrypt python 包 ( github )
import bcryptimport hmac# calculating a hashpassword = bcorrect horse battery staplehashed = bcrypt.hashpw(password, bcrypt.gensalt())# validating a hash (don't use ==)if (hmac.compare_digest(bcrypt.hashpw(password, hashed), hashed)): # login successful
python开发人员通常更喜欢passlib (bitbucket),尽管它的api命名并不正确。(”加密” 而不是 “hash”):
from passlib.hash import bcrypt# calculating a hashhash = bcrypt.encrypt(userspassword, rounds=12)# validating a hashif bcrypt.verify(userspassword, hash): # login successful
python 第二种方案 目前我们发现除了libsodium以外只有 django-scrypt package .可以较为健全的实现。其他的我们还在寻找中。
0×06 node.js node.js 第一种方案 在node.js上bcrypt( https://www.npmjs.com/package/bcrypt)有两种安全的实现方式,尽管bcrypt看起来是首选。
在下面的例子中,我们大量的使用了promises来简化错误处理:
var promise = require(bluebird);var bcrypt = promise.promisifyall(require(bcrypt));function addbcrypttype(err) { // compensate for `bcrypt` not using identifiable error types err.type = bcrypterror; throw err;}// calculating a hash:promise.try(function() { return bcrypt.hashasync(userspassword, 10).catch(addbcrypttype);}).then(function(hash) { // store hash in your password db.});// validating a hash:// load hash from your password db.promise.try(function() { return bcrypt.compareasync(userspassword, hash).catch(addbcrypttype);}).then(function(valid) { if (valid) { // login successful } else { // login wrong }});// you would handle errors something like this, but only at the top-most point where it makes sense to do so:promise.try(function() { // generate or compare a hash here}).then(function(result) { // ... some other stuff ...}).catch({type: bcrypterror}, function(err) { // something went wrong with bcrypt});
node.js 第二种方案 我们推荐 scrypt for humans,这是一个对开发人员很友好的 node-scrypt 包装器,非常容易使用。
var promise = require(bluebird);var scrypt = require(scrypt-for-humans);// calculating a hash:promise.try(function() { return scrypt.hash(userspassword);}).then(function(hash) { // store hash for long term use});// validating a hash:promise.try(function() { return scrypt.verifyhash(userspassword, hash);}).then(function() { // login successful}).catch(scrypt.passworderror, function(err) { // login failed});
*原文地址: paragonie ,东二门陈冠希/编译 0xroot后期整理,转载请注明来自freebuf黑客与极客(freebuf.com)