最常见的密码加盐方法有哪些?

信息安全 密码 验证 密码学 哈希
2021-08-17 05:03:19

我了解到 Sun 的人使用登录名作为密码散列的盐。这是一种常见的方法吗?最常见的盐值是多少?

2个回答

使用登录名作为 salt 很常见,但并不真正推荐:它只完成了部分工作。盐的要点是在所有散列密码实例中尽可能唯一,以阻止任何作为并行攻击的尝试(同时攻击多个散列密码,使用预先计算的表,如彩虹表......是并行攻击)。在不同的系统上,几个用户可能共享相同的登录名(那里有很多琼斯和史密斯)。此外,给定用户可以在保留其姓名的同时更改其密码;但即使是旧密码对攻击者也很有价值(如果只是因为旧密码通常是用户下次被指示更改密码时将重用的密码;而且,许多用户将在其他系统上使用相同的密码) .

生成盐推荐方法是使用一个好的随机数生成器(理想情况下加密强 RNG)并让它生成一堆随机字节。如果盐足够长(16 字节或更多),那么这可以确保所需的唯一性以压倒性的可能性,并且没有太多麻烦(例如,无需在现有盐的数据库中查找)。

我们只能希望这种推荐的方式也是或即将成为“最常见”的方式。

以下是一个不错的加盐和散列密码系统。有几点要记住,这就是为什么它看起来很长。这是一个关于盐哈希应该做什么的典型例子。我不打算讨论 Sun 所做的事情。

最好将盐和密码作为原始字节( , 等)处理byte[]unsigned char*以防使用的语言处理其他形式的文本字符串。

让我们使用加密强的伪随机数生成器生成新的盐来生成盐。在 Ruby 中,该库被方便地称为securerandom. 大多数语言还带有随机数生成器,但这对于盐来说是不够的。

让我们使用SHA256作为我们的底层哈希算法。所有散列算法都有一个输出大小,即散列函数输出的位大小。所有哈希算法也有一个特征内部块大小,它与输出大小不同。让我们使用与哈希算法的块大小一样大的盐。我们这样做是因为这是在散列密码之前我们可以添加到密码中的最大熵。如果我们使用比底层散列算法的块大小更大的盐,那么 HMAC 将简单地首先对盐进行散列以获得更小的盐,从而降低其熵,然后再将其与密码混合。SHA256 的块大小是 512 位(= 64 字节),所以让我们使用 64 字节的盐。

让我们直接使用HMAC -SHA256 而不是 SHA256,这样可以确保密码安全。我们将使用 salt 作为 HMAC-SHA256 的关键参数,而不是将其添加到密码中。HMAC-SHA256 包装了 SHA256 并做一些额外的工作来将盐与密码合并。

最后,SHA-256 等哈希算法的设计速度很快。无论我们的代码总体上做什么,它都可能花费 1% 的时间来验证用户和 99% 的其他内容。如果攻击者获得密码文件的副本,他的代码可能会花费 100% 的时间来暴力破解密码。所以让我们让整个算法变得超级慢:不要太慢以至于影响到我们的其余代码,但要慢到足以阻止攻击者。让我们取一个称为工作因数(可能是 16)的参数,并迭代 HMAC-SHA256 2 ^ 工作因数次(在示例中为 65536 次)。如果工作因数是 2,那么看起来像

HMAC-SHA256(salt,
  HMAC-SHA256(salt,
    HMAC-SHA256(salt,
      HMAC-SHA256(salt,
        password
      )
    )
  )
)

Ruby 中一个很好但很简单的解决方案:

require "securerandom"
require "openssl"

# method for generating a salt and computing a hashed
# version of a password
def salt_hash(password, work_factor)

  # get raw bytes from the password string; only needed in Ruby >= 1.9
  password = password.force_encoding(Encoding::ASCII_8BIT) if defined?(Encoding)

  # use SHA256 as the underlying hash algorithmn
  hash = OpenSSL::Digest::SHA256.new

  # generate a salt as long as the hash algorithm's block size
  # using a cryptographically strong pseudo-random number generator
  salt = SecureRandom.random_bytes(hash.new.block_length)

  # use HMAC, feeding it two parameters: the salt and the
  # underlying hash; since the underlying hash is SHA256,
  # this turns it into HMAC-SHA256
  hmac = OpenSSL::HMAC.new(salt, hash)

  # iterate running HMAC-SHA256 on the password 2^work-factor
  # times to make this function slow enough to discourage an
  # attacker, but not too slow as to make the service unresponsive
  iterations = 2 ** work_factor
  iterations.times do
    password = hmac.digest(password)
  end

  # return the salt and the hashed password to whoever called
  # this method
  {:salt => salt, :hash => password, :work => work_factor}

end

# how to call the method
salt_hash("seekrit", 16)
# and the output would be
 => {:salt => "raw bytes", :hash => "raw bytes", :work => 16}