这种散列/验证 class 足够安全吗?

is this hashing / verification class safe enough?

我知道这类问题在这里被问了 多次 次,但在每个问题下都有建议向知道自己在做什么的人问这些问题。 (这样我们也可以了解我们在做什么)

我也找不到足够令人满意的更新 php 函数的例子..

这是我为开源项目开发的散列 class。我有 4 个步骤

  1. 密码base64编码,更安全的加密格式
  2. 使用 sha256 将密码与服务器端密钥合并
  3. 合并,合并密码和胡椒粉,随机密钥将存储在数据库中(称为胆固醇,用于防止混淆,因为作为主要哈希盐,我使用 password_hash 默认随机盐)
  4. 将结果与 password_hash 进行哈希运算,默认河豚,成本为 16

我不确定的部分是密钥长度我是否应该使用另一个 base64 编码以防止 sha256 的原始字节问题 并且生成的散列是否具有直接插入 mysql.

的正确格式

还有 class 使用本身的安全性。

这里是 class:

<?php
namespace shotwn\lazywork;

/**
* add manual here
* pepper is a static server-side key, generated with hash_hmac sha256 and random keys
*
* cholesterol is a random, 22digit?(need more?) database stored key which has been used as salt with
* hash_hmac sha256
*
* main structure is
* password_hash(hash_hmac(sha256, hash_hmac(sha256, base64_encode(password), pepper),cholesterol))
*
*/

class PasswordKitchen {
  private static $password_pepper;

  function __construct() {
       try {
         self::$password_pepper = include "/../.nope/biber.key";
       } catch (Exception $e) {
         throw new Exception("No pepper key");
       }
   }

  private function season(string $password, string $cholesterol = null) {
    //use site-wide password pepper
    $password_safe = base64_encode ($password);
    if(isset($cholesterol) && $cholesterol != null) {
      $password_cholesterol = $cholesterol;
    } else {
      $password_cholesterol = substr(base64_encode(openssl_random_pseudo_bytes(17)),0,22);; //will be user-based mysql recorded
      $password_cholesterol = str_replace("+",".",$password_cholesterol);
    }

    $password_with_pepper = hash_hmac("sha256",$password_safe,self::$password_pepper);
    $password_with_pepper_and_cholesterol = hash_hmac("sha256",$password_with_pepper,$password_cholesterol);

    $seasonedPassword = (array) [
      "password_w_PaC" => $password_with_pepper_and_cholesterol,
      "password_cholesterol" => $password_cholesterol,
    ];

    return $seasonedPassword;
  }

  public function hash(string $password, $cost = 16) {
    $options = [
        'cost' => $cost, //change for admin accounts
    ];

    $seasoning = $this->season($password);
    $seasoned_password = $seasoning["password_w_PaC"];
    $password_cholesterol = $seasoning["password_cholesterol"];

    $passwordHash = password_hash($seasoned_password, PASSWORD_DEFAULT);

    return (array) [
      "hash" => $passwordHash,
      "cholesterol" => $password_cholesterol,
    ];
  }

  public function validate(string $password, string $cholesterol, string $hash) {
    $seasonThePassword = $this->season($password, $cholesterol);
    return password_verify($seasonThePassword["password_w_PaC"], $hash);
  }
}

这是 password_hash() 已经做的:

  • 它生成安全盐
  • 并计算一个成本为 BCrypt 的散列 当前 10 的系数。

因此无需采取额外步骤来安全地存储您的密码。特别是随机盐(胆固醇)的生成已经由函数完成。您传递给函数的成本因子从未被使用。

所以我建议直接使用 password_hash():

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_DEFAULT);

// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);

如果你想要一个更高的成本系数,你可以在选项中传递它,注意将成本系数增加一个,将增加一倍的计算时间,16 似乎是一个不必要的高系数。

如果你想包含服务器端机密,有更好的方法将其添加为胡椒粉。而是加密生成的散列。您可以在我关于 safely storing passwords.

的教程末尾找到更多解释