At the time of writing, Argon2 is preferred way of hashing password for storage purposes. You can use example for hashing and verifying passwords.


Example


package main

import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"log"
"strings"

"golang.org/x/crypto/argon2"
)

type Argon2ID struct {
format string
version int
time uint32
memory uint32
keyLen uint32
saltLen uint32
threads uint8
}

func main() {
argon2ID := NewArgon2ID()

plain := "inanzzz"

hash1, err := argon2ID.Hash(plain)
if err != nil {
log.Fatal(err)
}
ok1, err := argon2ID.Verify(plain, hash1)
if err != nil {
log.Fatal(err)
}

hash2, err := argon2ID.Hash(plain)
if err != nil {
log.Fatal(err)
}
ok2, err := argon2ID.Verify(plain, hash2)
if err != nil {
log.Fatal(err)
}

hash3, err := argon2ID.Hash(plain)
if err != nil {
log.Fatal(err)
}
ok3, err := argon2ID.Verify(plain, hash3+"uuu")
if err != nil {
log.Fatal(err)
}

fmt.Println("PLAIN:", plain)
fmt.Println("HASH 1:", hash1)
fmt.Println("VALID 1:", ok1)
fmt.Println("HASH 2:", hash2)
fmt.Println("VALID 2:", ok2)
fmt.Println("HASH 3:", hash3)
fmt.Println("VALID 3:", ok3)
}

func NewArgon2ID() Argon2ID {
return Argon2ID{
format: "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
version: argon2.Version,
time: 1,
memory: 64 * 1024,
keyLen: 32,
saltLen: 16,
threads: 4,
}
}

func (a Argon2ID) Hash(plain string) (string, error) {
salt := make([]byte, a.saltLen)
if _, err := rand.Read(salt); err != nil {
return "", err
}

hash := argon2.IDKey([]byte(plain), salt, a.time, a.memory, a.threads, a.keyLen)

return fmt.Sprintf(
a.format,
a.version,
a.memory,
a.time,
a.threads,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(hash),
),
nil
}

func (a Argon2ID) Verify(plain, hash string) (bool, error) {
hashParts := strings.Split(hash, "$")

_, err := fmt.Sscanf(hashParts[3], "m=%d,t=%d,p=%d", &a.memory, &a.time, &a.threads)
if err != nil {
return false, err
}

salt, err := base64.RawStdEncoding.DecodeString(hashParts[4])
if err != nil {
return false, err
}

decodedHash, err := base64.RawStdEncoding.DecodeString(hashParts[5])
if err != nil {
return false, err
}

hashToCompare := argon2.IDKey([]byte(plain), salt, a.time, a.memory, a.threads, uint32(len(decodedHash)))

return subtle.ConstantTimeCompare(decodedHash, hashToCompare) == 1, nil
}

Test


PLAIN: inanzzz
HASH 1: $argon2id$v=19$m=65536,t=1,p=4$3Ymrg20KqXd9mMawXP/YzA$nf7ubeO0tB1NDk4nBscgsHvIcDECMjIuEeEjgBRMe3s
VALID 1: true

HASH 2: $argon2id$v=19$m=65536,t=1,p=4$DneZEKdFvOarVcS5A6WsZA$dmpO4kOSbJr1ZoDWRIG+Bx8wuvpiVp9tQyyNjSwPWYk
VALID 2: true

HASH 3: $argon2id$v=19$m=65536,t=1,p=4$L/6i/DD9Ie5dKo7L6PpvVg$8OLc5G2E715nbsSgA4ZKcGngVLtAeCnB4CD76XbShic
VALID 3: false