40

这是我用来生成随机盐的函数:

function generateRandomString($nbLetters){
    $randString="";
    $charUniverse="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    for($i=0; $i<$nbLetters; $i++){
       $randInt=rand(0,61);
        $randChar=$charUniverse[$randInt];
        $randString=$randomString.$randChar;
    }
    return $randomString;
}

这是一个非商业网站。它仅用于生成盐(存储在数据库中并与用户提交的密码一起用于散列)。

这合适吗?我应该使用更大的字符子集吗?如果是这样,在 PHP 中是否有一种简单的方法可以做到这一点?

4

11 回答 11

45

如果您要对密码进行哈希处理,则应使用不需要您自己生成盐的现代哈希算法。使用弱散列算法会给您和您的用户带来危险。我最初的答案是八年前写的。时代变了,现在密码散列变得容易多了。

您应该始终使用内置函数来散列/检查密码。在任何时候使用自己的算法都会带来大量不必要的风险。

对于 PHP,考虑使用password_hash()PASSWORD_BCRYPT算法。无需提供自己的盐。

以下是我的原始答案,供后人参考:


警告:根据uniqid的文档,以下实现不会产生不可预测的盐。

php sha1 页面

$salt = uniqid(mt_rand(), true);

这看起来比您建议的更简单,更有效(因为每个都是独一无二的)。

于 2010-11-04T17:21:25.010 回答
18

如果您在 Linux 上,/dev/urandom这可能是您最好的随机性来源。它由操作系统本身提供,因此保证比任何 PHP 内置函数更可靠。

$fp = fopen('/dev/urandom', 'r');
$randomString = fread($fp, 32);
fclose($fp);

这将为您提供 32 个字节的随机 blob。您可能希望通过类似base64_encode()使其清晰易读的方法来传递它。无需自己玩弄角色。

编辑 2014:在 PHP 5.3 及更高版本中,openssl_random_pseudo_bytes()是获取一堆随机字节的最简单方法。在 *nix 系统上,它/dev/urandom在幕后使用。在 Windows 系统上,它使用 OpenSSL 库中内置的不同算法。

相关:https ://security.stackexchange.com/questions/26206

相关:我应该使用 urandom 还是 openssl_random_pseudo_bytes?

于 2010-11-04T17:55:20.207 回答
8

password_hash()在 PHP 5.5 和更新版本中可用。我很惊讶地发现这里没有提到它。

使用 password_hash() 不需要生成盐,因为盐是使用 bcrypt 算法自动生成的——因此不需要组成一组字符。

相反,使用 password_verify() 将用户提交的密码与存储在数据库中的唯一密码哈希进行比较。只需将用户名和密码哈希存储在用户数据库表中,然后您就可以使用 password_verify() 将其与用户提交的密码进行比较。

密码哈希()如何工作:

在将字符串存储在数据库中时,password_hash() 函数会输出唯一的密码哈希——建议该列最多允许 255 个字符。

$password = "goat";
echo password_hash($password, PASSWORD_DEFAULT);
echo password_hash($password, PASSWORD_DEFAULT);
echo password_hash($password, PASSWORD_DEFAULT);

// Output example (store this in the database)
$2y$10$GBIQaf6gEeU9im8RTKhIgOZ5q5haDA.A5GzocSr5CR.sU8OUsCUwq  <- This hash changes.
$2y$10$7.y.lLyEHKfpxTRnT4HmweDKWojTLo1Ra0hXXlAC4ra1pfneAbj0K
$2y$10$5m8sFNEpJLBfMt/3A0BI5uH4CKep2hiNI1/BnDIG0PpLXpQzIHG8y 

要验证散列密码,请使用password_verify()

$password_enc = password_hash("goat", PASSWORD_DEFAULT);
dump(password_verify('goat', $password_enc)); // TRUE
dump(password_verify('fish', $password_enc)); // FALSE

如果您愿意,可以选择手动添加盐,如下所示:

$password = 'MyPassword';
$salt = 'MySaltThatUsesALongAndImpossibleToRememberSentence+NumbersSuch@7913';
$hash = password_hash($password, PASSWORD_DEFAULT, ['salt'=>$salt]);
// Output: $2y$10$TXlTYWx0VGhhdFVzZXNBT.ApoIjIiwyhEvKC9Ok5qzVcSal7T8CTu  <- This password hash not change.
于 2015-10-23T04:57:39.790 回答
4

