我们如何在PHP中创建一个相当安全的密码哈希? [英] How can we create a fairly secure password hash in PHP?

查看:74
本文介绍了我们如何在PHP中创建一个相当安全的密码哈希?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在读密码散列,但是我读过的所有论坛都充斥着讨论背后的理论的人的帖子,我不太了解。



我有一个旧的(可能极其微弱的)密码脚本,其内容如下所示:
$ hash = sha1($ pass1);

 函数createSalt()
{
$ string = md5(uniqid(rand(),true));
return substr($ string,0,3);
}

$ salt = createSalt();
$ hash = sha1($ salt。$ hash);

如果我理解正确,盐的时间越长,黑客必须按顺序生成的表越大打破哈希。如果我错了,请纠正我。



我正在寻找一个更安全的新脚本,我在想这样的事情会好起来的:

 函数createSalt()
{
$ string = hash('sha256',uniqid(rand(),true));
返回$ string;
}


$ hash = hash('sha256',$ password);
$ salt = createSalt();
$ secret_server_hash ='ac1d81c5f99fdfc6758f21010be4c673878079fdc8f144394030687374f185ad';
$ salt2 = hash('sha256',$ salt);
$ hash = $ salt2。 $哈希。 $ secret_server_hash;
$ hash = hash('sha512',$ hash);

这是否更安全?这是否有明显的开销?

最重要的是,有没有更好的方法来确保我的数据库中的密码不能(真实地)通过密码分析来恢复,从而确保唯一的安全方式妥协是通过我自己的编码错误?



编辑:



阅读所有答案并进一步研究,我决定继续实施保护密码的bcrypt方法。这就是说,出于好奇的缘故,如果我要采用上面的代码并对其进行循环,例如10万次迭代,那么它是否会完成类似于bcrypt的强度/安全性?

解决方案

到目前为止,盐只能帮你。如果您使用的哈希算法速度如此之快,以至于生成彩虹表几乎没有任何成本,那么您的安全性仍然会受到影响。



