且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

我们如何在PHP中创建一个相当安全的密码哈希?

更新时间:2022-12-10 13:13:51

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



几点提示:




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

  • 执行 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 // $ $被添加到$ 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 $ 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 $ 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);