DEV Community

Cover image for How to Properly Hash and Salt Passwords in Golang Using Bcrypt 🔒
Gregory Gaines
Gregory Gaines

Posted on • Originally published at gregorygaines.com

How to Properly Hash and Salt Passwords in Golang Using Bcrypt 🔒

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)
}
Enter fullscreen mode Exit fullscreen mode

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"))
}
Enter fullscreen mode Exit fullscreen mode
Password Hash: $2a$04$kcsOln10l5Gm0lSgQmlrhuS0T9u124J3HfrAr9tnHltr9u7.iUsJm
Password Match: true
Enter fullscreen mode Exit fullscreen mode

Consider signing up for my newsletter or supporting me if this was helpful. Thanks for reading!

Discussion (1)

Collapse
vhiuwhfqowuhef profile image
vhiuwhfqowuhef • Edited on

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.