我不是密码专家。我在teamtreehouse.com上观看了有关哈希和加盐的视频,这是“使用Express和Mongo进行用户身份验证”课程的一部分。


我从视频中了解到,哈希表示密码存储在数据库中,用于防止说公司工作人员复制密码(哈希将适合于身份验证中给出的密码),并首先通过将密码传递给哈希函数(在这种情况下为简单哈希函数)来创建。 >我也了解对哈希加盐会随机化哈希。

我从视频中无法理解的是,如果我们更改(盐化),每次身份验证,如何继续正确评估哈希重新。换句话说,它仍如何与密码匹配。

编辑:也许我应该问:创建盐,将盐并置为哈希值并确保密码符合的机制是什么?到hash + salt组合。

评论

收到答案后,通常不欢迎实质性改变问题含义的编辑。就是说-创建新密码时,确定如何存储密码的代码负责生成随机盐,并将该随机盐放在该盐的哈希值和连接的密码旁边。 “机制是什么”-您是否正在要求某人深入研究PAM或其他实现并找到实际的代码?您能否以其他方式澄清这个问题的含义?

(您所说的“密码将符合哈希+盐组合”是什么意思?如果您具有密码P,哈希函数H和随机生成的盐S,那么您正在存储{S,H({S,P})} -因为S是纯文本存储的,所以您只需要一个P即可尝试重新运行H({S,P})并查看结果是否匹配;对可能的密码没有任何限制,因此不需要任何密码P需要“遵守”)。

我的意思是,据我所知,密码应符合哈希本身,因此,如果我们在哈希中添加盐,在我看来,这很可能是我错了---不同的哈希,因此密码不会遵守(像蓝色是蓝色,但蓝色和绿色是黄色,这当然不同于“仅”蓝色)。

正确-哈希不再匹配H(P)视为正确验证,而是期望匹配H({S,P})。也就是说,在将密码与存储的值进行比较时,首先将存储的盐(随机生成一次,然后在验证时再使用)输入哈希函数,然后再输入密码。 >
有一个XKCD漫画,当不使用盐时会显示出非常相似的现象。他们在这里误用了密码加密,其行为与不加盐的哈希密码非常相似。

#1 楼

直接答案

创建盐,将其并列在哈希中并确保密码符合哈希+盐组合的机制是什么?


盐是什么产生的? -一个随机数生成器,在生成或更改用户密码时使用。
哈希旁边的那个盐是什么并列的? -如何精确存储该随机数生成的结果是一个实现决策;它对算法没有影响。重要的是,盐值既要与存储的值连接在一起,又要(再次单独地)与哈希值连接在一起。

如何确保密码符合哈希+盐组合? -这里有两个阶段需要考虑:密码生成和密码检查。

在密码生成期间,会创建一个新的随机盐。然后,根据该盐和用户的密码生成要存储的哈希,然后将新生成的盐(作为纯文本)和哈希(由这两个片断生成)存储在密码数据库中。

检查密码时,将从密码数据库中检索这两个字段。检索到的盐与新输入的密码组合在一起,并且对组合的结果进行哈希处理。如果组合的哈希与先前存储的哈希匹配,则操作成功。因此,用户只需要记住他们的密码即可;从密码数据库检索了纯文本盐。因为哈希是首先从这两件事的组合中生成的,所以再次组合它们会再次生成相同的值。

在生成密码和生成密码时使用相同的盐值的事实。稍后检查密码,以确保密码+盐组合在生成时和以后进行检查时具有相同的哈希值。



Longer-Form解释
< br初始状态:无盐

假设您有密码:

123


...和一个加密哈希函数,可将该密码哈希为字符串:

GBIQ


,使得checkPassword 123 GBIQ(如下所述)发出GOOD

至关重要的是(对于其余部分才有意义)要理解,密码散列函数被设计为不可能以任何方式进行逆向,除非进行强力搜索(即,尝试所有可能的输入并将它们的输出与您要查找的内容进行比较)。

这是怎么回事

