目前认为MD5是部分不安全的。考虑到这一点,我想知道使用哪种机制来保护密码。

这个问题,“双重哈希”密码是否比只哈希一次更不安全? 建议哈希多次可能是一个好主意,而如何实现个别文件的密码保护?建议使用盐。

我用的是PHP。我想要一个安全快捷的密码加密系统。对一个密码进行一百万次哈希运算可能更安全,但也更慢。如何在速度和安全之间取得良好的平衡?此外,我更希望结果具有恒定数量的字符。

哈希机制必须在PHP中可用 必须是安全的 它可以使用盐(在这种情况下,所有的盐都一样好吗?有没有办法生产出好的盐?)

另外,我是否应该在数据库中存储两个字段(例如,一个使用MD5,另一个使用SHA)?这会让它更安全还是更不安全?

以防我不够清楚,我想知道要使用哪个哈希函数,以及如何选择一个好的盐,以便拥有一个安全和快速的密码保护机制。

没有完全涵盖我的问题的相关问题:

PHP中的SHA和MD5有什么区别 简单密码加密 为asp.net存储密钥和密码的安全方法 如何在Tomcat 5.5中实现加盐密码


当前回答

我使用的是Phpass,这是一个简单的单文件PHP类,可以在几乎每个PHP项目中非常容易地实现。参见The H。

默认情况下,它使用在Phpass中实现的最强可用加密,即bcrypt,并回落到其他加密,直到MD5,以提供向后兼容的框架,如Wordpress。

返回的散列可以按原样存储在数据库中。生成哈希的示例使用如下:

$t_hasher = new PasswordHash(8, FALSE);
$hash = $t_hasher->HashPassword($password);

要验证密码,可以使用:

$t_hasher = new PasswordHash(8, FALSE);
$check = $t_hasher->CheckPassword($password, $hash);

其他回答

我在这里找到了关于这个问题的完美主题:https://crackstation.net/hashing-security.htm,我希望你能从中受益,这里还有源代码,可以防止基于时间的攻击。

<?php
/*
 * Password hashing with PBKDF2.
 * Author: havoc AT defuse.ca
 * www: https://defuse.ca/php-pbkdf2.htm
 */

// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTES", 24);
define("PBKDF2_HASH_BYTES", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

function create_hash($password)
{
    // format: algorithm:iterations:salt:hash
    $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
    return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" .  $salt . ":" . 
        base64_encode(pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        ));
}

function validate_password($password, $good_hash)
{
    $params = explode(":", $good_hash);
    if(count($params) < HASH_SECTIONS)
       return false; 
    $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
    return slow_equals(
        $pbkdf2,
        pbkdf2(
            $params[HASH_ALGORITHM_INDEX],
            $password,
            $params[HASH_SALT_INDEX],
            (int)$params[HASH_ITERATION_INDEX],
            strlen($pbkdf2),
            true
        )
    );
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
    $diff = strlen($a) ^ strlen($b);
    for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
    {
        $diff |= ord($a[$i]) ^ ord($b[$i]);
    }
    return $diff === 0; 
}

/*
 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
 * $algorithm - The hash algorithm to use. Recommended: SHA256
 * $password - The password.
 * $salt - A salt that is unique to the password.
 * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
 * $key_length - The length of the derived key in bytes.
 * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
 * Returns: A $key_length-byte key derived from the password and salt.
 *
 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
 *
 * This implementation of PBKDF2 was originally created by https://defuse.ca
 * With improvements by http://www.variations-of-shadow.com
 */
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}
?>

免责声明:这个答案写于2008年。 从那时起,PHP给了我们password_hash和password_verify,自从它们被引入以来,它们是推荐的密码哈希和检查方法。 尽管如此,答案的理论仍然是一本很好的读物。

博士TL;

不该做的事

Don't limit what characters users can enter for passwords. Only idiots do this. Don't limit the length of a password. If your users want a sentence with supercalifragilisticexpialidocious in it, don't prevent them from using it. Don't strip or escape HTML and special characters in the password. Never store your user's password in plain-text. Never email a password to your user except when they have lost theirs, and you sent a temporary one. Never, ever log passwords in any manner. Never hash passwords with SHA1 or MD5 or even SHA256! Modern crackers can exceed 60 and 180 billion hashes/second (respectively). Don't mix bcrypt and with the raw output of hash(), either use hex output or base64_encode it. (This applies to any input that may have a rogue \0 in it, which can seriously weaken security.)

Dos

尽可能使用scrypt;如果你不能Bcrypt。 如果不能使用bcrypt或scrypt,则使用PBKDF2和SHA2散列。 当数据库被入侵时,重置所有人的密码。 实现一个合理的8-10个字符的最小长度,加上要求至少一个大写字母,一个小写字母,一个数字和一个符号。这将提高密码的熵,从而使其更难被破解。(关于一些争论,请参阅“什么是好密码?”一节。)

为什么要对密码进行哈希?

