Skip to content

Commit

Permalink
Use sensible way of storing user passwords
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
leonklingele committed Apr 11, 2017
1 parent 041db8f commit a037c3c
Showing 1 changed file with 37 additions and 38 deletions.
75 changes: 37 additions & 38 deletions en/09.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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.
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.

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 := "^&*()"
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`.

// salt1 + username + salt2 + MD5 splicing
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)
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.

last :=fmt.Sprintf("%x", h.Sum(nil))
In Go, it's recommended that you use the `bcrypt` package.

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.
The package's source code can be found at the following link: https://github.com/golang/crypto/blob/master/bcrypt/bcrypt.go

## Professional solution
Here is an example code snippet which can be used to hash, store and validate user passwords:

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.
package main

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`.
import (
"fmt"
"log"

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.
"golang.org/x/crypto/bcrypt"
)

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).
func main() {
userPassword1 := "some user-provided password"

The package's source code can be found at the following link: http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
// 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

Here is an example code snippet which can be used to obtain a derived key for an AES-256 encryption:
// 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

dk: = scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
// Comparing the password with the hash
if err := bcrypt.CompareHashAndPassword(hashFromDatabase, []byte(userPassword2)); err != nil {
// TODO: Properly handle error
log.Fatal(err)
}

You can generate unique password values using the above method, which are by far the most difficult to crack.
fmt.Println("Password was correct!")
}

## Summary

Expand Down

0 comments on commit a037c3c

Please sign in to comment.