原文:https://crackstation.net/hashing-security.htm

翻译:deepL

红字部分的翻译有空再想想

Salted Password Hashing - Doing it Right

If you're a web developer, you've probably had to make a user account system. The most important aspect of a user account system is how user passwords are protected. User account databases are hacked frequently, so you absolutely must do something to protect your users' passwords if your website is ever breached. The best way to protect passwords is to employ salted password hashing. This page will explain why it's done the way it is. 如果你是一个网络开发者,你准备要建立一个用户账户系统。建立账户系统最重要的部分就是如何保护用户的密码。用户账户数据库经常被黑客攻击,所以如果你的网站被攻破,你必须要做一些事情来保护用户的密码。保护密码的最好方法是采用密码加盐散列法

There are a lot of conflicting ideas and misconceptions on how to do password hashing properly, probably due to the abundance of misinformation on the web. Password hashing is one of those things that's so simple, but yet so many people get wrong. With this page, I hope to explain not only the correct way to do it, but why it should be done that way. 关于如何正确进行密码散列,有很多相互冲突的想法和误解,这可能是由于网络上有大量的错误信息。密码散列是其中一件非常简单的事情,但却有很多人弄错了。我希望通过这个页面,能解释正确的方法,而且解释为什么应该这样做。

IMPORTANT WARNING: If you are thinking of writing your own password hashing code, please don't!. It's too easy to screw up. No, that cryptography course you took in university doesn't make you exempt from this warning. This applies to everyone: DO NOT WRITE YOUR OWN CRYPTO! The problem of storing passwords has already been solved. Use either use either phpass, the PHP, C#, Java, and Ruby implementations in defuse/password-hashing, or libsodium. 警告:如果你想写自己的密码散列代码,请不要这样做!很容易搞砸。你在大学里上的密码学课程并不能使你豁免,对所有人都是。 不要编写你自己的散列代码!存储密码的问题已经被解决了。要么使用phpass,要么使用defuse/password-hashing中的PHP、C#、Java和Ruby实现,要么使用libsodium。

If for some reason you missed that big red warning note, please go read it now. Really, this guide is not meant to walk you through the process of writing your own storage system, it's to explain the reasons why passwords should be stored a certain way. 如果你因为某些原因错过了那个大的红色警告说明,请现在就去读它。本指南并不是要引导你完成编写自己的存储系统的过程,而是要解释为什么要以某种方式存储密码。

What is password hashing?

Hash algorithms are one way functions. They turn any amount of data into a fixed-length "fingerprint" that cannot be reversed. They also have the property that if the input changes by even a tiny bit, the resulting hash is completely different (see the example above). This is great for protecting passwords, because we want to store passwords in a form that protects them even if the password file itself is compromised, but at the same time, we need to be able to verify that a user's password is correct. 哈希算法是一个单向函数。它们将 任意长度的数据 转化为 固定长度的 "指纹",且无法逆转。它们还具有这样的特性:如果输入的数据有哪怕是一点点的变化,产生的哈希值就会完全不同(见上面的例子)。这对密码保护而言是非常好的,因为我们希望以一种保护密码的形式来存储密码,即使密码文件本身被破坏;但同时,我们需要能够验证用户的明文密码是否正确。

The general workflow for account registration and authentication in a hash-based account system is as follows: 在一个用到哈希的账户系统中,账户注册和认证的一般工作流程如下。

  1. The user creates an account. 用户创建账户。
  2. Their password is hashed and stored in the database. At no point is the plain-text (unencrypted) password ever written to the hard drive. 他们的密码被哈希化并存储在数据库中。在任何时候,纯文本(未加密的)密码都不会被写入硬盘
  3. When the user attempts to login, the hash of the password they entered is checked against the hash of their real password (retrieved from the database). 当用户试图登录时,输入的密码的哈希值 与 数据库中的哈希值 进行比对。
  4. If the hashes match, the user is granted access. If not, the user is told they entered invalid login credentials. 如果哈希值匹配,用户就被授予访问权。如果不匹配,用户会被告知他们输入了无效的登录凭证。
  5. Steps 3 and 4 repeat every time someone tries to login to their account. 每次有人试图登录他们的账户时,都会重复步骤3和4。

In step 4, never tell the user if it was the username or password they got wrong. Always display a generic message like "Invalid username or password." This prevents attackers from enumerating valid usernames without knowing their passwords. 在第4步中,永远不要告诉用户是他们的用户名还是密码出错。始终显示一个通用信息,如 "无效的用户名或密码"。这可以防止攻击者在不知道密码的情况下列举出有效的用户名

It should be noted that the hash functions used to protect passwords are not the same as the hash functions you may have seen in a data structures course. The hash functions used to implement data structures such as hash tables are designed to be fast, not secure. Only cryptographic hash functions may be used to implement password hashing. Hash functions like SHA256, SHA512, RipeMD, and WHIRLPOOL are cryptographic hash functions. 要注意的是,用于 保护密码的哈希函数 与你在 数据结构课程中看到的哈希函数 是不一样的。用于实现数据结构(如哈希表)的哈希函数被设计为快速,而不是安全。只有 密码学的哈希函数 可以用来实现密码散列。SHA256、SHA512、RipeMD和WHIRLPOOL等哈希函数是 密码学哈希函数。