几点提示:




  • 对所有密码使用单一盐分。使用随机生成的每个口令的盐。

  • 执行 NOT 重新哈希未修改的哈希(碰撞问题,见我以前的答案,你需要无限的哈希输入)。

  • Do NOT 尝试将自己的哈希算法或混合匹配算法创建为复杂操作。

  • 如果卡住了破碎/不安全/快速哈希原语,请使用加强关键词。这增加了攻击者计算彩虹表所需的时间。例如:


    $ hr
    $ b $ pre $函数strong_hash($ input ,$ salt = null,$ algo ='sha512',$ rounds = 20000){
    if($ salt === null){
    $ salt = crypto_random_bytes(16);
    } else {
    $ salt = pack('H *',substr($ salt,0,32));
    }

    $ hash = hash($ algo,$ salt。$ input);
    $ b $ for $($ i = 0; $ i <$ rounds; $ i ++){
    // $ $被添加到$ hash以创建
    // //无限输入。
    $ hash = hash($ algo,$ hash。$ input);
    }

    //返回salt和hash。要验证,只需
    //将存储的哈希值作为第二个参数传递。
    返回bin2hex($ salt)。 $散列;
    }

    函数crypto_random_bytes($ count){
    static $ randomState = null;

    $ bytes ='';
    $ b $ if(function_exists('openssl_random_pseudo_bytes')&&
    (strtoupper(substr(PHP_OS,0,3))!=='WIN')){// OpenSSL slow on Win
    $ bytes = openssl_random_pseudo_bytes($ count);
    }

    if($ bytes ===''&& is_readable('/ dev / urandom')&&
    ($ hRand = @fopen( '/ dev / urandom','rb'))!== FALSE){
    $ bytes = fread($ hRand,$ count);
    fclose($ hRand);
    }

    if(strlen($ bytes)< $ count){
    $ bytes ='';

    if($ randomState === null){
    $ randomState = microtime();
    if(function_exists('getmypid')){
    $ randomState。= getmypid();


    $ b $($ i = 0; $ i <$ count; $ i + = 16){
    $ randomState = md5(microtime( )。$ randomState);

    if(PHP_VERSION> ='5'){
    $ bytes。= md5($ randomState,true);
    } else {
    $ bytes。= pack('H *',md5($ randomState));
    }
    }

    $ bytes = substr($ bytes,0,$ count);
    }

    返回$ bytes;
    }






    不要部署自己的(固有缺陷)哈希/盐算法,为什么不使用安全专业人员开发的一个?



    使用 bcrypt 。这正是为了这一点而开发的。它的缓慢和多轮确保攻击者必须部署大量资金和硬件才能破解密码。添加到每个密码盐(bcrypt REQUIRES盐),你可以肯定,一个攻击几乎不可行,没有可笑的金额或硬件。

    便携式PHP散列框架在非便携模式下允许您使用bcrypt轻松生成散列。



    您也可以使用 crypt() 函数来生成输入字符串的bcrypt散列。如果你沿着这条路线走下去,确保每个散列产生一个盐。



    这个类可以自动生成salt并根据输入验证现有散列。 b
    $ b

      class Bcrypt {
    private $ rounds;
    public function __construct($ rounds = 12){
    if(CRYPT_BLOWFISH!= 1){
    抛出新的异常(在此安装中不支持bcrypt。 /隐窝);
    }

    $ this-> rounds = $ rounds;


    公共函数散列($ input){
    $ hash = crypt($ input,$ this-> getSalt());

    if(strlen($ hash)> 13)
    return $ hash;

    返回false;


    public function verify($ input,$ existingHash){
    $ hash = crypt($ input,$ existingHash);

    返回$ hash === $ existingHash;
    }

    private function getSalt(){
    $ salt = sprintf('$ 2a $%02d $',$ this-> rounds);

    $ bytes = $ this-> getRandomBytes(16);

    $ salt。= $ this-> encodeBytes($ bytes);

    返回$ salt;
    }

    private $ randomState;
    private function getRandomBytes($ count){
    $ bytes ='';
    $ b $ if(function_exists('openssl_random_pseudo_bytes')&&
    (strtoupper(substr(PHP_OS,0,3))!=='WIN')){// OpenSSL slow on Win
    $ bytes = openssl_random_pseudo_bytes($ count);
    }

    if($ bytes ===''&& is_readable('/ dev / urandom')&&
    ($ hRand = @fopen( '/ dev / urandom','rb'))!== FALSE){
    $ bytes = fread($ hRand,$ count);
    fclose($ hRand);
    }

    if(strlen($ bytes)< $ count){
    $ bytes ='';

    if($ this-> randomState === null){
    $ this-> randomState = microtime();
    if(function_exists('getmypid')){
    $ this-> randomState。= getmypid();


    $ b $($ i = 0; $ i <$ count; $ i + = 16){
    $ this-> randomState = md5(microtime()。$ this-> randomState);

    if(PHP_VERSION> ='5'){
    $ bytes。= md5($ this-> randomState,true);
    } else {
    $ bytes。= pack('H *',md5($ this-> randomState));
    }
    }

    $ bytes = substr($ bytes,0,$ count);
    }

    返回$ bytes;

    $ b $ private函数encodeBytes($ input){
    return strtr(rtrim(base64_encode($ input),'='),'+','。');


    你可以这样使用这段代码:

      $ bcrypt = new Bcrypt(15); 

    $ hash = $ bcrypt-> hash('password');
    $ isGood = $ bcrypt-> verify('password',$ hash);


    I have been reading up on password hashing, but all the forums I read are full of posts from people debating theory behind it that I don't really understand.

    I have an old (and presumably extremely weak) password script that reads like this: $hash = sha1($pass1);

    function createSalt()
    {
    $string = md5(uniqid(rand(), true));
    return substr($string, 0, 3);
    }
    
    $salt = createSalt();
    $hash = sha1($salt . $hash);
    

    If I understand correctly, the longer the salt, the larger the table the hacker has to generate in order to break the hash. Please correct me if I am wrong.

    I am looking to write a new script that is more secure, and I am thinking that something like this would be okay:

    function createSalt()
    {
    $string = hash('sha256', uniqid(rand(), true));
    return $string;
    }
    
    
    $hash = hash('sha256', $password);
    $salt = createSalt();
    $secret_server_hash =     'ac1d81c5f99fdfc6758f21010be4c673878079fdc8f144394030687374f185ad';
    $salt2 = hash('sha256', $salt);
    $hash = $salt2 . $hash . $secret_server_hash;
    $hash = hash('sha512', $hash );
    

    Is this more secure? Does this have a noticeable amount of overhead?

    Most importantly, is there some better way to make sure that the passwords in my database cannot be (realistically) recovered by cryptanalysis, thus ensuring that the only way security will be compromised is through my own error in coding?

    EDIT:

    Upon reading all of your answers and further reasearching, I have decided to go ahead and implement the bcrypt method of protecting my passwords. That being said, for curiosity's sake, if I were to take my above code and put a loop on it for say, 100,000 iterations, would that accomplish something similar to the strength/security of bcrypt?

    解决方案

    Salts can only help you so far. If the hashing algorithm you use is so fast that there is little to no cost for generating rainbow tables, your security is still compromised.

    A few pointers:

    • Do NOT use a single salt for all passwords. Use a randomly generated salt per password.
    • Do NOT rehash an unmodified hash (collision issue, see my previous answer, you need infinite input for hashing).
    • Do NOT attempt to create your own hashing algorithm or mix-matching algorithms into a complex operation.
    • If stuck with broken/unsecure/fast hash primitives, use key strengthening. This increases the time required for the attacker to compute a rainbow table. Example:

    function strong_hash($input, $salt = null, $algo = 'sha512', $rounds = 20000) {
      if($salt === null) {
        $salt = crypto_random_bytes(16);
      } else {
        $salt = pack('H*', substr($salt, 0, 32));
      }
    
      $hash = hash($algo, $salt . $input);
    
      for($i = 0; $i < $rounds; $i++) {
        // $input is appended to $hash in order to create
        // infinite input.
        $hash = hash($algo, $hash . $input);
      }
    
      // Return salt and hash. To verify, simply
      // passed stored hash as second parameter.
      return bin2hex($salt) . $hash;
    }
    
    function crypto_random_bytes($count) {
      static $randomState = null;
    
      $bytes = '';
    
      if(function_exists('openssl_random_pseudo_bytes') &&
          (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
        $bytes = openssl_random_pseudo_bytes($count);
      }
    
      if($bytes === '' && is_readable('/dev/urandom') &&
         ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
        $bytes = fread($hRand, $count);
        fclose($hRand);
      }
    
      if(strlen($bytes) < $count) {
        $bytes = '';
    
        if($randomState === null) {
          $randomState = microtime();
          if(function_exists('getmypid')) {
            $randomState .= getmypid();
          }
        }
    
        for($i = 0; $i < $count; $i += 16) {
          $randomState = md5(microtime() . $randomState);
    
          if (PHP_VERSION >= '5') {
            $bytes .= md5($randomState, true);
          } else {
            $bytes .= pack('H*', md5($randomState));
          }
        }
    
        $bytes = substr($bytes, 0, $count);
      }
    
      return $bytes;
    }
    


    Instead of deploying your own (inherently with flaws) hash/salt algorithm, why not use one that was developed by security professionals?

    Use bcrypt. It's been developed exactly for this in mind. It slowness and multiple rounds ensures that an attacker must deploy massive funds and hardware to be able to crack your passwords. Add to that per-password salts (bcrypt REQUIRES salts) and you can be sure that an attack is virtually unfeasible without either ludicrous amount of funds or hardware.

    The Portable PHP Hashing Framework in non-portable mode allows you to generate hashes using bcrypt easily.

    You can also use crypt() function to generate bcrypt hashes of input strings. If you go down that route, make sure you generate one salt per hash.

    This class can automatically generate salts and verify existing hashes against an input.

    class Bcrypt {
      private $rounds;
      public function __construct($rounds = 12) {
        if(CRYPT_BLOWFISH != 1) {
          throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
        }
    
        $this->rounds = $rounds;
      }
    
      public function hash($input) {
        $hash = crypt($input, $this->getSalt());
    
        if(strlen($hash) > 13)
          return $hash;
    
        return false;
      }
    
      public function verify($input, $existingHash) {
        $hash = crypt($input, $existingHash);
    
        return $hash === $existingHash;
      }
    
      private function getSalt() {
        $salt = sprintf('$2a$%02d$', $this->rounds);
    
        $bytes = $this->getRandomBytes(16);
    
        $salt .= $this->encodeBytes($bytes);
    
        return $salt;
      }
    
      private $randomState;
      private function getRandomBytes($count) {
        $bytes = '';
    
        if(function_exists('openssl_random_pseudo_bytes') &&
            (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
          $bytes = openssl_random_pseudo_bytes($count);
        }
    
        if($bytes === '' && is_readable('/dev/urandom') &&
           ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
          $bytes = fread($hRand, $count);
          fclose($hRand);
        }
    
        if(strlen($bytes) < $count) {
          $bytes = '';
    
          if($this->randomState === null) {
            $this->randomState = microtime();
            if(function_exists('getmypid')) {
              $this->randomState .= getmypid();
            }
          }
    
          for($i = 0; $i < $count; $i += 16) {
            $this->randomState = md5(microtime() . $this->randomState);
    
            if (PHP_VERSION >= '5') {
              $bytes .= md5($this->randomState, true);
            } else {
              $bytes .= pack('H*', md5($this->randomState));
            }
          }
    
          $bytes = substr($bytes, 0, $count);
        }
    
        return $bytes;
      }
    
      private function encodeBytes($input) {
        return strtr(rtrim(base64_encode($input), '='), '+', '.');
      }
    }
    

    You may use this code as such:

    $bcrypt = new Bcrypt(15);
    
    $hash = $bcrypt->hash('password');
    $isGood = $bcrypt->verify('password', $hash);
    

    这篇关于我们如何在PHP中创建一个相当安全的密码哈希?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