php哈希 - php密码加密




为PHP密码提供安全散列和盐分 (10)

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

这个问题, “双重散列”密码不仅仅是一次散列吗? 建议多次散列可能是一个好主意,而如何实现单个文件的密码保护? 建议使用盐。

我使用PHP。 我想要一个安全快速的密码加密系统。 散列密码一百万次可能更安全,但速度也更慢。 如何在速度和安全之间取得良好的平衡? 此外,我更喜欢结果有一个恒定数量的字符。

  1. 哈希机制必须在PHP中可用
  2. 它必须是安全的
  3. 它可以使用盐(在这种情况下,所有的盐都是同样好的吗?有什么方法可以产生好的盐吗?)

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

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

不完全覆盖我的问题的相关问题:

PHP和SHA中的MD5有什么区别
简单的密码加密
存储密钥的安全方法,用于asp.net的密码
你将如何在Tomcat 5.5中实现咸味密码


免责声明 :这个答案是在2008年写的。

从那以后,PHP给了我们php.net/manual/en/function.password-hash.phppassword_verify ,自推出以来,他们是推荐的密码散列和检查方法。

尽管答案的理论仍然是一个很好的解读。

TL; DR

注意事项

  • 不要限制用户可以输入密码的字符。 只有白痴才能做到这一点。
  • 不要限制密码的长度。 如果你的用户想要一个带有supercalifragilisticexpialidocious的句子,不要阻止他们使用它。
  • 切勿以纯文本格式存储用户的密码。
  • 切勿将密码通过电子邮件发送给用户, 除非他们丢失了密码,并且您发送了临时密码
  • 永远不要以任何方式记录密码。
  • 永远不要使用SHA1或MD5甚至SHA256哈希密码! 现代饼干可分别超过60亿和1800亿次/秒(分别)。
  • 不要混合使用bcrypt和hash()的原始输出 ,要么使用十六进制输出,要么使用base64_encode。 (这适用于任何可能有恶意\0输入,这会严重削弱安全性。)

DOS

  • 尽可能使用scrypt; 如果你不行,就加密。
  • 如果您不能使用bcrypt或scrypt,并使用SHA2散列,请使用PBKDF2。
  • 数据库受损时重置每个人的密码。
  • 实现合理的8-10个字符的最小长度,并且至少需要1个大写字母,1个小写字母,一个数字和一个符号。 这将改善密码的熵,反过来使它更难以破解。 (请参阅“什么是一个很好的密码?”以进行一些辩论。)

为什么要密码?

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

另一个需要在用户帐户上使用强大哈希的原因是为了让您有足够的时间来更改系统中的所有密码。 如果您的数据库被破坏,您将需要足够的时间至少锁定系统,如果不更改数据库中的每个密码。

Whitehat Security首席技术官Jeremiah Grossman在最近密码恢复之后在其博客中表示 ,要求强力破解密码保护:

有趣的是,在生存这个噩梦中,我学到了很多我不知道密码破解,存储和复杂性的知识。 我明白为什么密码存储比密码复杂性更重要。 如果你不知道你的密码是如何存储的,那么你真正可以依赖的是复杂性。 这可能是密码和加密专家的常识,但对于普通的信息安全或网络安全专家,我非常怀疑它。

(强调我的。)

无论如何,什么使得一个好的密码?

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

简而言之,熵是密码内多少变化。 当密码只有小写的罗马字母时,只有26个字符。 这并没有太大的变化。 字母数字密码更好,有36个字符。 但是,允许大写和小写的符号大约有96个字符。 这比字母好多了。 一个问题是,为了让我们的密码难忘,我们插入模式 - 这减少了熵。 哎呀!

密码熵很容易approximated 。 使用全部ASCII字符(大约96个可键入的字符)会产生6.6个字符的熵,对于未来的安全性而言,密码的8个字符仍然太低(熵为52.679位)。 但好消息是:更长的密码和带有unicode字符的密码确实会增加密码的熵并使其更难破解。

Crypto StackExchange站点上有更长的密码熵讨论。 一个好的谷歌搜索也会带来很多结果。

在我和@popnoodles谈到的评论中,他指出,通过使密码策略更易于预测, 强制 X长度的密码策略以及许多字母,数字,符号等实际上可以减少熵。 我同意。 Randomess尽可能真正随机,始终是最安全但最难忘的解决方案。

据我所知,制作世界上最好的密码是Catch-22。 要么它不令人难忘,太可预测,太短,太多的unicode字符(很难在Windows / Mobile设备上键入),太长等等。没有密码对我们的目的来说足够好,所以我们必须像保护它们一样保护它们在诺克斯堡。

最佳实践

Bcrypt和scrypt是当前的最佳实践。 scrypt会比bcrypt更好,但它并没有被Linux / Unix或网络服务器视为标准,并且还没有对它的算法进行深入的评论。 但是,该算法的未来仍然看起来很有希望。 如果你正在使用Ruby,那么就有一个可以帮助你的scrypt gem ,而Node.js现在有它自己的scrypt包。 您可以通过Scrypt扩展名或Libsodium扩展名在PHP中使用Scrypt(均可在PECL中使用)。