替换rand(0,61)mt_rand(0, 61),你应该没问题(因为mt_rand更擅长产生随机数)......

但比盐的强度更重要的是你散列它的方式。如果你有一个很好的盐常规,但只做md5($pass.$salt),你就扔掉了盐。我个人建议拉伸散列......例如:

function getSaltedHash($password, $salt) {
    $hash = $password . $salt;
    for ($i = 0; $i < 50; $i++) {
        $hash = hash('sha512', $password . $hash . $salt);
    }
    return $hash;
}

有关哈希拉伸的更多信息,请查看此 SO 答案...

于 2010-11-04T17:41:06.533 回答
4

我会从另一个答案中获取建议并使用mt_rand(0, 61),因为 Mersenne Twister 会产生更好的熵。

此外,您的函数实际上是两部分:生成随机$nbLetters数字和在 base62 中编码。对于在几年后偶然发现它的维护程序员(也许是你!)来说,这将使事情变得更加清晰:

// In a class somewhere
private $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

private function getBase62Char($num) {
    return $chars[$num];
}

public function generateRandomString($nbLetters){
    $randString="";

    for($i=0; $i < $nbLetters; $i++){
        $randChar = getBase62Char(mt_rand(0,61));
        $randString .= $randChar;
    }

    return $randomString;
}
于 2010-11-04T19:01:13.660 回答
2

这是我的方法,它使用来自大气噪声的真正随机数。它都与伪随机值和字符串混合在一起。洗牌和散列。这是我的代码:我称之为矫枉过正。

<?php
function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $randomString;
}

function get_true_random_number($min = 1, $max = 100) {
    $max = ((int) $max >= 1) ? (int) $max : 100;
    $min = ((int) $min < $max) ? (int) $min : 1;
    $options = array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => false,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_ENCODING => '',
        CURLOPT_USERAGENT => 'PHP',
        CURLOPT_AUTOREFERER => true,
        CURLOPT_CONNECTTIMEOUT => 120,
        CURLOPT_TIMEOUT => 120,
        CURLOPT_MAXREDIRS => 10,
    );

    $ch = curl_init('http://www.random.org/integers/?num=1&min='
        . $min . '&max=' . $max . '&col=1&base=10&format=plain&rnd=new');
    curl_setopt_array($ch, $options);
    $content = curl_exec($ch);
    curl_close($ch);

    if(is_numeric($content)) {
        return trim($content);
    } else {
        return rand(-10,127);
    }
}

function generateSalt() {
    $string = generateRandomString(10);
    $int = get_true_random_number(-2,123);
    $shuffled_mixture = str_shuffle(Time().$int.$string);
    return $salt = md5($shuffled_mixture);
}

echo generateSalt();
?>

大气噪声由 random.org 提供。我还看到了熔岩灯图像的真正随机生成,这些图像通过色调和位置进行解释。(色调是位置)

于 2014-04-08T20:42:49.357 回答
2

如果您有 Windows 并且无法执行 /dev/random,这是一个更好的方法。

//Key generator
$salt = base64_encode(openssl_random_pseudo_bytes(128, $secure));
//The variable $secure is given by openssl_random_ps... and it will give a true or false if its tru then it means that the salt is secure for cryptologic.
while(!$secure){
    $salt = base64_encode(openssl_random_pseudo_bytes(128, $secure));
}
于 2014-12-30T08:40:46.300 回答
1

我认为一个很好的盐例如是用户名(如果你在谈论 pw 散列并且用户名没有改变。)

您不需要生成任何东西,也不需要存储更多数据。

于 2010-11-04T18:07:18.450 回答
1

一个相当简单的技术:

$a = array('a', 'b', ...., 'A', 'B', ..., '9');
shuffle($a);
$salt = substr(implode($a), 0, 2);  // or whatever sized salt is wanted

与 uniqid() 不同,它生成随机结果。

于 2013-05-30T00:57:27.957 回答
1

我用这个:

$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
于 2014-02-27T06:49:22.827 回答
0

如果您想要最终唯一的盐,您应该使用用户输入和要求的唯一值,例如电子邮件或用户名,然后使用 sha1 对其进行散列,然后将其合并 - 连接 - 与您的代码生成的盐值。

另一个,你必须$charUniverse通过一些特殊字符来扩展,例如@,!#-等。

于 2014-04-06T22:01:41.973 回答