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

标签 php passwords hash

我一直在阅读有关密码哈希的内容,但我读过的所有论坛都充满了人们争论其背后的理论的帖子,而我并不真正理解。

我有一个旧的(并且可能非常弱)密码脚本,其内容如下: $hash = sha1($pass1);

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

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

如果我理解正确的话,盐越长,黑客为了破解哈希值必须生成的表就越大。如果我错了,请纠正我。

我正在寻找编写一个更安全的新脚本,并且我认为这样的事情就可以了:

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

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

最重要的是,是否有更好的方法来确保我的数据库中的密码无法(实际上)通过密码分析恢复,从而确保安全性受到损害的唯一方法是通过我自己的编码错误?

编辑:

在阅读了您的所有答案并进一步研究后,我决定继续实现 bcrypt 方法来保护我的密码。话虽这么说,出于好奇,如果我采用上面的代码并对其进行循环(例如 100,000 次迭代),是否会实现与 bcrypt 的强度/安全性类似的效果?

最佳答案

盐只能帮助你到目前为止。如果您使用的哈希算法非常快,以至于生成彩虹表的成本几乎为零,那么您的安全性仍然会受到损害。

一些提示:

  • 请勿对所有密码使用单一盐。每个密码使用随机生成的盐。
  • 不要不要重新散列未修改的散列(冲突问题,see my previous answer,您需要无限输入进行散列)。
  • 请勿尝试创建自己的哈希算法或将算法混合到复杂的操作中。
  • 如果遇到损坏/不安全/快速哈希原语,请使用 key strengthening 。这增加了攻击者计算彩虹表所需的时间。示例:
<小时/>
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;
}
<小时/>

与其部署您自己的(本身就有缺陷的)哈希/盐算法,为什么不使用由安全专业人员开发的算法呢?

使用bcrypt。它正是为此而开发的。它的缓慢性和多轮性确保攻击者必须部署大量资金和硬件才能破解您的密码。添加每个密码的盐(bcrypt 需要盐),您可以确定,如果没有大量的资金或硬件,攻击实际上是不可行的。

Portable PHP Hashing Framework在非可移植模式下,您可以轻松使用 bcrypt 生成哈希值。

您还可以使用crypt()函数生成输入字符串的 bcrypt 哈希值。如果您沿着这条路线走下去,请确保每个哈希生成一个盐。

此类可以自动生成盐并根据输入验证现有哈希值。

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), '='), '+', '.');
  }
}

您可以这样使用此代码:

$bcrypt = new Bcrypt(15);

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

关于php - 我们如何在 PHP 中创建相当安全的密码哈希?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6340105/

相关文章:

php - Postgres,查询错误

c# - 散列和加盐密码字段

php - 安全地发送纯文本密码?

java - 移植Java : C++ --> Class types

php - 从 HTML 页面创建 CSV 文件

javascript - 在获取内容之前 AJAX 重新加载页面

c - 在 C 中正确使用 mod 运算符

hash - "over"中的 "overpass-the-hash"是什么意思?

php - AJAX 帖子 : echoing posted anchor tag value

java - 如何存储 MySQL 数据库密码和用户名以访问数据库?