Merging other languages

This commit is contained in:
James Miranda
2016-09-23 18:01:10 -03:00
parent 380a8ee74c
commit de3c5bdaa4
490 changed files with 24539 additions and 24588 deletions

View File

@@ -1,89 +1,98 @@
# 9.5 存储密码
过去一段时间以来, 许多的网站遭遇用户密码数据泄露事件, 这其中包括顶级的互联网企业Linkedin, 国内诸如CSDN该事件横扫整个国内互联网随后又爆出多玩游戏800万用户资料被泄露另有传言人人网、开心网、天涯社区、世纪佳缘、百合网等社区都有可能成为黑客下一个目标。层出不穷的类似事件给用户的网上生活造成巨大的影响人人自危因为人们往往习惯在不同网站使用相同的密码所以一家“暴库”全部遭殃。
那么我们作为一个Web应用开发者在选择密码存储方案时, 容易掉入哪些陷阱, 以及如何避免这些陷阱?
## 普通方案
目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要(digest)恢复原始数据这也是“单向”二字的来源。常用的单向哈希算法包括SHA-256, SHA-1, MD5等。
Go语言对这三种加密算法的实现如下所示
//import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/sha1"
h := sha1.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/md5"
h := md5.New()
io.WriteString(h, "需要加密的密码")
fmt.Printf("%x", h.Sum(nil))
单向哈希有两个特性:
- 1同一个密码进行单向哈希得到的总是唯一确定的摘要。
- 2计算速度快。随着技术进步一秒钟能够完成数十亿次单向哈希计算。
结合上面两个特点,考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码。这个摘要组合也被称为`rainbow table`
因此通过单向加密之后存储的数据,和明文存储没有多大区别。因此,一旦网站的数据库泄露,所有用户的密码本身就大白于天下。
## 进阶方案
通过上面介绍我们知道黑客可以用`rainbow table`来破解哈希后的密码,很大程度上是因为加密时使用的哈希算法是公开的。如果黑客不知道加密的哈希算法是什么,那他也就无从下手了。
一个直接的解决办法是,自己设计一个哈希算法。然而,一个好的哈希算法是很难设计的——既要避免碰撞,又不能有明显的规律,做到这两点要比想象中的要困难很多。因此实际应用中更多的是利用已有的哈希算法进行多次哈希。
但是单纯的多次哈希,依然阻挡不住黑客。两次 MD5、三次 MD5之类的方法我们能想到黑客自然也能想到。特别是对于一些开源代码这样哈希更是相当于直接把算法告诉了黑客。
没有攻不破的盾,但也没有折不断的矛。现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的 “salt”。他们通常的做法是先将用户输入的密码进行一次MD5或其它哈希算法加密将得到的 MD5 值前后加上一些只有管理员自己知道的随机串再进行一次MD5加密。这个随机串中可以包括某些固定的串也可以包括用户名用来保证每个用户加密使用的密钥都不一样
//import "crypto/md5"
//假设用户名abc密码123456
h := md5.New()
io.WriteString(h, "需要加密的密码")
//pwmd5等于e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
//指定两个 salt salt1 = @#$% salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"
//salt1+用户名+salt2+MD5拼接
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)
last :=fmt.Sprintf("%x", h.Sum(nil))
在两个salt没有泄露的情况下黑客如果拿到的是最后这个加密串就几乎不可能推算出原始的密码是什么了。
## 专家方案
上面的进阶方案在几年前也许是足够安全的方案,因为攻击者没有足够的资源建立这么多的`rainbow table`。 但是,时至今日,因为并行计算能力的提升,这种攻击已经完全可行。
怎么解决这个问题呢?只要时间与资源允许,没有破译不了的密码,所以方案是:故意增加密码计算所需耗费的资源和时间,使得任何人都不可获得足够的资源建立所需的`rainbow table`
这类方案有一个特点,算法中都有个因子,用于指明计算密码摘要所需要的资源和时间,也就是计算强度。计算强度越大,攻击者建立`rainbow table`越困难,以至于不可继续。
这里推荐`scrypt`方案scrypt是由著名的FreeBSD黑客Colin Percival为他的备份服务Tarsnap开发的。
目前Go语言里面支持的库http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。
## 总结
看到这里,如果你产生了危机感,那么就行动起来:
- 1如果你是普通用户那么我们建议使用LastPass进行密码存储和生成对不同的网站使用不同的密码
- 2如果你是开发人员 那么我们强烈建议你采用专家方案进行密码存储。
## links
* [目录](<preface.md>)
* 上一节: [确保输入过滤](<09.4.md>)
* 下一节: [加密和解密数据](<09.6.md>)
# 9.5 Password storage
Over the years, many websites have suffered from breaches in user password data. Even top internet companies such as Linkedin and CSDN.net have been effected. The impact of these types of events has been felt across the entire internet, and cannot be underestimated. This is especially the case for today's internet users, who often adopt the habit of using the same password for many different websites.
As web developers, we have many choices when it comes to implementing a password storage scheme. However, this freedom is often a double edged sword. So what are the common pitfalls and how can we avoid falling into them?
## Common solutions
Currently, the most frequently used password storage scheme is to one-way hash plaintext passwords before storing them. The most important characteristic of one-way hashing is that it is infeasible to recover the original data given the hashed data -hence the "one-way" in one-way hashing. Commonly used cryptographic, one-way hash algorithms include SHA-256, SHA-1, MD5 and so on.
You can easily use the three aforementioned encryption algorithms in Go as follows:
//import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/sha1"
h := sha1.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/md5"
h := md5.New()
io.WriteString(h, "需要加密的密码")
fmt.Printf("%x", h.Sum(nil))
There are two key features of one-way hashing:
1) given a one-way hash of a password, the resulting summary is always uniquely determined.
2) calculation speed. As technology advances, it only takes a second to complete billions of one-way hash calculations.
Given the combination of the above two characteristics, and taking into account the fact that the majority of people use some combination of common passwords, an attacker can compute a combination of all the common passwords. Even though the passwords you store in your database may be hash values only, if attackers gain access to this database, they can compare the stored hashes to their precomputed hashes to obtain the corresponding passwords. This type of attack relies on what is typically called a `rainbow table`.
We can see that encrypting user data using one-way hashes may not be enough. Once a website's database gets leaked, the user's original password could potentially be revealed to the world.
## Advanced solution
Through the above description, we've seen that hackers can use `rainbow table`s to crack hashed passwords, largely because the hash algorithm used to encrypt them is public. If the hackers do not know what the encryption algorithm is, they wouldn't even know where to start.
An immediate solution would be to design your own hash algorithm. However, good hash algorithms can be very difficult to design both in terms of avoiding collisions and making sure that your hashing process is not too obvious. These two points can be much more difficult to achieve than expected. For most of us, it's much more practical to use the existing, battle hardened hash algorithms that are already out there.
But, just to repeat ourselves, one-way hashing is still not enough to stop more sophisticated hackers from reverse engineering user passwords. Especially in the case of open source hashing algorithms, we should never assume that a hacker does not have intimate knowledge of our hashing process.
Of course, there are no impenetrable shields, but there are also no unbreakable spears. Nowadays, any website with decent security will use a technique called "salting" to store passwords securely. This practice involves concatenating a server-generated random string to a user supplied password, and using the resulting string as an input to a one-way hash function. The username can be included in the random string to ensure that each user has a unique encryption key.
//import "crypto/md5"
// Assume the username abc, password 123456
h := md5.New()
io.WriteString(h, "password need to be encrypted")
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
// Specify two salt: salt1 = @#$% salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"
// salt1 + username + salt2 + MD5 splicing
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)
last :=fmt.Sprintf("%x", h.Sum(nil))
In the case where our two salt strings have not been compromised, even if hackers do manage to get their hands on the encrypted password string, it will be almost impossible to figure out what the original password is.
## Professional solution
The advanced methods mentioned above may have been secure enough to thwart most hacking attempts a few years ago, since most attackers would not have had the computing resources to compute large `rainbow table`s. However, with the rise of parallel computing capabilities, these types of attacks are becoming more and more feasible.
How do we securely store a password so that it cannot be deciphered by a third party, given real life limitations in time and memory resources? The solution is to calculate a hashed password to deliberately increase the amount of resources and time it would take to crack it. We want to design a hash such that nobody could possibly have the resources required to compute the required `rainbow table`.
Very secure systems utilize hash algorithms that take into account the time and resources it would require to compute a given password digest. This allows us to create password digests that are computationally expensive to perform on a large scale. The greater the intensity of the calculation, the more difficult it will be for an attacker to pre-compute `rainbow table`s -so much so that it may even be infeasible to try.
In Go, it's recommended that you use the `scrypt` package, which is based on the work of the famous hacker Colin Percival (of the FreeBSD backup service Tarsnap).
The packge's source code can be found at the following link: http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
Here is an example code snippet which can be used to obtain a derived key for an AES-256 encryption:
dk: = scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
You can generate unique password values using the above method, which are by far the most difficult to crack.
## Summary
If you're worried about the security of your online life, you can take the following steps:
1) As a regular internet user, we recommend using LastPass for password storage and generation; on different sites use different passwords.
2) As a Go web developer, we strongly suggest that you use one of the professional, well tested methods above for storing user passwords.
## Links
- [Directory](preface.md)
- Previous section: [SQL injection](09.4.md)
- Next section: [Encrypt and decrypt data](09.6.md)