我一直在阅读密码散列,但我读过的所有论坛都充斥着人们辩论其背后的理论的帖子,我并不理解。
我有一个旧的(可能非常弱)密码脚本,如下所示: $ 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的强度/安全性的东西?
答案 0 :(得分:11)
盐只能帮助你到目前为止。如果您使用的哈希算法速度非常快,生成彩虹表几乎没有成本,那么您的安全性仍会受到影响。
一些指示:
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 REQUIRES盐),你可以确定攻击几乎是不可行的,没有可笑的资金或硬件。
非便携模式下的Portable PHP Hashing Framework可让您轻松使用bcrypt生成哈希值。
您还可以使用crypt()
函数生成输入字符串的bcrypt哈希值。如果沿着那条路走下去,请确保每个哈希生成一个盐。
此类可以自动生成salt并验证输入的现有哈希值。
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);
答案 1 :(得分:4)
如果我理解正确,时间越长 盐,表越大 黑客必须生成才能 打破哈希。如果我,请纠正我 我错了。
是的,这是正确的。虽然如果有人试图打破只有一个用户的哈希,盐值就没用了。 Salts可用于防止(减慢)攻击者对所有用户哈希值进行字典攻击。
让我用一个例子解释一下。假设您的系统中有3个用户,并且您没有使用salt值,因此您的数据库会喜欢这样:
user1: hash1
user2: hash2
user3: hash3
现在让我们假设攻击者获得了数据库的副本。他现在可以通过以下方式进行字典攻击:
h = hash(possible_password)
h == hash1?
h == hash2?
h == hash3?
所以,他可以通过只调用一次哈希函数来检查3个用户中的一个是否有密码possible_password
。
不要假设您在数据库中保存与salt值结合的哈希值,如下所示:
user1: hash1_salted, salt1
user2: hash2_salted, salt2
user3: hash3_salted, salt3
攻击者再次复制您的数据库。但现在为了查看3个用户中的一个是否使用possible_password
,他必须进行以下检查:
hash(possible_password + salt1) == hash1_salted?
hash(possible_password + salt2) == hash2_salted?
hash(possible_password + salt3) == hash3_salted?
如您所见,在这种情况下,攻击者的速度减慢了3倍(系统中的用户数),因为他必须散列3个不同的字符串。这是盐值背后的一般概念,您可以在wikipedia上阅读更多内容。
但在你的情况下,盐太大了。你想要防止的是2个不同的用户哈希值具有相同的盐值。因此,例如2位长度的盐可能不是一个好主意(对于超过4个用户,它将确保2具有相同的盐值)。无论如何,盐值超过48 bits就足够了。
此外,在这里散布盐$salt2 = hash('sha256', $salt);
并没有什么意义,这可能会以某种方式减慢事情,但总的来说,在系统中添加更多复杂性在处理时被认为是错误的安全
最后,在处理安全性时,在代码中使用特定值永远不会很好,例如$secret_server_hash
,应始终避免使用这些常量值。
最好使用SHA-2而不是MD5,因为近年来在MD5中发现了一些安全性vulnerabilities(尽管它们还不是很实用)。
所以我会做这样的事情:
function createSalt()
{
$string = hash('sha256', uniqid(rand(), true));
return susbstr($string, 0, 8); // 8 characters is more than enough
}
$salt = createSalt();
$hash = hash('sha256', $hash . $password );
然后将$hash
保存在您的数据库中。
无论如何,正如一些用户已经指出的那样。而不是创建自己的安全功能(这是学习安全性的好方法),你应该更好地使用众所周知的库,这些库由更多人测试,因此可能更安全。在您的情况下,您应该查看crypt
,它可以满足您的需求。
答案 2 :(得分:2)
实际上没有必要尝试实现自己的哈希系列。这是一个实现bcrypt的简单类:
class Password
{
# return a hashed version of the plain text password.
public static function hash($plain_text, $cost_factor = 10)
{
if ($cost_factor < 4 || $cost_factor > 31)
throw new Exception('Invalid cost factor');
$cost_factor = sprintf('%02d', $cost_factor);
$salt = '';
for ($i = 0; $i < 8; ++$i)
$salt .= pack('S1', mt_rand(0, 0xffff));
$salt = strtr(rtrim(base64_encode($salt), '='), '+', '.');
return crypt($plain_text, '$2a$'.$cost_factor.'$'.$salt);
}
# validate that a hashed password is the same as the plain text version
public static function validate($plain, $hash)
{
return crypt($plain, $hash) == $hash;
}
}
使用:
$hash = Password::hash('foo');
if (Password::validate('foo', $hash)) echo "valid";
bcrypt的优点是你可以通过散列密码来计算成本(通过$cost_factor
)。这使得尝试通过强力恢复整个数据库的密码是不切实际的。