验证思路的本质
验证思路的本质无非是对比当前用户输入的账号密码与正确的(且存储于数据库中的)账号密码是否一致,若一致则认为身份验证通过。
密码存储策略
store passwords as plain-text
考虑到安全性因素,原始密码当然不能明文直接存储于数据库。
Brute force Attacks
Brute force is the action of trying out all the possibilities iteratively following a generation rule. It’s like when we try to open a padlock by listing all the possibilities from 0000 to 9999 until the lock opens.
Hash(password)
经过散列处理(如MD5),并将散列后的字符串进行存储,是一种存储密码的方法。
因为,散列后的值一般是无法通过特定算法得到原始字段(对应于原始密码)。
Dictionary Attack(字典法攻击)
然而,通过一个很大的哈希映射表,并在表中搜索该MD5值,很有可能就在极短的时间内找到该散列值对应的原始字段。
这种攻击方法本质上是利用给特定的一个哈希函数一个特定的 input ,都一定会得到一个不会改变的 output。因此,攻击者可以提前构造好一个原始字符串到 hash 值的映射表。
最终,如果在这个映射表中找到了一个哈希值时(能不能找到取决于这个映射表有多大),就可以将 hash 值还原成原始密码了。
Rainbow Table Attack(彩虹表攻击)
Rainbow table
A rainbow table is a precomputed table for caching the output of cryptographic hash functions, usually for cracking password hashes. Tables are usually used in recovering a key derivation function (or credit card numbers, etc.) up to a certain length consisting of a limited set of characters.
It is a practical example of a space–time tradeoff, using less computer processing time and more storage than a brute-force attack which calculates a hash on every attempt, but more processing time and less storage than a simple key derivation function with one entry per hash.
Use of a key derivation that employs a salt makes this attack infeasible.
Hash with Salt
进而,我们采用加盐(salt) + 散列处理的方式。即,在进行散列处理之前,在原始字段的任意固定位置插入特定的字符串(salt值),其作用是让加盐后的散列结果和没有加盐的散列结果不相同,最终使得依赖彩虹表的破解方式以还原出原始密码的概率大大降低。
Hash Functions
There are a number of modern hashing algorithms that have been specifically designed for securely storing passwords. This means that they should be slow (unlike algorithms such as MD5 and SHA-1, which were designed to be fast), and how slow they are can be configured by changing the work factor.
Common mistakes
Salt re-use
注意,这里的salt值,应该是为每个密码都生成一个对应的salt值(并将这个salt值明文存储于数据库中)。 一个常见的错误,是所有用户的密码使用同样的一个salt值(硬编码(Hard Code),作为一个静态变量存储于执行的程序集中),这样称为盐值复用(Salt Reuse)。
Short salt
If a salt is too short, an attacker may precompute a table of every possible salt appended to every likely password. Using a long salt ensures such a table would be prohibitively large.
从代码实现的角度来说,在每个用户注册时,用户都需要设置自己的账号和密码。
- 这时,我们为这个用户生成一个高长度的随机数作为salt值。而不同的用户注册时,都会分别进行一次随机数生成。因此每个用户都对应一个特定的salt值;
- 将用户设置的原始明文密码+salt值作为一个整体,并进行散列处理,这个结果暂时称为“加盐后的密码散列值”;
- 将用户账号名、”加盐后的密码散列值“和盐值存储于数据库中;
- 此后每次用户登录时,将“此时用户尝试进行登录的密码”+“数据库中读取的对应该用户的盐值” 看做一个整体,并进行散列处理计算,若这个结果与“数据库中存储的加盐后的密码散列值”相同,则验证通过。
这样我们就能保证,即使我们的数据库被入侵者获取并全表下载后(拖库),黑客也几乎不可能将用户的登录密码还原出来。
加盐后的密码破解
经过加盐后,并不意味着被黑客拖库后就不能还原出原始密码,只是破解的成本被大大提高。
假设加盐值有1000种可能,一张彩虹表100G,如要暴力破解以测试这1000种可能,则需要100GB * 1000 = 10TB 的空间。
更何况我们还可以将salt值设置为一个高长度的随机数,最终使得通过暴力破解或者原始密码的可能性变为微乎其微。
Reference
- WIKIPEDIA - Salt (cryptography)
- https://en.wikipedia.org/wiki/Rainbow_table
- https://stackoverflow.com/questions/1054022/best-way-to-store-password-in-database
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#peppering
- 设计安全的账号系统的正确姿势
- 即使被拖库,也可以保证密码不泄露
- 如何正确对用户密码进行加密?