It is easy to think that all you have to do is run the password through a cryptographic hash function and your users' passwords will be secure. This is far from the truth. There are many ways to recover passwords from plain hashes very quickly. There are several easy-to-implement techniques that make these "attacks" much less effective. To motivate the need for these techniques, consider this very website. On the front page, you can submit a list of hashes to be cracked, and receive results in less than a second. Clearly, simply hashing the password does not meet our needs for security. 人们很容易认为,将密码通过 密码学的哈希函数运行,用户的密码就会很安全。这与事实相去甚远。有很多方法可以很快从普通哈希值中恢复密码。有几种易于实施的技术可以使这些 "攻击 "的效果大大降低。为了激发你对这些技术的渴求,请看这个网站。在首页上,你可以提交一个需要破解的哈希值列表,并在不到一秒钟的时间内收到结果。显然,简单地对密码进行 普通散列 并不能满足我们对安全的需求

The next section will discuss some of the common attacks used to crack plain password hashes.

How Hashes are Cracked

  • Dictionary and Brute Force Attacks

    Dictionary Attack

    Trying apple        : failed
    Trying blueberry    : failed
    Trying justinbeiber : failed

    ...

    Trying letmein      : failed
    Trying s3cr3t       : success!

    Brute Force Attack

    Trying aaaa : failed
    Trying aaab : failed
    Trying aaac : failed

    ...

    Trying acdb : failed
    Trying acdc : success!

    The simplest way to crack a hash is to try to guess the password, hashing each guess, and checking if the guess's hash equals the hash being cracked. If the hashes are equal, the guess is the password. The two most common ways of guessing passwords are dictionary attacks and brute-force attacks. 破解哈希值的最简单方法是 猜密码,对每个猜测进行哈希,并与 库里的哈希值 比较。如果哈希值相等,则猜测值就是目标密码。猜测密码的两种最常见的方法是字典攻击暴力攻击

    A dictionary attack uses a file containing words, phrases, common passwords, and other strings that are likely to be used as a password. Each word in the file is hashed, and its hash is compared to the password hash. If they match, that word is the password. These dictionary files are constructed by extracting words from large bodies of text, and even from real databases of passwords. Further processing is often applied to dictionary files, such as replacing words with their "leet speak" equivalents ("hello" becomes "h3110"), to make them more effective. 字典攻击 使用 一个包含单词、短语、常用密码和其他可能被用作密码的字符串的文件。文件中的每个词都要被输入,被验证系统 散列,并与 密码散列 进行比较,如果它们匹配,该词就是密码。这些文件是通过从大量的文本中,甚至是从真实的密码数据库中提取单词来构建的。字典文件 通常还会被进一步处理,例如用 "leet speak "的对应词来替换单词("hello "变成 "h3110"),以使它们更加有效。

    A brute-force attack tries every possible combination of characters up to a given length. These attacks are very computationally expensive, and are usually the least efficient in terms of hashes cracked per processor time, but they will always eventually find the password. Passwords should be long enough that searching through all possible character strings to find it will take too long to be worthwhile. 暴力攻击尝试所有可能的字符组合,直到尝试到给定的长度。这种攻击的计算成本很高,通常都是效率最低的,但它们最终总是会找到密码。所以密码应该足够长,以至于通过暴力攻击来寻找它将花费太多时间而不值得。

    There is no way to prevent dictionary attacks or brute force attacks. They can be made less effective, but there isn't a way to prevent them altogether. If your password hashing system is secure, the only way to crack the hashes will be to run a dictionary or brute-force attack on each hash. 没有办法防止字典攻击或暴力攻击可以令它们变得不那么有效,但没有办法完全防止它们。如果你的密码散列系统 足够安全,破解散列的唯一方法是对每个散列进行字典或暴力攻击。

  • Lookup Tables

    Searching: 5f4dcc3b5aa765d61d8327deb882cf99: FOUND: password5
    Searching: 6cbe615c106f422d23669b610b564800:  not in database
    Searching: 630bf032efe4507f2c57b280995925a9: FOUND: letMEin12
    Searching: 386f43fab5d096a7a66d67c8f213e5ec: FOUND: mcd0nalds
    Searching: d5ec75d5fe70d428685510fae36492d9: FOUND: p@ssw0rd!

    Lookup tables are an extremely effective method for cracking many hashes of the same type very quickly. The general idea is to pre-compute the hashes of the passwords in a password dictionary and store them, and their corresponding password, in a lookup table data structure. A good implementation of a lookup table can process hundreds of hash lookups per second, even when they contain many billions of hashes. 查找表 是一种非常有效的方法,可以快速破解许多相同类型的哈希。一般的方法是预先计算密码字典中每个密码的哈希值,并将它们及 对应的密码存储在一个表里。查找表如果用得好,可以实现可以每秒处理数百种哈希的查找,即使它们包含几十亿条的哈希值。(字典攻击的文件里仅有password,而查找表里是password+对应的散列值)

    If you want a better idea of how fast lookup tables can be, try cracking the following sha256 hashes with CrackStation's free hash cracker. 如果你想更好地了解查找表的速度,可以尝试用CrackStation的免费哈希破解器破解以下sha256哈希值。

    c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc
    08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7
    e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904
    5206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd

  • Reverse Lookup Tables

    Searching for hash(apple) in users' hash list...     : Matches [alice3, 0bob0, charles8]
    Searching for hash(blueberry) in users' hash list... : Matches [usr10101, timmy, john91]
    Searching for hash(letmein) in users' hash list...   : Matches [wilson10, dragonslayerX, joe1984]
    Searching for hash(s3cr3t) in users' hash list...    : Matches [bruce19, knuth1337, john87]
    Searching for hash(z@29hjja) in users' hash list...  : No users used this password

    This attack allows an attacker to apply a dictionary or brute-force attack to many hashes at the same time, without having to pre-compute a lookup table. 这种攻击允许攻击者同时对许多哈希值进行字典或暴力攻击,而不需要预先计算出 查询表。

    First, the attacker creates a lookup table that maps each password hash from the compromised user account database to a list of users who had that hash. The attacker then hashes each password guess and uses the lookup table to get a list of users whose password was the attacker's guess. This attack is especially effective because it is common for many users to have the same password. 首先,攻击者创建一个对照表,将已被攻破的数据库中的每个哈希值 映射到 对应的用户列表。然后,攻击者 继续猜密码 并进行散列,用 对照表 来比对这些散列值,从而反向地得到这些散列值对应的用户。这种攻击特别有效,因为许多用户都拥有相同的密码。

  • Rainbow Tables https://www.zhihu.com/question/19790488

    Rainbow tables are a time-memory trade-off technique. They are like lookup tables, except that they sacrifice hash cracking speed to make the lookup tables smaller. Because they are smaller, the solutions to more hashes can be stored in the same amount of space, making them more effective. Rainbow tables that can crack any md5 hash of a password up to 8 characters long exist. 彩虹表是一种时间-内存折衷技术。它们就像查找表一样,只不过它们牺牲了哈希破解的速度来使查找表更小。因为它们更小,所以更多的哈希值的解决方案可以存储在相同的空间里,使它们更有效。现在已经有了可以破解任何8个字符以内的密码的md5哈希值的彩虹表。

