Use sensible way of storing user passwords
This removes a lot of confusion for users and only provides them with a good and a bad solution. We switched from scrypt to bcrypt, as that makes it even easier to store and validate passwords.
This commit is contained in:
85
en/09.5.md
85
en/09.5.md
@@ -4,11 +4,11 @@ Over the years, many websites have suffered from breaches in user password data.
|
||||
|
||||
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
|
||||
## 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.
|
||||
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 encryption algorithms in Go as follows:
|
||||
You can easily use the three aforementioned hashing algorithms in Go as follows:
|
||||
|
||||
//import "crypto/sha256"
|
||||
h := sha256.New()
|
||||
@@ -32,56 +32,55 @@ There are two key features of one-way hashing:
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Advanced solution
|
||||
## Good 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.
|
||||
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.
|
||||
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).
|
||||
In Go, it's recommended that you use the `bcrypt` package.
|
||||
|
||||
The package's source code can be found at the following link: http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
|
||||
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 obtain a derived key for an AES-256 encryption:
|
||||
Here is an example code snippet which can be used to hash, store and validate user passwords:
|
||||
|
||||
dk: = scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
|
||||
package main
|
||||
|
||||
You can generate unique password values using the above method, which are by far the most difficult to crack.
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user