Skip to content
Snippets Groups Projects
password.go 2.21 KiB
// Package pwhash provides a simple interface to hashed passwords with
// support for multiple hashing algorithms.
//
// The format is the well-known dollar-separated field string,
// extended with optional algorithm-specific parameters:
//
//     $id[$params...]$salt$encrypted
//
// We extend 'id' beyond the values supported by the libc crypt(3)
// function with the following hashing algorithms:
//
// Scrypt (id '$s$'), in which case the parameters are N, R and P.
//
// Argon2 (id '$a2$'), with parameters time, memory and threads. To
// tune Argon2 parameters, you can run the benchmarks in this package:
// the parameterized benchmarks are named with
// time/memory(MB)/threads. For nicer results:
//
//     go test -bench=Argon2 -run=none . 2>&1 | \
//         awk '/^Bench/ {ops=1000000000 / $3; print $1 " " ops " ops/sec"}'
//
package pwhash

import (
	"crypto/rand"
	"io"
	"strings"
)

// PasswordHash is the interface for a password hashing algorithm
// implementation.
type PasswordHash interface {
	// ComparePassword returns true if the given password matches
	// the encrypted one.
	ComparePassword(string, string) bool

	// Encrypt the given password.
	Encrypt(string) string
}

func getRandomBytes(n int) []byte {
	b := make([]byte, n)
	_, err := io.ReadFull(rand.Reader, b[:])
	if err != nil {
		panic(err)
	}
	return b
}

// A registry of default handlers for decoding passwords.
var prefixRegistry = map[string]PasswordHash{
	"$1$":       NewSystemCrypt(),
	"$5$":       NewSystemCrypt(),
	"$6$":       NewSystemCrypt(),
	"$s$":       NewScrypt(),
	"$a2$":      NewArgon2(),
	"$argon2i$": NewArgon2Std(),
}

// ComparePassword returns true if the given password matches the
// encrypted one.
func ComparePassword(encrypted, password string) bool {
	for pfx, h := range prefixRegistry {
		if strings.HasPrefix(encrypted, pfx) {
			return h.ComparePassword(encrypted, password)
		}
	}
	return false
}
// DefaultEncryptAlgorithm is used by the Encrypt function to encrypt
// passwords.
var DefaultEncryptAlgorithm PasswordHash

func init() {
	DefaultEncryptAlgorithm = NewArgon2()
}

// Encrypt will encrypt a password with the default algorithm.
func Encrypt(password string) string {
	return DefaultEncryptAlgorithm.Encrypt(password)
}