Next, we'll look at a technique called salting, which makes it impossible to use lookup tables and rainbow tables to crack a hash. 接下来,我们将看看一种叫做 "加盐 "的技术,它使人们无法使用查找表和彩虹表来破解哈希值。

Adding Salt

hash("hello")                    = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007

Lookup tables and rainbow tables only work because each password is hashed the exact same way. If two users have the same password, they'll have the same password hashes. We can prevent these attacks by randomizing each hash, so that when the same password is hashed twice, the hashes are not the same. 查找表和彩虹表之所以能够生效,是因为每个密码的散列方式完全相同。如果两个用户有相同的密码,他们就会有相同的哈希值。我们可以通过随机化每个哈希值来防止这些攻击,这样子当同一个密码被哈希两次时,哈希值是不一样的。

We can randomize the hashes by appending or prepending a random string, called a salt, to the password before hashing. As shown in the example above, this makes the same password hash into a completely different string every time. To check if a password is correct, we need the salt, so it is usually stored in the user account database along with the hash, or as part of the hash string itself. 我们可以通过 在散列前 向密码附加或预置一个随机字符串(称为)来 随机化散列。如上面的例子所示,这使得同一个密码每次散列成一个完全不同的字符串。当检查一个密码是否正确时,我们也需要,所以通常与哈希值一起存储在数据库中,或者作为哈希字符串本身的一部分。

The salt does not need to be secret. Just by randomizing the hashes, lookup tables, reverse lookup tables, and rainbow tables become ineffective. An attacker won't know in advance what the salt will be, so they can't pre-compute a lookup table or rainbow table. If each user's password is hashed with a different salt, the reverse lookup table attack won't work either. 盐不需要保密。仅仅通过随机化哈希值,查询表、反向查询表和彩虹表就变得无效了。攻击者不会事先知道盐是什么,所以他们不能预先计算出一个查询表或彩虹表。如果每个用户的密码都是用不同的盐进行散列,那么反向查找表的攻击也不会起作用。(不难理解,不同网站对给同一个用户名的盐是不同的,所以破了其他网站的库也没有用)

In the next section, we'll look at how salt is commonly implemented incorrectly. 在下一节,我们将看看通常是如何被错误使用的。

The WRONG Way: Short Salt & Salt Reuse

The most common salt implementation errors are reusing the same salt in multiple hashes, or using a salt that is too short. 最常见的错误是在多次哈希中重复使用同一个盐,或者使用一个太短的盐

Salt Reuse

A common mistake is to use the same salt in each hash. Either the salt is hard-coded into the program, or is generated randomly once. This is ineffective because if two users have the same password, they'll still have the same hash. An attacker can still use a reverse lookup table attack to run a dictionary attack on every hash at the same time. They just have to apply the salt to each password guess before they hash it. If the salt is hard-coded into a popular product, lookup tables and rainbow tables can be built for that salt, to make it easier to crack hashes generated by the product. 一个常见的错误是在每次哈希中使用相同的盐。无论盐是被硬编码到程序里,还是随机生成的,都是无效的,因为这样子对于相同的密码,仍然会有相同的哈希值。攻击者仍然可以使用反向查找表攻击,在同一时间对每个哈希值进行字典攻击。他们只需要在哈希之前对每个密码加盐。如果盐被硬编码到一个流行的产品中,就可以靠这个盐重建 查找表和彩虹表,更加容易破解产品生成的哈希值。

A new random salt must be generated each time a user creates an account or changes their password. 每次用户创建账户或更改密码时,都必须生成一个新的随机盐

Short Salt

If the salt is too short, an attacker can build a lookup table for every possible salt. For example, if the salt is only three ASCII characters, there are only 95x95x95 = 857,375 possible salts. That may seem like a lot, but if each lookup table contains only 1MB of the most common passwords, collectively they will be only 837GB, which is not a lot considering 1000GB hard drives can be bought for under $100 today. 如果盐太短,攻击者可以为每个可能的盐都建立一个查询表。例如,如果盐只有三个ASCII字符,就只有95x95x95=857,375种可能的盐。这看起来似乎很多,但如果每个查询表只包含1MB的最常见的密码,那么它们总共只有837GB,考虑到今天1000GB的硬盘可以在100美元以下买到,这并不算多。

For the same reason, the username shouldn't be used as a salt. Usernames may be unique to a single service, but they are predictable and often reused for accounts on other services. An attacker can build lookup tables for common usernames and use them to crack username-salted hashes. 出于同样的原因,用户名不应该被当作盐来使用。用户名对于一个服务来说可能是唯一的,但它们是可预测的,而且经常在其他服务的账户中重复使用。攻击者可以为常见的用户名建立查询表,并利用它们来破解用户名加盐的哈希值。

