PHP密码的安全哈希和盐值

1249

目前有人称MD5算法存在部分不安全的问题。考虑到这一点,我想知道哪种机制适合用于密码保护。

一个名为《双重哈希是否比单次哈希更不安全?》的问题建议多次哈希可能是个好主意,而《如何为单个文件实现密码保护?》则建议使用盐。

我使用PHP。我想要一个既安全又快速的密码加密系统。虽然密码哈希百万次可能更安全,但也更慢。如何在速度和安全之间取得平衡?此外,我希望结果具有恒定数量的字符。

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

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

如果我的表述不够清晰,我想知道哪些哈希函数和如何选择好的盐以获得一个既安全又快速的密码保护机制。

以下是一些相关问题,但并未完全涵盖我的问题:

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


14
http://www.openwall.com/phpass/ 是一个非常不错的库。 - Alfred
60
MD5现在已完全不安全。 - JqueryToAddNumbers
3
这取决于您使用它的目的。对于彩虹表匹配或简单暴力破解未加盐的MD5密码来说很容易,但是如果使用良好的盐值,构建用于快速破解密码集的彩虹表仍然极其不切实际,并且暴力破解也不可行。 - Craig Ringer
14
PHP 5.5+内置了安全的密码哈希函数。http://php.net/manual/en/function.password-hash.php - Terence Johnson
如何使用PHP 5.5密码哈希函数 - Sliq
显示剩余4条评论
14个回答

8

我在这里找到了一个关于此问题的完美主题: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));
}
?>

1
你给我们提供了一个没有任何用处的解决方案。 - Michael

7

我通常使用SHA1和盐值与用户ID(或其他用户特定信息)一起,有时我还会使用一个固定的盐值(这样我就有了两部分盐值)。

现在,SHA1也被认为存在某种程度的漏洞,但比MD5要好得多。通过使用盐值(任何盐值),您可以防止使用通用的彩虹表来攻击您的哈希值(有些人甚至成功地使用谷歌作为彩虹表进行搜索)。攻击者可以使用您的盐值生成彩虹表,因此您应该包括一个用户特定的盐值。这样,他们将不得不为您系统中的每条记录生成一个彩虹表,而不仅仅是为整个系统生成一个!通过这种类型的加盐,即使是MD5也相当安全。


2
常量盐不是一个好主意......可能不是致命缺陷,但它不必要地削弱了方案。 - frankodwyer
MD5和SHA1都很快,因此这是一个不好的想法。 - CodesInChaos

5

SHA1和一个盐足以满足需求(当然,这取决于你是为Fort Knox编写代码还是为你的购物清单设计登录系统)。如果SHA1对你来说不够好,可以使用SHA256

盐的作用是使哈希结果失衡。例如,空字符串的MD5哈希值为d41d8cd98f00b204e9800998ecf8427e。因此,如果有人记得这个哈希值并知道它是一个空字符串的哈希值,那么问题就来了。但是,如果该字符串带有盐(比如"MY_PERSONAL_SALT"),则“空字符串”的哈希值(即"MY_PERSONAL_SALT")变为aeac2612626724592271634fb14d3ea6,因此不容易被追溯。我的意思是,使用任何盐都比不使用要好。因此,知道使用哪种盐并不是很重要。

实际上有一些网站专门做这个 - 你可以输入一个(md5)哈希值,它就会输出一个生成该特定哈希值的已知明文。如果你能访问存储纯md5哈希值的数据库,那么你只需将管理员的哈希值输入到该服务中即可轻松登录。但是,如果密码被加盐,则这样的服务将变得无效。

此外,双重哈希通常被认为是一种不好的方法,因为它减少了结果空间。所有流行的哈希都是固定长度的。因此,你只能拥有这个固定长度的有限值,结果变得不太多样化。这可能被视为盐的另一种形式,但我不建议这样做。


目标网站不应包含任何过于敏感的内容(它不是银行),但我仍然希望它是安全的。 - luiscubal
1
双重哈希并不能减少结果空间。迭代哈希是对字典和暴力攻击的常见控制方法(它比减慢密码检查速度要慢得多)。 - frankodwyer
2
@frankodwyer: 是的,这很糟糕。sha1(sha1($foo)) 实际上会减少输出空间,因为内部函数的任何冲突都会自动成为外部函数的冲突。退化是线性的,但仍然是一个问题。迭代哈希方法在每一轮中将数据反馈回去,例如 $hash = sha1(sha1($salt . $password) . $salt)。但那还是不好的... 最好使用 PBKDF2 或 Bcrypt... - ircmaxell

-7

好的,在第一步中我们需要盐 盐必须是唯一的 所以让我们生成它

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

另外我们需要哈希值 我正在使用sha512 它是最好的,而且它在php中可用

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

现在我们可以使用这些函数来生成安全密码

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

现在我们需要将$hash_psw变量值和$salt变量保存到数据库中

授权时,我们将使用相同的步骤...

这是保护客户密码的最佳方式...

附注:对于最后两个步骤,您可以使用自己的算法... 但请确保您能够在将来生成此哈希密码 当您需要授权用户时...


4
这个问题涉及密码的哈希函数。即使加盐,一次sha512 的运算通常被认为不足以提供良好的密码保护。(还有随机数生成器不是密码学安全的,因此在生成密码时使用它是有风险的)。 - luiscubal
4
你不知道自己在做什么。阅读这篇帖子中的顶部答案,你就会明白为什么你的代码不仅存在安全隐患,而且毫无意义。 - cryptic ツ
好的。我的代码不安全。 所以请告诉我, 为什么你在算法中只使用sha256??? 我知道sha512是最好的, 为什么不使用它呢??? - shalvasoft
2
@shalvasoft sha512在一般哈希方面表现良好,但密码保护需要具有非常特定的属性的哈希值(例如,“缓慢”是一个奇怪的“好事”,而sha512相当快)。一些人已经使用sha512作为构建密码哈希函数的基础模块,但现在推荐的方法是“使用bcrypt并关注scrypt”。 - luiscubal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接