...现在,有人没有偷走您的密码数据库可以提前解决GBIQ是密码123的哈希的问题,因此,他们可以使用一个具有数以百万计的常见密码及其哈希的大型数据库,并将其与所窃取的任何密码数据库进行比较。 (此外,他们可以查看哪些用户具有相同的哈希,并立即知道这些用户具有相同的密码。)因此,如果以及何时他们确实窃取了您的密码数据库,他们只需检查一下即可。它们包含的哈希值与他们已经知道的明文密码大列表相对应-他们发现的任何匹配项都是可以破解的帐户!

添加Salt

假设您要阻止这些攻击-因此,您要做的就是生成一个随机值,每当用户更改密码时,该值就会用作“盐”。因此,当用户将密码设置为123时,假设您随机生成了盐111。然后,获取111|123的哈希,并得到P2C/的结果。然后,您将盐和(盐和密码的哈希值)哈希存储为类似的内容:

111|P2C/


P2C/5a3H不同,因为即使密码未更改,盐也是哈希值的一部分)。当用户输入密码123时,您将从存储中检索该111|前缀,将其前缀在前面,然后对111|123进行哈希处理。如果结果为P2C/,那么您知道用户输入的密码是正确的。

因此,当您为密码123生成新的哈希而不指定盐时,每个密码都不相同:

$ hashPassword 123
3sm|BVve
$ hashPassword 123
gwu|00Eq
$ hashPassword 123
84c|akWi


...但其中任何一项通过检查:

$ checkPassword 123 '3sm|BVve'
GOOD
$ checkPassword 123 'gwu|00Eq'
GOOD
$ checkPassword 123 '84c|akWi'
GOOD


...并进行更改任何部分(盐或实际的密码)都会破坏它:

$ checkPassword 123 'ABC|BVve'
BAD
$ checkPassword 123 '3sm|NOOP'
BAD


为什么起作用? salt不仅是数据库前缀,而且还是哈希函数本身输入的前缀。因此,每个哈希值都因盐而异。

优势/效果

彩虹桌攻击失败了

现在,有人不能仅仅弄清楚而不是GBIQ123的哈希值-相反,如果他们想提前计算出可能的哈希值,则需要计算并存储所有可能的盐值。即使添加少量盐,它也会很快变得很难处理(因为盐比人类生成的密码随机得多):8位盐将工作量(和所需的存储量)乘以256; 16位乘65,536; 32位乘4,294,967,296。

因此,取而代之的是,窃取密码数据库并看到ABC|BVve条目的人需要弄清楚可以将哪些字符串附加到ABC|并馈入哈希函数以获取BVve作为输出。因为123是一个非常简单的密码(并且四个字节远不是足够长的哈希值),所以他们可能会很快找到一些东西,但是他们只需要攻击一个密码,而不仅仅是在数据库之间进行大型数据库合并他们偷了他们和他们预先计算的;并且使用强密码和适当长的哈希值,这样的攻击可能会花费很长时间。

即使相同的密码也具有不同的哈希值

此外,如果您有第二位用户设置了密码,到123,几乎可以肯定他们会(如果您的盐足够长且足够随机)则具有不同的盐。因此,假设第二个用户获得盐222。由于222|123的哈希与111|123完全不同(而111|123哈希为5a3H,也许222|123哈希为CJq3,因此该条目存储为222|CJq3),因此查看密码数据库的人无法知道这两个用户的密码是相同的(因此,如果他们想找出CEO的密码,他们所要做的就是贿赂看门人,或者其他使用相同密码的人)。


示例实现

未加盐

以上实际上执行了先前示例中使用的123-> GBIQ转换:




hashPassword() {
  local password=
  openssl dgst -sha256 -binary <<<"$password" \
    | openssl enc -base64 \
    | head -c 4 \
    && printf '\n'
}


因此,对数据库条目检查密码只是直接比较:



checkPassword() {
  local userPassword= databaseEntry=
  if [[ $(hashPassword "$userPassword") = "$databaseEntry" ]]; then
    echo "GOOD"
  else
    echo "BAD"
  fi
}


盐渍

使用盐,对哈希进行哈希处理的过程发生了变化:现在它接受在检查中使用的盐,并且-必须生成与先前运行相同的哈希-再次加入相同的盐。

它还具有生成新的随机盐的能力(尽管我们可能会将这种责任加给调用者):