To make it impossible for an attacker to create a lookup table for every possible salt, the salt must be long. A good rule of thumb is to use a salt that is the same size as the output of the hash function. For example, the output of SHA256 is 256 bits (32 bytes), so the salt should be at least 32 random bytes. 为了让攻击者不可能为盐创建查询表,盐必须很长。一个好的经验法则是使用一个与散列函数的输出相同大小的盐。例如,SHA256的输出是256比特(32字节),所以盐应该至少是32个随机字节

The WRONG Way: Double Hashing & Wacky Hash Functions

This section covers another common password hashing misconception: wacky combinations of hash algorithms. It's easy to get carried away and try to combine different hash functions, hoping that the result will be more secure. In practice, though, there is very little benefit to doing it. All it does is create interoperability problems, and can sometimes even make the hashes less secure. Never try to invent your own crypto, always use a standard that has been designed by experts. Some will argue that using multiple hash functions makes the process of computing the hash slower, so cracking is slower, but there's a better way to make the cracking process slower as we'll see later. 本节涉及另一个常见的密码散列误区:散列算法的古怪组合。人们很容易得意忘形,试图结合不同的哈希函数,希望结果更安全。但在实践中,这样做没有什么好处。它所做的只是产生互用性问题,有时甚至会使哈希值不那么安全。不要试图创造你自己的散列函数,使用由专家设计的标准总没错。有些人认为,使用多个哈希函数会使计算哈希值的过程变慢,所以破解也会变慢,但有一个更好的方法可以使破解过程变慢,我们将在后面看到。

Here are some examples of poor wacky hash functions I've seen suggested in forums on the internet.

  • md5(sha1(password))
  • md5(md5(salt) + md5(password))
  • sha1(sha1(password))
  • sha1(str_rot13(password + salt))
  • md5(sha1(md5(md5(password) + sha1(password)) + md5(password)))

Do not use any of these.

Note: This section has proven to be controversial. I've received a number of emails arguing that wacky hash functions are a good thing, because it's better if the attacker doesn't know which hash function is in use, it's less likely for an attacker to have pre-computed a rainbow table for the wacky hash function, and it takes longer to compute the hash function. 注意:事实证明,这一节是有争议的。我收到了一些邮件,认为哈希函数组合在一起是一件好事,因为攻击者不知道使用的是哪种哈希函数,就不太可能预先计算出 彩虹表,而且计算这种哈希函数组合需要更长的时间。

An attacker cannot attack a hash when he doesn't know the algorithm, but note Kerckhoffs's principle, that the attacker will usually have access to the source code (especially if it's free or open source software), and that given a few password-hash pairs from the target system, it is not difficult to reverse engineer the algorithm. It does take longer to compute wacky hash functions, but only by a small constant factor. It's better to use an iterated algorithm that's designed to be extremely hard to parallelize (these are discussed below). And, properly salting the hash solves the rainbow table problem. 攻击者在不知道算法的情况下无法攻击,但请注意Kerckhoffs原则,攻击者通常会有机会接触到源代码(尤其是是免费或开放源代码的软件),给定目标系统的一些密码-哈希对,做算法逆向工程并不困难。计算 哈希函数组合 确实需要更长的时间,但这只是一个小影响因素。最好是使用一种 被设计成极难并行化的 迭代算法(这些将在下面讨论)。而且,对散列适当的加盐可以解决彩虹表的问题。

If you really want to use a standardized "wacky" hash function like HMAC, then it's OK. But if your reason for doing so is to make the hash computation slower, read the section below about key stretching first. 如果你真的想使用一个已经标准化的 哈希函数组合,比如HMAC,那也可以。但如果你这样做 只是为了使哈希计算的速度变慢的话,请先阅读下面关于密钥拉伸的部分。

Compare these minor benefits to the risks of accidentally implementing a completely insecure hash function and the interoperability problems wacky hashes create. It's clearly best to use a standard and well-tested algorithm. 把上述微乎其微的优点 和 可能会出现的安全风险 且 可能产生的互用性问题 放在一起比较。显然,使用一个标准的、经过良好测试的算法 更好。

Hash Collisions

Because hash functions map arbitrary amounts of data to fixed-length strings, there must be some inputs that hash into the same string. Cryptographic hash functions are designed to make these collisions incredibly difficult to find. From time to time, cryptographers find "attacks" on hash functions that make finding collisions easier. A recent example is the MD5 hash function, for which collisions have actually been found. 因为散列函数将任意长度的数据 映射成 固定长度的字符串,所以一定有一些输入会散列成相同的字符串。加密哈希函数被设计得使这种碰撞难以发现。密码学家发现 对哈希函数的 "诸多攻击",使发现碰撞变得容易了。最近的一个例子是MD5哈希函数,它的碰撞实际上已经被发现了。

Collision attacks are a sign that it may be more likely for a string other than the user's password to have the same hash. However, finding collisions in even a weak hash function like MD5 requires a lot of dedicated computing power, so it is very unlikely that these collisions will happen "by accident" in practice. A password hashed using MD5 and salt is, for all practical purposes, just as secure as if it were hashed with SHA256 and salt. Nevertheless, it is a good idea to use a more secure hash function like SHA256, SHA512, RipeMD, or WHIRLPOOL if possible. 碰撞攻击 表明除了用户的密码之外,其他字符串可能具有相同的哈希值。当然,即使是像MD5这样的弱散列函数,找到碰撞也是需要大量的专用计算能力的,所以在实践中发生碰撞的可能性非常小。就所有实际情况而言,使用MD5加盐进行哈希的密码与使用SHA256加盐进行哈希的密码一样安全。当然,可以的话,使用更安全的哈希函数,如SHA256、SHA512、RipeMD或WHIRLPOOL。

The RIGHT Way: How to Hash Properly

This section describes exactly how passwords should be hashed. The first subsection covers the basics—everything that is absolutely necessary. The following subsections explain how the basics can be augmented to make the hashes even harder to crack. 本节明确地描述 应该如何对密码进行散列。第一小节包括了 所有绝对必要的基础知识。往后的小节解释了如何对基础部分进行增强 使散列值更难破解

The Basics: Hashing with Salt

Warning: Do not just read this section. You absolutely must implement the stuff in the next section: "Making Password Cracking Harder: Slow Hash Functions".

We've seen how malicious hackers can crack plain hashes very quickly using lookup tables and rainbow tables. We've learned that randomizing the hashing using salt is the solution to the problem. But how do we generate the salt, and how do we apply it to the password? 我们已经看到恶意的攻击者如何利用 lookup tables和rainbow tables快速破解 简单的哈希。我们已经知道,用盐对散列进行随机化 能解决上述问题。但我们如何生成盐,如何将其应用于密码?

Salt should be generated using a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG). CSPRNGs are very different than ordinary pseudo-random number generators, like the "C" language's rand() function. As the name suggests, CSPRNGs are designed to be cryptographically secure, meaning they provide a high level of randomness and are completely unpredictable. We don't want our salts to be predictable, so we must use a CSPRNG. The following table lists some CSPRNGs that exist for some popular programming platforms. 盐应该使用 加密安全 伪随机数发生器(CSPRNG)生成。CSPRNG与普通的伪随机数生成器非常不同,比如 "C "语言的rand()函数。顾名思义,CSPRNG是被设计成 加密安全 的,这意味着它们有更高的随机性,并且完全不可预测。我们不希望盐是可预测的,所以我们必须使用CSPRNG。下表列出了一些存在于一些流行的编程平台的CSPRNG。

