【问题标题】:How to generate a good salt - Is my function secure enough?如何生成好的盐 - 我的功能是否足够安全?
【发布时间】:2011-05-05 04:50:06
【问题描述】:

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

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 中是否有一种简单的方法可以做到这一点?

【问题讨论】:

    标签: php security salt


    【解决方案1】:

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

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

    对于 PHP,请考虑使用 password_hash()PASSWORD_BCRYPT 算法。无需自备盐。

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


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

    来自php sha1 page

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

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

    【讨论】:

      【解决方案2】:

      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;
      }
      

      有关哈希拉伸的更多信息,请查看this SO answer...

      【讨论】:

      • 我的函数是:function stringHashing($saltedString){$hashedString=hash('sha256',$saltedString);返回 $hashedString; }
      • @ircmaxell 这需要更新还是现在仍然相关?
      【解决方案3】:

      如果您使用的是 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

      相关:should i use urandom or openssl_random_pseudo_bytes?

      【讨论】:

      • 我同意这一点。 OP 正在使用非加密安全伪随机生成器。我只想补充一点,盐可以作为二进制文件存储在数据库中,以减少 base64 编码/解码延迟,因为哈希对身份验证登录应该是透明的。此外,如果服务器是 IIS/Win32,则 mcrypt_create_iv() 函数(如果可用)将汇集 win32 加密 API,如果您在该场景中需要良好的随机位并且不想处理 COM 接口(CAPICOM 或其他)。由于 OP 询问了随机盐,我认为@kijin 的答案是最好的解决方案。
      • +1 这是此页面上的最佳答案,虽然我不会做 base64_encode。请记住,所有消息摘要函数都是二进制安全的,并且您想要一个完整的字节盐,字母数字彩虹表很常见,但 base256 彩虹表是闻所未闻的。
      • @Rook 当然,您可以将任何二进制字符串传递给哈希函数。但是盐通常也与哈希一起存储。 (否则你以后要如何比较哈希值和密码?)看,大多数人倾向于将整个内容存储在 MySQL 等数据库的文本列中。文本列不接受任意二进制文件,因此您必须要么 将其转换为blob 列 使用类似base64 的东西。它只会让你的盐更长;加密强度是相同的。
      • @kijin 我明白你在说什么。但实际上,如果您在插入之前转义二进制文件,那么它会保留该值。您甚至可以将图像存储在数据库中,mysql_real_escape_string()
      • @Rook 即使您对其进行转义,blob 也是二进制值(例如图像)的正确数据类型。文本列附加了一个字符集,这可能会根据您使用的 DBMS 混淆您的二进制文件。我想提供一个与通常分配给密码字段的 varchar/text 类型配合得很好的答案,所以我坚持我的建议,即任何二进制盐都应该是 base64 编码的。如果你想使用 blob 来节省一些磁盘空间,那很好。
      【解决方案4】:

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

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

      【讨论】:

      • 不错的主意,但您可能希望至少先对用户名执行一个简单的功能。可以简单到重复两次,或者在最后添加一个字符。虽然您的建议在数学上是合理的,但很容易猜到。
      • 谁在乎能不能猜到?盐的目的是防止对数据库中的 all 散列使用 one 彩虹表。这就是它的工作。
      • 是的,没错。盐甚至可以是公开的,重要的是盐对于每个密码都是单独的。
      【解决方案5】:

      我会从另一个答案中获得建议并使用 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;
      }
      

      【讨论】:

        【解决方案6】:

        一个相当简单的技巧:

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

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

        【讨论】:

          【解决方案7】:

          我用这个:

          $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
          

          【讨论】:

          • Base64 编码也可以返回字符 '+' 和 '/',也许这不是 OP 需要的。然后应该为具有给定长度的特定算法创建盐,您示例中的长度是固定的并且难以预测。
          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

            <?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 提供。我还看到了熔岩灯图像的真正随机生成,这些图像通过色调和位置进行解释。 (色调是位置)

            【讨论】:

              【解决方案10】:

              如果您有 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));
              }
              

              【讨论】:

                【解决方案11】:

                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.
                

                【讨论】:

                • 这个答案很好,因为它涵盖了用于生成密码的最新 PHP 实现。但它没有回答关于如何生成好的盐的问题(有时你明确需要这个,例如用于其他散列目的)。
                • 谢谢。生成新盐很简单:$salt = password_hash("goat", PASSWORD_DEFAULT); 答案已更新。
                • @KristofferBohmann - 您不会以这种方式创建盐,结果是密码哈希。以这种方式生成盐对 cpu 来说是非常浪费的,而且“盐”不是最优的。
                猜你喜欢
                • 2016-09-27
                • 2015-01-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-11-11
                相关资源
                最近更新 更多