17

Using golang.org/x/crypto/bcrypt and GORM (http://gorm.io/docs/) I'm trying to encrypt a password. The problem is that every encryption of it is different every time, so it can never match the one in the database.

var result []string

password := []byte(data.Password)
encryptedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) // different every time

db.Where(&User{Username: strings.ToLower(data.Username)}).First(&user).Pluck("password", &result)
encryptionErr := bcrypt.CompareHashAndPassword(encryptedPassword, []byte(result[0]))

if encryptionErr == nil { // passwords match! }

I have confirmed that the input is the same every time and that the password given from the database is correct.

What am I doing wrong here?

Jeffrey Anderson
  • 409
  • 2
  • 5
  • 10
  • 4
    Are you actually getting an error that passwords don't match from `CompareHashAndPassword`, or is your concern just that the `GenerateFromPassword` output is different each time? – Adrian Aug 31 '18 at 18:48
  • Yeah I'm getting the error that they don't match, even when the input is the same. – Jeffrey Anderson Aug 31 '18 at 19:02
  • I can only suggest re-checking that the inputs are, in fact, byte-for-byte identical. I have used this bcrypt library many times, and have never had any issue. The bytes returned by `GenerateFromPassword` will be different every time because it includes a random salt for security, but the salt is included in the output and does not interfere with password validation. – Adrian Aug 31 '18 at 19:12

2 Answers2

23

The problem is that every encryption of it is different every time, so it can never match the one in the database.

This is normal bcrypt behavior.

bcrypt returns a different hash each time because it incorporates a different random value into the hash. This is known as a "salt". It prevents people from attacking your hashed passwords with a "rainbow table", a pre-generated table mapping password hashes back to their passwords. The salt means that instead of there being one hash for a password, there's 2^16 of them. Too many to store.

The salt is stored as part of the hashed password. So bcrypt.CompareHashAndPassword(encryptedPassword, plainPassword) can encrypt plainPassword using the same salt as encryptedPassword and compare them.

See this answer for more information and Dustin Boswell's excellent Storing User Passwords Securely: hashing, salting, and Bcrypt.

What am I doing wrong here?

You're trying to compare the generated hashed password with the stored hashed password. At least I certainly hope it's the hashed password that's stored in the database.

What you want instead is to compare the stored hashed password with the plain password the user entered. bcrypt.CompareHashAndPassword will then use the stored hashed password's salt to hash the plain password and compare.

// Normally this comes from user input and is *never* stored
plainPassword := "supersekret"

// The encrypted password is stored in the database
db.Where(&User{Username: strings.ToLower(data.Username)}).First(&user).Pluck("password", &result)
encryptedPassword := []byte(result[0])

// Check if the stored encrypted password matches "supersekret"
encryptionErr := bcrypt.CompareHashAndPassword(encryptedPassword, plainPassword)
if encryptionErr == nil {
    fmt.Println("Greetings Professor Falken")
} else {
    fmt.Println(encryptionErr)
}
Schwern
  • 153,029
  • 25
  • 195
  • 336
10

The bcrypt hash algorithm, by design, generates a different encrypted string every time you call it (it is salted). If you have a plaintext password you want to check, and ciphertext in the database, you should be able to pass those two things to bcrypt.CompareHashAndPassword. Adapting your code:

var result []string
db.Where(&User{Username: strings.ToLower(data.Username)})
        .First(&user)
        .Pluck("password", &result)

encryptionErr := bcrypt.CompareHashAndPassword([]byte(result[0]), []byte(data.Password))

You shouldn't need to call bcrypt.GenerateFromPassword again; as you note, it will generate a different encrypted password and it should be all but impossible to compare the two for equality.

David Maze
  • 130,717
  • 29
  • 175
  • 215