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 affected. The impact of these 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?
Bad solution
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 not feasible 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 hashing 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 hashing 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.
Good solution
The method 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 bcrypt
package.
The package’s source code can be found at the following link: https://github.com/golang/crypto/blob/master/bcrypt/bcrypt.go
Here is an example code snippet which can be used to hash, store and validate user passwords:
package main
import (
"fmt"
"log"
"golang.org/x/crypto/bcrypt"
)
func main() {
userPassword1 := "some user-provided password"
// Generate "hash" to store from user password
hash, err := bcrypt.GenerateFromPassword([]byte(userPassword1), bcrypt.DefaultCost)
if err != nil {
// TODO: Properly handle error
log.Fatal(err)
}
fmt.Println("Hash to store:", string(hash))
// Store this "hash" somewhere, e.g. in your database
// After a while, the user wants to log in and you need to check the password he entered
userPassword2 := "some user-provided password"
hashFromDatabase := hash
// Comparing the password with the hash
if err := bcrypt.CompareHashAndPassword(hashFromDatabase, []byte(userPassword2)); err != nil {
// TODO: Properly handle error
log.Fatal(err)
}
fmt.Println("Password was correct!")
}
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
- Previous section: SQL injection
- Next section: Encrypt and decrypt data