hashPassword() {
  local password= salt=
  if ! [[ $salt ]]; then
    # generate new random bytes for salt
    salt=$(openssl rand -base64 4 | head -c 3)
  fi
  # put salt at the front of our output
  printf '%s|' "$salt"
  # then also generate the hash WITH THE SALT AS PART OF THE HASHED VALUE
  openssl dgst -sha256 -binary <<<"${salt}|${password}" \
    | openssl enc -base64 \
    | head -c 4 \
    && printf '\n'
}

checkPassword() {
  local userPassword= databaseEntry= salt hash
  # split the database entry into the salt and the hash
  IFS='|' read -r salt hash <<<"$databaseEntry"
  # use that salt with the user's plaintext password to generate a hash
  if [[ $(hashPassword "$userPassword" "$salt") = "$databaseEntry" ]]; then
    echo GOOD
  else
    echo BAD
  fi
}


评论


$ \ begingroup $
让我们继续聊天中的讨论。
$ \ endgroup $
–查尔斯·达菲(Charles Duffy)
17-09-26在12:51

$ \ begingroup $
@Benia,实际上我确实是从记忆中寻找所需澄清的地方开始进行编辑的。希望这会有所帮助吗?
$ \ endgroup $
–查尔斯·达菲(Charles Duffy)
17年9月30日在13:06

#2 楼

盐以纯文本形式存储在哈希旁边,并且在检查密码时使用相同的盐。

其目的是为了降低获取数据库副本的攻击者的速度:


他们必须自行攻击每个密码(他们需要为每个哈希尝试每个输入,而不是尝试每个输入并将输出与所有哈希进行一次比较)。
它们无法使用大量的预先计算的查找表(“彩虹表”),因为加盐从本质上等同于为每个用户提供自己的哈希函数。

无需保留盐的秘密或比相应的盐更安全地存储它hash。

(这与Pepper相对,后者以相同的方式混合到哈希中,但每个应用程序都是固定的,并且不存储在数据库中。这试图防御获得数据库访问权的攻击者,但不是应用程序的程序代码或配置文件。)

评论


$ \ begingroup $
第一次遇到“辣椒”一词(尽管不是第一次遇到这个概念)。真好!
$ \ endgroup $
– almcnicoll
17年9月24日在20:37

$ \ begingroup $
密码哈希:加盐+胡椒粉还是足够盐?,如何安全地哈希密码?以及如何储存盐?
$ \ endgroup $
–雅科
17年9月25日在5:58

$ \ begingroup $
正如我从2:05-2:18的视频中了解到的那样,当我们通过哈希函数创建一个盐腌的哈希时,我们会为其传递一个密码和一个串联的盐(盐是由另一个函数创建的)。哈希函数如何知道与盐区分开的密码?
$ \ endgroup $
–user41937
17年9月25日在7:00

$ \ begingroup $
@Benia基本上,哈希函数可以采用串联hash = H(salt |密码)或类似的方法(hash = H(salt | H(password))),并且您需要存储salt和hash。通常具有固定长度,因此您可以将它们存储在同一字段中(然后将盐作为前n个字节分开)或单独存储。
$ \ endgroup $
–gusto2
17-09-25在8:11



#3 楼

重要的是,请牢记有关这些主题的一些内容:盐,胡椒粉,MAC,PBE哈希(即:有意的慢哈希)。


哈希的随机性不比其输入的随机性大。 。它们只是掩盖了明显的模式。这样,相等检查是唯一可用的操作。
您必须知道哈希输入中有多少熵,它来自何处以及相对于授权用户与未授权用户的熵。想到一点熵就是验证哈希值的搜索空间加倍。

如果包含盐,那么您所做的就是使所有预先计算的字典无效。您实际上是在选择从标准函数派生的随机哈希函数。即:将sha256_salt[412](x)sha256_salt[53215](x)视为不同的功能。当您使用加盐的哈希值时,从某种意义上说x实际上是“随机的”至关重要。它需要足够的熵(相对于应该无法验证密码的人而言)才能被视为该功能输入的正确“类型”。

但请记住:盐是没有用的如果散列值的熵太小,以至于甚至不需要预先计算的字典。例如:“您为哪个三字母代理商工作?”是一个不超过几熵的答案。