Python os.urandom

The salt needs to be unique per-user per-password. Every time a user creates an account or changes their password, the password should be hashed using a new random salt. Never reuse a salt. The salt also needs to be long, so that there are many possible salts. As a rule of thumb, make your salt is at least as long as the hash function's output. The salt should be stored in the user account table alongside the hash.盐 对不同用户\不同密码都需要是唯一的。每次用户创建账户或更改密码时,应使用新的随机盐对密码进行散列。不要重复使用一个盐。盐需要足够长,以便能生成尽可能多的盐。经验之谈,最好 使你的盐至少与哈希函数的输出一样长。盐应该与哈希值一起存储在用户账户表中。

To Store a Password

  1. Generate a long random salt using a CSPRNG. 使用CSPRNG生成一个长的随机盐。
  2. Prepend the salt to the password and hash it with a standard password hashing function like Argon2, bcrypt, scrypt, or PBKDF2. 在密码前加上盐,并用标准的密码散列函数如Argon2、bcrypt、scrypt或PBKDF2进行散列。
  3. Save both the salt and the hash in the user's database record. 将盐和散列值都保存在数据库中.

To Validate a Password

  1. Retrieve the user's salt and hash from the database. 从数据库中检索用户的盐和哈希值。
  2. Prepend the salt to the given password and hash it using the same hash function. 在输入的密码上加盐,并使用相同的哈希函数对其进行哈希。
  3. Compare the hash of the given password with the hash from the database. If they match, the password is correct. Otherwise, the password is incorrect. 比较新计算的哈希值和数据库中的哈希值。如果它们匹配,则该密码是正确的。

In a Web Application, always hash on the server. 使用网络程序时,总要在服务器上进行哈希运算

If you are writing a web application, you might wonder where to hash. Should the password be hashed in the user's browser with JavaScript, or should it be sent to the server "in the clear" and hashed there? 如果你在开发一个web app,你会想应该在哪里进行哈希。密码应该在用户的浏览器中用JavaScript进行散列呢(客户端),还是应该 "明码" 发送到服务器并在那里进行散列(服务端)?

Even if you are hashing the user's passwords in JavaScript, you still have to hash the hashes on the server. Consider a website that hashes users' passwords in the user's browser without hashing the hashes on the server. To authenticate a user, this website will accept a hash from the browser and check if that hash exactly matches the one in the database. This seems more secure than just hashing on the server, since the users' passwords are never sent to the server, but it's not. 即使你在JavaScript中对用户的密码进行了散列,你仍然必须在服务器上对散列值进行处理。试想 一个网站在 用户浏览器中对 用户的密码 进行散列,而不在服务器上进行散列。为了验证一个用户,这个网站服务器 将接受 来自浏览器的哈希值,并检查该哈希值是否与数据库中的哈希值一致。这似乎比在服务器上进行散列更安全,因为用户密码的明文从未被发送到服务器上,但事实并非如此。

The problem is that the client-side hash logically becomes the user's password. All the user needs to do to authenticate is tell the server the hash of their password. If a bad guy got a user's hash they could use it to authenticate to the server, without knowing the user's password! So, if the bad guy somehow steals the database of hashes from this hypothetical website, they'll have immediate access to everyone's accounts without having to guess any passwords. 问题在于,客户端计算出的哈希值 在逻辑上变成了用户的密码。用户需要做的就是告诉服务器他们的密码的哈希值(而非密码)。如果一个坏人得到了哈希值,他可以直接用它来验证登录,而不需要知道用户的密码明文。因此,如果坏人以某种方式窃取了这个假想网站的哈希值数据库,他们就可以立即访问每个人的账户,不需要猜测任何密码。

