Prerequisites
- Download the golang bcrypt library using
go get golang.org/x/crypto/bcrypt
.
Hashing
// Hash password using the bcrypt hashing algorithm
func hashPassword(password string) (string, error) {
// Convert password string to byte slice
var passwordBytes = []byte(password)
// Hash password with bcrypt's min cost
hashedPasswordBytes, err := bcrypt.
GenerateFromPassword(passwordBytes, bcrypt.MinCost)
return string(hashedPasswordBytes), err
}
func main() {
// Hash password
var hashedPassword, err = hashPassword("password1")
if err != nil {
println(fmt.Println("Error hashing password"))
return
}
fmt.Println("Password Hash:", hashedPassword)
}
No Need to Salt Passwords
Bcrypt uses a concept named cost
which represents the number of hash iterations that bcrypt undertakes. Hashing time is calculated as 2 ^ cost
and the higher the cost, the longer the hashing process takes.
This deters attackers because they can't quickly brute force a password match and increasing computational power will do little to help. Bcrypt uses a variable named bcrypt.MinCost
as a default cost and any cost lower defaults to bcrypt.DefaultCost
.
Password Matching
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
// Hash password
func hashPassword(password string) (string, error) {
// Convert password string to byte slice
var passwordBytes = []byte(password)
// Hash password with Bcrypt's min cost
hashedPasswordBytes, err := bcrypt.
GenerateFromPassword(passwordBytes, bcrypt.MinCost)
return string(hashedPasswordBytes), err
}
// Check if two passwords match using Bcrypt's CompareHashAndPassword
// which return nil on success and an error on failure.
func doPasswordsMatch(hashedPassword, currPassword string) bool {
err := bcrypt.CompareHashAndPassword(
[]byte(hashedPassword), []byte(currPassword))
return err == nil
}
func main() {
// Hash password
var hashedPassword, err = hashPassword("password1")
if err != nil {
println(fmt.Println("Error hashing password"))
return
}
fmt.Println("Password Hash:", hashedPassword)
// Check if passed password matches the original password
fmt.Println("Password Match:",
doPasswordsMatch(hashedPassword, "password1"))
}
Password Hash: $2a$04$kcsOln10l5Gm0lSgQmlrhuS0T9u124J3HfrAr9tnHltr9u7.iUsJm
Password Match: true
Consider signing up for my newsletter or supporting me if this was helpful. Thanks for reading!
Top comments (1)
There is a really significant flaw here that makes this advice actually harmful.
Use of
bcrypt.MinCost
(a value of 4) renders BCrypt nearly useless. It is exactly the wrong value to use for actually storing passwords securely. The entire point of BCrypt is to be slow, choosing the lowest possible cost makes BCrypt fast, completely defeating the purpose of using BCrypt in the first place. You should only ever use the minimum cost in your test suite, to avoid slowing down tests.MinCost
should never be the default value you reach for.In fact, the
bcrypt.DefaultCost
of 10, which is meant to be a safe default to use, is still too low by today's standards. You should be defaulting to 12 at this point, and ideally measuring and picking the highest possible value you can afford, based on how much latency it adds during login/signup.Anybody copy-pasting this code into their app is going to be producing extremely insecure password hashes. Even when BCrypt was first introduced more than 20 years ago, the cost factor recommended was 6 for normal users and 8 for super users, which are respectively four and sixteen times slower than a cost of 4.
See labs.clio.com/bcrypt-cost-factor-4... which shows how dangerous low costs are, and note that the lowest they bother to test is 6, which is still 4x slower than the
MinCost
you're recommending here.