只需尝试:

threeLetterAgencies = ["CIA", "FBI", "NSA", "NRO", ...]
for r in database.Records:
  for a in threeLetterAgencies:
    if r.hashed == hashfn(r.salt, a):
      print("You work for %s" % a)


在这种情况下,盐基本上是没有用的。问题是密码的盐化哈希要求高度随机的输入。这就是为什么告诉您不要使用您的狗名,孩子名或词典词作为密码的原因。在这种情况下,我们正在谈论的是一个特别小的字典。

如果用户选择一个真正随机的词典单词,那么只有几十万个选择。但是用户从不太随机的集合中选择随机字典单词,并尝试通过少量的调整来弥补它,例如将E变成3,等等。一个错误的密码只有十几个熵左右。这就是为什么通常使用故意的“慢速”哈希来处理密码,以弥补它们没有我们想要的熵不足的原因。
另一方面,一个简单的MAC基本上是一个hash(encryptedInput + secretKey),因此即使您猜测输入必须是加密输入,也无法验证它,除非您也猜测secretKey。这实际上是一种单向加密,因此您可以验证自己的猜测(即:用于加密的数据库索引)。如果您使用的是32位随机密钥,那么对于没有密钥的攻击者,至少有32位随机性。

Pepper的目的是人为地放慢速度,通过从字面上添加熵位hash(password+pepper)进行哈希处理。这会减慢对任何合法或非法人员的支票;胡椒中的熵位数。如果胡椒不够大(例如只有4位);只会使暴力破解单个密码的时间延长16倍。与仅进行一次哈希验证相比,这还会导致合法密码检查花费16倍的时间。

#4 楼

注意:这是一个过于简化的解释,但它有效地传达了概念,因此可以推断到实际的实际实现。

我会对此有所了解。 。我从研究此概念时就了解了您的困惑,因此我很可能可以解决它。

哈希函数

首先,什么是哈希函数?它实际上只是将输入转化为输出的东西。它还不允许您将输出更改回输入,因此查找对应于某个输出的输入的唯一真实方法是尝试一堆输入并查看匹配的输入。

在这种情况下,值得注意的一点是,对于某些输入,它将始终产生相同的输出。因此,如果您“散列”“狗”一词,您总是会得到相同的输出,说“ qwerty”。

不加盐的弱点

说某人将所有单词散列以英语显示并存储每个输出。这意味着,如果他们看到“ qwerty”,则不必进行散列所有内容并检查输出是否为“ qwerty”的工作。他们可以在数据库中查找它,然后看到“ dog”一词散列为“ qwerty”,因此他们很容易将其破解。

如果泄漏的数据库中有多个人拥有使用密码“狗”?在输出中,“ qwerty”将出现多次,因此攻击者只需将其破解一次,他就将其全部破解。

解决方案:加盐!

解决方案当然是盐。盐实际上只是一种价值,任何价值。唯一真正的要求是该值对于每个用户都是唯一的,因此满足该要求的最简单方法是为每个用户随机生成盐。

因此,这次有两个用户,爱丽丝(Alice)和鲍勃(Bob),密码为“狗”,但他们各自都有一个唯一的盐。为了简单起见,假设它们的盐是它们的名称。

对于爱丽丝,我们对“ alice + dog”进行哈希运算,而不是对“ dog”进行哈希,对于鲍勃,我们对“ bob + dog”进行哈希运算。这次,爱丽丝的输出为“ ytrewq”,鲍勃的输出为“ poiuy”。我们将这些输出和使用的盐存储在数据库中。

现在,如果攻击者出现并试图破解Alice的密码,他必须尝试对“ alice + apple”进行哈希处理”,然后是“ alice + banana”等,每次将输出与存储的输出进行比较。如果他最终破解了它,那么他必须为Bob进行同样的工作,因为输入内容会有所不同(“ bob + apple”等)。

他不能只查找自己的所有内容数据库,因为他将不得不为每个用户创建一个单独的数据库,这违反了数据库的要点。

简单地说,这就是使用盐的要点。实际上,盐通常是随机生成的,大约为128位,以减少意外重用,并确保没有攻击者拥有该特定盐的查找表。

评论