This isn't to say that you shouldn't hash in the browser, but if you do, you absolutely have to hash on the server too. Hashing in the browser is certainly a good idea, but consider the following points for your implementation: 这并不是说你不应该在浏览器中哈希,但如果你真这样做了,你也绝对要在服务器上同样哈希一次。在浏览器中进行哈希运算当然是个好主意,但要考虑以下几点来实现。

  • Client-side password hashing is not a substitute for HTTPS (SSL/TLS). If the connection between the browser and the server is insecure, a man-in-the-middle can modify the JavaScript code as it is downloaded to remove the hashing functionality and get the user's password. 客户端密码散列不能替代HTTPS(SSL/TLS)。如果浏览器和服务器之间的连接不安全,中间人可以在下载时修改JavaScript代码,删除散列功能并获得用户的密码。

  • Some web browsers don't support JavaScript, and some users disable JavaScript in their browser. So for maximum compatibility, your app should detect whether or not the browser supports JavaScript and emulate the client-side hash on the server if it doesn't. 有些网络浏览器不支持JavaScript,有些用户在浏览器中禁用了JavaScript。因此,为了获得最大的兼容性,你的应用程序应该检测浏览器是否支持JavaScript,如果不支持,就要在服务器上模拟客户端的哈希值。

  • You need to salt the client-side hashes too. The obvious solution is to make the client-side script ask the server for the user's salt. Don't do that, because it lets the bad guys check if a username is valid without knowing the password. Since you're hashing and salting (with a good salt) on the server too, it's OK to use the username (or email) concatenated with a site-specific string (e.g. domain name) as the client-side salt. 你也需要给客户端的哈希值加盐。明显的解决方案是让客户端脚本向服务器询问用户的盐。不要这样做,因为这让坏人在不知道密码的情况下检查一个用户名是否有效(有效的用户名就会返回盐)。由于你在服务器上也进行了散列和加盐(用一个好的盐),所以使用用户名(或电子邮件)与网站特定的字符串(如域名)连接起来作为客户端的盐是可以的。

Making Password Cracking Harder: Slow Hash Functions

Salt ensures that attackers can't use specialized attacks like lookup tables and rainbow tables to crack large collections of hashes quickly, but it doesn't prevent them from running dictionary or brute-force attacks on each hash individually. High-end graphics cards (GPUs) and custom hardware can compute billions of hashes per second, so these attacks are still very effective. To make these attacks less effective, we can use a technique known as key stretching. 盐确保攻击者不能使用特定的攻击方法,如 lookup tables 和 rainbow tables来快速破解大量的哈希值集合,但它并不妨碍他们对每个哈希值单独进行dictionary or brute-force attacks。高端显卡(GPU)和定制硬件可以每秒计算数十亿个哈希值,所以这些攻击仍然非常有效。为了使这些攻击不那么高效,我们可以使用一种被称为密钥拉伸的技术。

The idea is to make the hash function very slow, so that even with a fast GPU or custom hardware, dictionary and brute-force attacks are too slow to be worthwhile. The goal is to make the hash function slow enough to impede attacks, but still fast enough to not cause a noticeable delay for the user. 这个技术旨在让哈希过程变得非常慢,即使有快速的GPU或定制硬件,字典和暴力攻击也会很慢了。我们的目标是使哈希函数慢到足以阻碍攻击,但这个速度仍然快到不会对用户造成明显的延迟

Key stretching is implemented using a special type of CPU-intensive hash function. Don't try to invent your own–simply iteratively hashing the hash of the password isn't enough as it can be parallelized in hardware and executed as fast as a normal hash. Use a standard algorithm like PBKDF2 or bcrypt. You can find a PHP implementation of PBKDF2 here. 密钥拉伸是使用一种 特殊的CPU密集型哈希函数 实现的。不要尝试创造你自己的--简单的迭代散列是不够的,因为它可以在硬件中并行化,并像普通哈希值一样快速执行。使用一个标准的算法,如PBKDF2或bcrypt。你可以在这里找到一个PBKDF2的PHP实现。

These algorithms take a security factor or iteration count as an argument. This value determines how slow the hash function will be. For desktop software or smartphone apps, the best way to choose this parameter is to run a short benchmark on the device to find the value that makes the hash take about half a second. This way, your program can be as secure as possible without affecting the user experience. 这些算法以 安全系数迭代次数 作为参数。这个值决定了哈希函数会有多慢。对于桌面软件或智能手机应用程序来说,决定这个参数的最好方法是在设备上运行一个简短的基准测试,找到使哈希过程花费大约半秒的值。这样,你的程序就能在不影响用户体验的情况下尽可能地安全。

If you use a key stretching hash in a web application, be aware that you will need extra computational resources to process large volumes of authentication requests, and that key stretching may make it easier to run a Denial of Service (DoS) attack on your website. I still recommend using key stretching, but with a lower iteration count. You should calculate the iteration count based on your computational resources and the expected maximum authentication request rate. The Denial of Service threat can be eliminated by making the user solve a CAPTCHA every time they log in. Always design your system so that the iteration count can be increased or decreased in the future. 如果你在网络应用中使用密钥拉伸散列,请注意你将需要额外的计算资源来处理大量的认证请求,并且密钥拉伸可能使你的网站更容易受到DoS攻击。但我仍然建议使用密钥拉伸,但要降低迭代次数。你应该根据你的 计算资源预期的最大认证请求率 来计算迭代次数。DoS威胁 可以通过让用户在每次登录时 回答一个验证码来消除。在设计你的系统时,一定要让 迭代次数在将来可以增加或减少