哈希密码背后的目标很简单:通过破坏数据库防止恶意访问用户帐户。所以密码哈希的目标是阻止黑客或破解者花费太多的时间或金钱来计算纯文本密码。时间/成本是你武器库中最好的威慑。

希望对用户帐户进行良好、健壮的散列的另一个原因是给您足够的时间来更改系统中的所有密码。如果您的数据库被泄露,您至少需要足够的时间来锁定系统,如果不更改数据库中的每个密码的话。

Whitehat Security的CTO Jeremiah Grossman在最近一次密码恢复后,在White Hat Security的博客上表示,他需要暴力破解密码保护:

有趣的是,在这个噩梦中,我学到了很多我不知道的关于密码破解、存储和复杂性的知识。我开始理解为什么密码存储比密码复杂度重要得多。如果您不知道密码是如何存储的,那么您真正可以依赖的就是复杂性。对于密码和加密专家来说,这可能是常识,但对于一般的InfoSec或Web安全专家来说,我高度怀疑这一点。

(强调我的。)

到底什么才是好的密码呢?

熵。(并不是说我完全赞同兰德尔的观点。)

简而言之,熵就是密码内部的变化量。当密码只有小写罗马字母时,也就是26个字符。变化不大。字母数字组合的密码更好,最多36个字符。但是允许大写和小写,加上符号,大约是96个字符。这比只写信好多了。一个问题是,为了让我们的密码更容易记住,我们插入了模式——这减少了熵。哦!

密码熵很容易逼近。使用全范围的ascii字符(大约96个可输入字符)产生每个字符6.6的熵,对于未来的安全性来说,8个字符的密码仍然太低(52.679位熵)。但好消息是:较长的密码和使用unicode字符的密码确实会增加密码的熵,使其更难破解。

在Crypto StackExchange网站上有一个关于密码熵的更长的讨论。一个好的谷歌搜索也会出现很多结果。

在我与@popnoodles交谈的评论中,他指出,强制执行X长度的密码策略,包含X个字母、数字、符号等,实际上可以通过使密码方案更可预测来减少熵。我同意。随机性总是最安全但最不容易记住的解决方案。

据我所知,制定世界上最好的密码是一个两难的选择。要么是不容易记住,太可预测,太短,太多unicode字符(很难在Windows/移动设备上输入),要么是太长,等等。任何密码都不能真正满足我们的目的,所以我们必须像保护诺克斯堡一样保护它们。

最佳实践

Bcrypt and scrypt are the current best practices. Scrypt will be better than bcrypt in time, but it hasn't seen adoption as a standard by Linux/Unix or by webservers, and hasn't had in-depth reviews of its algorithm posted yet. But still, the future of the algorithm does look promising. If you are working with Ruby there is an scrypt gem that will help you out, and Node.js now has its own scrypt package. You can use Scrypt in PHP either via the Scrypt extension or the Libsodium extension (both are available in PECL).

如果您想了解如何使用bcrypt,或者找到一个好的包装器,或者使用PHPASS之类的东西来实现更传统的实现,我强烈建议您阅读crypt函数的文档。我建议至少进行12轮bcrypt,如果不是15到18轮的话。

当我了解到bcrypt只使用blowfish的密钥调度时,我改变了使用bcrypt的想法,并具有可变成本机制。后者可以通过增加blowfish本已昂贵的密钥调度来增加暴力破解密码的成本。

一般的做法

我几乎无法想象这种情况了。PHPASS支持PHP 3.0.18到5.3,因此它几乎适用于所有可以想象到的安装——如果您不确定您的环境是否支持bcrypt,则应该使用它。

但是假设您根本不能使用bcrypt或PHPASS。然后什么?

尝试使用环境/应用程序/用户感知所能容忍的最大轮数的PDKBF2实现。我建议最低数量是2500发。此外,如果hash_hmac()可用,请确保使用hash_hmac(),以使操作更难再现。

未来的实践

PHP 5.5中提供了一个完整的密码保护库,可以抽象出使用bcrypt的任何痛苦。当我们大多数人在最常见的环境(尤其是共享主机)中使用PHP 5.2和5.3时,@ircmaxell已经为即将到来的API构建了向后兼容PHP 5.3.7的兼容层。

密码学概述和免责声明

真正破解散列密码所需的计算能力并不存在。计算机“破解”密码的唯一方法是重新创建密码,并模拟用于保护密码的哈希算法。散列的速度与它的野蛮强制能力线性相关。更糟糕的是,大多数哈希算法可以很容易地并行化,从而执行得更快。这就是为什么像bcrypt和scrypt这样昂贵的方案如此重要。

You cannot possibly foresee all threats or avenues of attack, and so you must make your best effort to protect your users up front. If you do not, then you might even miss the fact that you were attacked until it's too late... and you're liable. To avoid that situation, act paranoid to begin with. Attack your own software (internally) and attempt to steal user credentials, or modify other user's accounts or access their data. If you don't test the security of your system, then you cannot blame anyone but yourself.