如果你想了解如何使用bcrypt,或者发现自己是一个good wrapper或者使用类似openwall.com/phpass东西来实现更多的传统实现,我强烈建议阅读crypt函数的文档。 我建议至少12轮bcrypt,如果不是15到18。

当我知道bcrypt只使用blowfish的关键时间表和可变成本机制时,我改变了使用bcrypt的想法。 后者让你通过增加河豚已经很贵的关键时间表来增加强制密码的成本。

平均做法

我几乎无法想象这种情况了。 openwall.com/phpass支持PHP 3.0.18到5.3,所以它几乎可以在任何可以想象的安装上使用 - 如果您不确定您的环境是否支持bcrypt,则应该使用它。

但是,假设你根本不能使用bcrypt或PHPASS。 然后怎样呢?

尝试使用您的环境/应用程序/用户感知可以容忍的最大轮次数来实施PDKBF2 。 我建议的最低数字是2500发。 另外,如果可以使用hash_hmac()来使操作难以重现,请确保使用它。

未来的做法

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

密码学概述和免责声明

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

您无法预见所有威胁或攻击途径,因此您必须尽全力保护用户。 如果你不这样做,那么你甚至可能会错过你遭到袭击的事实,直到它为时已晚...... 而你要承担责任 。 为了避免这种情况,首先采取偏执行为。 攻击自己的软件(内部)并尝试窃取用户凭据,或修改其他用户的帐户或访问其数据。 如果你没有测试系统的安全性,那么除了你自己,你不能责怪任何人。

最后:我不是密码学家。 无论我说的是我的意见,但我碰巧认为这是基于良好的常识......和大量的阅读。 请记住,尽可能地偏执狂,让事情尽可能闯入,然后,如果您仍然担心,请联系白帽黑客或密码专家,看看他们对您的代码/系统的看法。


SHA1 and a salt should suffice (depending, naturally, on whether you are coding something for Fort Knox or a login system for your shopping list) for the foreseeable future. If SHA1 isn't good enough for you, use SHA256 .

The idea of a salt is to throw the hashing results off balance, so to say. It is known, for example, that the MD5-hash of an empty string is d41d8cd98f00b204e9800998ecf8427e . So, if someone with good enough a memory would see that hash and know that it's the hash of an empty string. But if the string is salted (say, with the string " MY_PERSONAL_SALT "), the hash for the 'empty string' (ie " MY_PERSONAL_SALT ") becomes aeac2612626724592271634fb14d3ea6 , hence non-obvious to backtrace. What I'm trying to say, that it's better to use any salt, than not to. Therefore, it's not too much of an importance to know which salt to use.

There are actually websites that do just this - you can feed it a (md5) hash, and it spits out a known plaintext that generates that particular hash. If you would get access to a database that stores plain md5-hashes, it would be trivial for you to enter the hash for the admin to such a service, and log in. But, if the passwords were salted, such a service would become ineffective.

Also, double-hashing is generally regarded as bad method, because it diminishes the result space. All popular hashes are fixed-length. Thus, you can have only a finite values of this fixed length, and the results become less varied. This could be regarded as another form of salting, but I wouldn't recommend it.