If you are worried about the computational burden, but still want to use key stretching in a web application, consider running the key stretching algorithm in the user's browser with JavaScript. The Stanford JavaScript Crypto Library includes PBKDF2. The iteration count should be set low enough that the system is usable with slower clients like mobile devices, and the system should fall back to server-side computation if the user's browser doesn't support JavaScript. Client-side key stretching does not remove the need for server-side hashing. You must hash the hash generated by the client the same way you would hash a normal password. 如果你担心计算负担,但仍想在网络应用中使用密钥拉伸,可以考虑在用户的浏览器中用JavaScript运行密钥拉伸算法。斯坦福大学JavaScript加密库包括PBKDF2。迭代次数应设置得足够低,以便系统可用于移动设备等较慢的客户端,如果用户的浏览器不支持JavaScript,系统应在服务器端进行密钥拉伸计算。客户端密钥拉伸并不能消除对服务器端散列的需要,你必须对客户端生成的散列进行散列,就像你对普通密码进行散列一样。

Impossible-to-crack Hashes: Keyed Hashes and Password Hashing Hardware

As long as an attacker can use a hash to check whether a password guess is right or wrong, they can run a dictionary or brute-force attack on the hash. The next step is to add a secret key to the hash so that only someone who knows the key can use the hash to validate a password. This can be accomplished two ways. Either the hash can be encrypted using a cipher like AES, or the secret key can be included in the hash using a keyed hash algorithm like HMAC. 只要攻击者能够使用哈希值来检查密码猜测是对还是错,他们就可以对哈希值进行字典或暴力攻击。下一步是给哈希值添加一个 secret key密钥,这样只有知道密钥的人才能使用哈希值来验证密码。这可以通过两种方式来实现。要么使用像AES这样的密码对散列进行加密,要么使用像HMAC这样的有密钥的散列算法将密钥包含在散列中。

This is not as easy as it sounds. The key has to be kept secret from an attacker even in the event of a breach. If an attacker gains full access to the system, they'll be able to steal the key no matter where it is stored. The key must be stored in an external system, such as a physically separate server dedicated to password validation, or a special hardware device attached to the server such as the YubiHSM. 这并不像它听起来那么容易。即使在发生漏洞的情况下,密钥也必须对攻击者保密(所以不能把密钥放在源代码里)。如果攻击者获得了对系统的完全访问权,那么无论密钥存储在哪里,他们都能盗取密钥。密钥必须存储在一个外部系统中,例如一个专门用于密码验证的物理独立服务器,或者一个连接到服务器的特殊硬件设备,如YubiHSM。

I highly recommend this approach for any large scale (more than 100,000 users) service. I consider it necessary for any service hosting more than 1,000,000 user accounts. 我强烈建议任何大规模(超过100,000个用户)服务采用这种方法。我认为对于任何托管超过1,000,000个用户账户的服务来说 这是必要的。

If you can't afford multiple dedicated servers or special hardware devices, you can still get some of the benefits of keyed hashes on a standard web server. Most databases are breached using SQL Injection Attacks, which, in most cases, don't give attackers access to the local filesystem (disable local filesystem access in your SQL server if it has this feature). If you generate a random key and store it in a file that isn't accessible from the web, and include it into the salted hashes, then the hashes won't be vulnerable if your database is breached using a simple SQL injection attack. Don't hard-code a key into the source code, generate it randomly when the application is installed. This isn't as secure as using a separate system to do the password hashing, because if there are SQL injection vulnerabilities in a web application, there are probably other types, such as Local File Inclusion, that an attacker could use to read the secret key file. But, it's better than nothing. 如果你负担不起多个专用服务器或特殊的硬件设备,你然可以在一个标准的网络服务器上获得 带密钥哈希 的一些好处。大多数数据库都是使用SQL注入攻击而被攻破的,在大多数情况下,攻击者并不能访问本地文件系统(如果你的SQL服务器有这个功能,就禁用本地文件系统访问)。如果你生成一个随机密钥,并将其存储在一个不能从网络上访问的文件中,并将其包含在加盐的哈希值中,那么倘若你的数据库被SQL注入攻击攻破,哈希值也不会受到攻击。不要在源代码中硬编码一个密钥,应该在安装应用程序时随机生成。这并不像使用一个单独的系统来做密码散列那样安全,因为如果网络应用中存在SQL注入漏洞,可能还有其他类型的漏洞,如 本地文件包含(漏洞),攻击者可以用它来读取 密钥文件。但是,有总比没有好。

Please note that keyed hashes do not remove the need for salt. Clever attackers will eventually find ways to compromise the keys, so it is important that hashes are still protected by salt and key stretching. 请注意,带密钥的哈希值一样是需要盐的。聪明的攻击者最终会找到破坏密钥的方法,所以 哈希值加盐密钥拉伸(key stretching是密钥拉伸,用来变速的;secret key密钥,用来限制验证权限的。注意区分) 依然不可少。

Other Security Measures

Password hashing protects passwords in the event of a security breach. It does not make the application as a whole more secure. Much more must be done to prevent the password hashes (and other user data) from being stolen in the first place. 密码散列 可以在出现安全漏洞的情况下保护密码。它并不能使整个应用程序更加安全。必须做更多的工作来防止 密码散列(和其他用户数据)从一开始就被盗。

Even experienced developers must be educated in security in order to write secure applications. A great resource for learning about web application vulnerabilities is The Open Web Application Security Project (OWASP). A good introduction is the OWASP Top Ten Vulnerability List. Unless you understand all the vulnerabilities on the list, do not attempt to write a web application that deals with sensitive data. It is the employer's responsibility to ensure all developers are adequately trained in secure application development. 即使是有经验的开发人员也必须接受安全教育,以便编写安全的应用程序。学习网络应用程序漏洞的一个好资源是开放网络应用安全项目(OWASP)。OWASP的十大漏洞列表就是一个很好的入门介绍。除非你了解清单上的所有漏洞,否则不要试图编写处理敏感数据的程序。雇主有责任确保所有开发人员在安全应用开发方面得到充分的培训。