最后,我不是密码学家。无论我说什么都是我的观点,但我碰巧认为这是基于良好的常识……还有大量的阅读。记住,要尽可能地多疑,让事情尽可能地难以侵入,然后,如果你仍然担心,联系一个白帽黑客或密码学家,看看他们对你的代码/系统有什么看法。

需要记住的事情

关于PHP密码加密已经说了很多,其中大多数都是非常好的建议,但是在开始使用PHP进行密码加密之前,请确保您已经实现了以下内容或准备实现以下内容。

服务器

港口

无论您的加密技术有多好,如果您不能正确地保护运行PHP和DB的服务器,那么您所有的努力都是徒劳的。大多数服务器的功能相对相同,它们都有指定的端口,允许您通过ftp或shell远程访问它们。确保您更改了活动的任何远程连接的默认端口。如果不这样做,实际上就使攻击者在访问您的系统时少做了一步。

用户名

在这个世界上,最好不要使用admin、root或类似的用户名。此外,如果你是在一个基于unix的系统上,不要让根帐户登录可访问,它应该总是只有sudo。

密码

你告诉你的用户要设置好的密码以避免被黑客攻击,你也要这么做。当后门大开着的时候,费劲地锁上前门又有什么意义呢?

数据库

服务器

理想情况下,您希望DB和APPLICATION位于不同的服务器上。由于成本原因,这并不总是可行的,但它确实具有一定的安全性,因为攻击者必须通过两个步骤才能完全访问系统。

USER

始终让您的应用程序拥有自己的帐户来访问DB,并且只给它所需的特权。

然后为您创建一个单独的用户帐户,该帐户不存储在服务器上的任何地方,甚至不存储在应用程序中。

比如总是不要做这个根或类似的东西。

密码

遵循与所有好密码相同的原则。另外,不要在同一系统上的任何SERVER或DB帐户上重复使用相同的密码。

PHP

密码

永远不要在你的数据库中存储密码,而是存储哈希和唯一的盐,我将在后面解释为什么。

哈希

单向哈希!!!!!!!永远不要以一种可以反转的方式哈希密码,哈希值应该是一种方式,这意味着你不反转它们并将它们与密码进行比较,而是以同样的方式哈希输入的密码并比较两个哈希值。这意味着即使攻击者访问了数据库,他也不知道实际的密码是什么,只知道其结果哈希值。这意味着在最糟糕的情况下为用户提供更多的安全性。

有很多不错的散列函数(password_hash, hash等等),但是为了使散列有效,您需要选择一个好的算法。(bcrypt和类似的算法都是不错的算法。)

当哈希速度是关键时,越慢越能抵抗暴力攻击。

哈希中最常见的错误之一是哈希值对用户来说不是唯一的。这主要是因为盐不是唯一产生的。

在散列之前,密码应该始终加盐。Salting在密码中添加一个随机字符串,这样类似的密码在DB中就不会出现相同的密码。然而,如果盐对每个用户都不是唯一的(例如:你使用了硬编码的盐),那么你几乎已经让你的盐变得毫无价值。因为一旦攻击者找到了一个密码盐,他就有了所有密码盐。

当你创建一个salt时,确保它是唯一的密码,然后将完成的散列和salt存储在你的DB中。这将使攻击者在获得访问权限之前必须单独破解每个盐和哈希。这对攻击者来说意味着更多的工作和时间。

用户创建密码

如果用户通过前端创建密码,则意味着必须将密码发送到服务器。这就产生了一个安全问题,因为这意味着未加密的密码将被发送到服务器,如果攻击者能够监听和访问,那么PHP中的所有安全性都是毫无价值的。始终安全地传输数据,这是通过SSL完成的,但即使SSL也不是完美无缺的(OpenSSL的Heartbleed缺陷就是一个例子)。

还要让用户创建一个安全的密码,这很简单,应该一直这样做,用户最终会感激它。

最后,无论你采取什么安全措施,没有什么是100%安全的,保护的技术越先进,攻击就会变得越先进。但是,遵循这些步骤将使您的站点更加安全,并且攻击者更不愿意进行攻击。

下面是一个PHP类,它可以轻松地为密码创建散列和盐

http://git.io/mSJqpw

我不会以两种不同的方式存储哈希密码,因为这样的话,系统至少和正在使用的最弱的哈希算法一样弱。

我通常使用SHA1和salt和用户ID(或其他特定于用户的信息),有时我还使用常数salt(因此我有2部分salt)。

SHA1 is now also considered somewhat compromised, but to a far lesser degree than MD5. By using a salt (any salt), you're preventing the use of a generic rainbow table to attack your hashes (some people have even had success using Google as a sort of rainbow table by searching for the hash). An attacker could conceivably generate a rainbow table using your salt, so that's why you should include a user-specific salt. That way, they will have to generate a rainbow table for each and every record in your system, not just one for your entire system! With that type of salting, even MD5 is decently secure.