I usually use SHA1 and salt with the user ID (or some other user-specific piece of information), and sometimes I additionally use a constant salt (so I have 2 parts to the 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.


ok in the fitsy we need salt salt must be unique so let generate it

   /**
     * Generating string
     * @param $size
     * @return string
     */
    function Uniwur_string($size){
        $text = md5(uniqid(rand(), TRUE));
        RETURN substr($text, 0, $size);
    }

also we need the hash I`m using sha512 it is the best and it is in php

   /**
     * Hashing string
     * @param $string
     * @return string
     */
    function hash($string){
        return hash('sha512', $string);
    }

so now we can use this functions to generate safe password

// generating unique password
$password = Uniwur_string(20); // or you can add manual password
// generating 32 character salt
$salt = Uniwur_string(32);
// now we can manipulate this informations

// hashin salt for safe
$hash_salt = hash($salt);
// hashing password
$hash_psw = hash($password.$hash_salt);

now we need to save in database our $hash_psw variable value and $salt variable

and for authorize we will use same steps...

it is the best way to safe our clients passwords...

Ps for last 2 steps you can use your own algorithm... but be sure that you can generate this hashed password in the future when you need to authorize user...


从PHP 5.5开始,PHP具有用于哈希和验证密码的简单,安全的函数php.net/manual/en/function.password-hash.phppassword_verify

$password = 'anna';
$hash = password_hash($password, PASSWORD_DEFAULT);
$expensiveHash = password_hash($password, PASSWORD_DEFAULT, array('cost' => 20));

password_verify('anna', $hash); //Returns true
password_verify('anna', $expensiveHash); //Also returns true
password_verify('elsa', $hash); //Returns false

使用password_hash() ,它会生成一个随机salt并将其包含在输出的散列中(以及所使用的成本和算法) password_verify()然后读取该散列并确定使用的salt和加密方法,并验证它是否违反提供的明文密码。

提供PASSWORD_DEFAULT指示PHP使用已安装版本的PHP的默认哈希算法。 究竟哪种算法意味着在未来的版本中随时间而变化,以便它始终是最强大的可用算法之一。

增加成本(默认为10)使得哈希更难以暴力破解,但也意味着生成哈希和验证密码对于服务器的CPU来说会更有效。

请注意,尽管默认哈希算法可能会发生变化,但旧哈希将继续验证,因为所使用的算法已存储在哈希中,并且password_verify()提取。


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


我只想指出,PHP 5.5包含一个密码散列API ,它提供了一个围绕crypt()的封装。 这个API显着简化了哈希,验证和重新哈希密码哈希的任务。 作者还发布了一个兼容包 (以一个简单的require使用的单个password.php文件的形式),对于那些使用PHP 5.3.7及更高版本并且现在想要使用它的人来说。

它现在只支持BCRYPT,但它的目标是很容易扩展到包含其他密码散列技术,并且由于技术和成本存储为散列的一部分,对首选散列技术/成本的更改不会使当前散列失效,框架将自动进行,在验证时使用正确的技术/成本。 如果你没有明确定义你自己的,它也处理生成一个“安全”盐。

该API公开了四个功能:

  • password_get_info() - 返回关于给定散列的信息
  • password_hash() - 创建一个密码哈希
  • password_needs_rehash() - 检查给定的哈希是否与给定的选项匹配。 用于检查散列是否符合您当前的技术/成本模式,以便您在必要时重新散列
  • password_verify() - 验证密码是否与散列匹配

目前这些函数接受PASSWORD_BCRYPT和PASSWORD_DEFAULT密码常量,这些常量在当时是同义词,不同之处在于,当支持更新,更强大的哈希算法时,PASSWORD_DEFAULT“可能会在更新的PHP版本中发生变化。” 在登录时使用PASSWORD_DEFAULT和password_needs_rehash()(并在必要时重新刷新)应确保您的哈希值对于暴力攻击具有合理的弹性,而对于您来说很少或没有工作。

编辑:我刚刚意识到这是在罗伯特K的答案中简要提及。 我会在这里留下这个答案,因为我认为它提供了一些关于它如何工作的信息,以及它为那些不了解安全性的人提供的易用性。


我在这里找到了关于这个问题的完美主题: https://crackstation.net/hashing-security.htm : 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));
}
?>

虽然这个问题已经得到解答,但我只想重申,用于散列的盐应该是随机的,并不像第一个答案中的建议那样使用电子邮件地址。

有关更多解释,请访问http://www.pivotalsecurity.com/blog/password-hashing-salt-should-it-be-random/

最近我讨论了用随机比特腌制的密码哈希是否比用可猜测或已知的盐腌过的哈希更安全。 让我们来看看:如果存储密码的系统和存储随机盐的系统一样受到危害,那么攻击者可以访问哈希以及盐,所以盐是否是随机的并不重要。 攻击者可以生成预先计算的彩虹表来破解哈希。 这里有趣的部分 - 生成预先计算的表格并不那么简单。 让我们以WPA安全模型为例。 您的WPA密码实际上永远不会发送到无线接入点。 相反,它与您的SSID(网络名称,如Linksys,Dlink等)进行哈希处理。 这是如何工作的一个非常好的解释。 为了从哈希中检索密码,您需要知道密码以及salt(网络名称)。 无线教会已经预先计算了哈希表,其中有前1000个SSID和大约100万个密码。 所有表格的大小约为40 GB。 正如您可以在他们的网站上阅读的那样,有人使用了15天的FGPA阵列3天来生成这些表格。 假设受害者使用的SSID为“a387csf3”,密码为“123456”,是否会被这些表破解? 没有! .. 这不可以。 即使密码较弱,表格也没有SSID为a387csf3的散列。 这是随机盐的美丽。 它会威慑在预先计算好的桌子上兴旺发达的饼干。 它可以阻止一个确定的黑客? 可能不会。 但是使用随机盐确实提供了额外的防御层。 在讨论这个话题时,让我们讨论在独立系统上存储随机盐的其他优点。 场景#1:密码哈希存储在系统X上,并且用于哈希的盐值存储在系统Y上。这些盐值是可猜测的或已知的(例如,用户名)方案#2:密码哈希存储在系统X上,盐值用于散列存储在系统Y上。这些盐值是随机的。 如果系统X遭到破坏,正如您所猜测的那样,在单独的系统上使用随机盐有一个巨大的优势(方案#2)。 攻击者需要猜测附加值才能破解哈希。 如果使用32位盐,则对于猜测的每个密码可能需要2 ^ 32 = 4,294,967,296(约42亿次)的迭代。


谷歌表示SHA256可用于PHP。

你一定要用盐。 我建议使用随机字节(而不是限制自己的字符和数字)。 通常,您选择的时间越长,越安全,越慢。 我猜,64字节应该没问题。





protection