Having a third party "penetration test" your application is a good idea. Even the best programmers make mistakes, so it always makes sense to have a security expert review the code for potential vulnerabilities. Find a trustworthy organization (or hire staff) to review your code on a regular basis. The security review process should begin early in an application's life and continue throughout its development. 让第三方对你的应用程序进行 "渗透测试 "是一个好主意。即使是最好的程序员也会犯错,所以让安全专家来审查代码的潜在漏洞总是有意义的。找一个值得信赖的组织(或雇佣员工),定期审查你的代码。安全审查过程应该在应用程序的早期开始,并在整个开发过程中持续进行。

It is also important to monitor your website to detect a breach if one does occur. I recommend hiring at least one person whose full time job is detecting and responding to security breaches. If a breach goes undetected, the attacker can make your website infect visitors with malware, so it is extremely important that breaches are detected and responded to promptly. 同样重要的是,监测你的网站,以便在发生漏洞时发现。我建议至少雇佣一个全职工作是检测和应对安全漏洞的人。如果有一个漏洞没被发现,攻击者可以使你的网站感染恶意软件,所以及时发现和应对漏洞是极其重要的。

flask 8用户认证_Salted Password Hashing相关推荐

  1. Nginx应用(搭建网站、设置权限、用户认证)

    利用nginx服务搭建一个网站(www) 第一个历程:编写虚拟主机配置文件 cd /etc/nginx/conf.d/ #扩展名只能是.conf.因为主配置文件里加载的.conf #cat /etc/ ...

  2. Web开发 ------ 基于Flask的 任务清单管理系统(二):用户认证

    文章目录 任务清单管理系统(二) 一.用户认证 1.数据库模型 (1)技术要点 (2)核心代码 提交数据库 提交github: (3)测试代码 2.Flask-Login优化数据库模型 (1)技术要点 ...

  3. Flask Web开发基础实战-1.0用户认证与注册模块

    目录: 前言: 一,账户密码安全性 使用Werkzeug实现密码散列 二,创建登录的认证蓝本 三,Flask-Login认证用户 1.用于登录的用户数据库模型 2.保护路由 3.添加登录表单 4.登入 ...

  4. 使用Flask实现用户登陆认证的详细过程

    用户认证的原理 在了解使用Flask来实现用户认证之前,我们首先要明白用户认证的原理.假设现在我们要自己去实现用户认证,需要做哪些事情呢? 首先,用户要能够输入用户名和密码,所以需要网页和表单,用以实 ...

  5. 使用 Jwt-Auth 实现 API 用户认证以及无痛刷新访问令牌

    最近在做一个公司的项目,前端使用 Vue.js,后端使用 Laravel 构建 Api 服务,用户认证的包本来是想用 Laravel Passport 的,但是感觉有点麻烦,于是使用了 jwt-aut ...

  6. Laravel 5.5 使用 Jwt-Auth 实现 API 用户认证以及无痛刷新访问令牌

    最近在做一个公司的项目,前端使用 Vue.js,后端使用 Laravel 构建 Api 服务,用户认证的包本来是想用 Laravel Passport 的,但是感觉有点麻烦,于是使用了 jwt-aut ...

  7. Laravel核心代码学习--用户认证系统的实现细节

    用户认证系统的实现细节 上一节我们介绍了Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthMa ...

  8. Laravel核心解读--用户认证系统的实现细节

    用户认证系统的实现细节 上一节我们介绍来Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthMa ...

  9. 加盐哈希-Salted Password Hashing

    (译文:http://blog.jobbole.com/61872/ (中文) 原文:https://crackstation.net/hashing-security.htm (英文) 个人推荐英文 ...

最新文章

  1. !! 机器学习常用工具
  2. div实现半透明遮盖层
  3. PAT甲级1013 Battle Over Cities:[C++题解]并查集、结构体存边
  4. 淮南:发力“大数据”能源城激活新动能
  5. 垂直领域出海,多语言预训练好使吗?
  6. Vue的响应式及相关问题
  7. python切换ip群发邮件_python获取外网IP并发邮件的实现方法
  8. 玩转 Linux 常用命令
  9. python3.8安装教程 20200701
  10. sql 2005 中分页
  11. 主流量化交易的几种策略模型
  12. Django REST Framework——3. 序列化器(Serializer)
  13. 7.物理地址(MAC地址)是什么?
  14. vue POST 请求方式下载excel文件
  15. Docker安装及使用
  16. 高通耳机阻抗估算流程
  17. 如何减小电压跟随器输出电阻_逐次比较式模数转换器如何获取最佳采样频率
  18. 别看手机了,收心了收心了
  19. 说话技巧 为人处事
  20. 解析wiki数据(带标点,保留格式)

热门文章

  1. Hadoop集群上的Hive安装时进行初始化元数据信息出现错误HiveSchemaTool:Parsing failed. Reason: Missing required option:
  2. vue 页面卡顿(数据量大)
  3. 自然语言处理--信息模型
  4. Data()笔记之getDay()的基本用法
  5. 英特尔 QSV 在 FFMPEG 中的使用(Linux)
  6. China Mobile announces acquisition of China Railway Communication, unveiling industry reshuffle
  7. Mybatis+MySql中字段名为保留字(关键字)的情况
  8. FastRTPS - eProsima FASTRTPSGEN Manual
  9. 华大(小华)HC32L130工程创建
  10. uni-app视图容器之movable-view