$ \ begingroup $
@CharlesDuffy我以前没有听说过,介意分享消息吗?无论如何,我都将其切换了,因为为什么不呢。
$ \ endgroup $
–篷
17年9月26日在16:17

$ \ begingroup $
@Awn,说句公道话,它有点争议,它取决于人们更关心针对哪种攻击进行优化,并且仅当盐的大小等于或大于单个输入的大小时才有意义阻止使用哈希算法。如果您从理论上讲出了一个知道salt并试图猜测密码的攻击者,那么您要先输入密码,然后再要求salt;如果您有攻击者试图用可能的盐生成彩虹表以生成流行密码列表,则您希望盐优先,最后密码。
$ \ endgroup $
–查尔斯·达菲(Charles Duffy)
17 Sep 26 '17:27



$ \ begingroup $
(添加盐的任何一种方法都使彩虹表的存储成本变得昂贵得多;此时,您所要调整的只是计算它们的CPU成本,这是一个相对较小的因素,相当于〜1比特的盐)。
$ \ endgroup $
–查尔斯·达菲(Charles Duffy)
17-09-26在16:29



#5 楼

看来您误解了盐的功能和用途。答案分为两部分:

散列密码

密码作为单行哈希存储在数据库中。顾名思义,与加密相反的单向哈希不能逆转。但是,相同的输入将始终导致相同的哈希。因此,要确定用户是否键入了正确的密码,请对输入的内容进行哈希运算,并将其与存储的哈希值进行比较。

用于此特定目的的哈希函数还具有第二个功能:速度慢(即计算量大)。

这两个目的都是为了降低攻击者的速度,这些攻击者破坏了数据库并因此拥有密码散列。哈希本身不会告诉您密码,也无法从哈希中获取密码。通过对每个可能的密码进行哈希处理直到获得匹配为止,对密码数据库进行强行强制。这是一个缓慢的过程。

将密码哈希值设置为

此过程有两个缺点:


使用相同密码的两个用户将具有相同的密码哈希。
随着计算机变得越来越强大,并且可以使用GPU处理,至少要强制使用普通密码及其变体(字典式攻击)相当可行。最重要的是,您可以预先准备好最常见的一百万个密码,方法是对所有密码进行哈希处理并建立其哈希值数据库,然后使用反向查找来找出密码。这称为彩虹表。

简单地在对哈希进行哈希处理之前,先将随机字符串添加到密码中。由于您需要知道此字符串才能重复哈希并将用户输入与存储的哈希进行比较,因此盐也以纯文本格式存储在数据库中。

盐克服了上述两个弱点。两个用户可以使用相同的密码,但是由于他们的密码不同,所以哈希也不同。加密哈希值的结果是,即使输入中的一个字节更改也将导致完全不同的哈希值。
彩虹表也变得不切实际,因为您不仅需要存储每个密码的哈希值,而且还需要存储每个密码的哈希值密码会杂乱无章。如果盐只有2个字节,则为1 mio。单词数据库现在猛增到约650亿。而且使用较大的盐很容易且便宜,这使得彩虹表完全不切实际(使用8字节盐,即使预先计算出最常见的1,000个密码也需要存储10 ^ 22对。)

仍然存在的弱点

仍然存在一个弱点。即使我无法预先计算彩虹表,也可以暴力破解密码数据库中最常见的密码。通过简单地将所有用户盐的所有哈希值都哈希并与存储的哈希值进行比较,尝试最常见的100个左右的密码所需的时间约为几个小时。因此,强烈建议用户不要使用“密码”或“ 12345678”或诸如此类的密码。即使具有最强的保护,仍然可以强行使用极弱的密码。在现实世界中,尝试使用前100个左右的密码几乎可以确保我成功两次。

评论


$ \ begingroup $
请注意,估算彩虹表的存储大小比这里暗示的要复杂一些-彩虹表不仅是普通密码及其哈希表的简单列表,而且是使用它们存储密码的一种特殊方式。哈希算法本身作为存储/查找机制的一部分。但是,您仍然必须对它们全部进行计算,因此计算时间仍然与您必须哈希的值的数量成正比。
$ \ endgroup $
–IMSoP
17年9月27日在9:47