diff --git a/pwhash/password.go b/pwhash/password.go
new file mode 100644
index 0000000000000000000000000000000000000000..5f3abea1647b26793a766907807aab6f5a60c118
--- /dev/null
+++ b/pwhash/password.go
@@ -0,0 +1,253 @@
+package pwhash
+
+import (
+	"crypto/rand"
+	"crypto/subtle"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+
+	"github.com/amoghe/go-crypt"
+	"golang.org/x/crypto/argon2"
+	"golang.org/x/crypto/scrypt"
+)
+
+// PasswordHash is a convenience interface common to all types in this package.
+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
+}
+
+// SystemCryptPasswordHash uses the glibc crypt function.
+type SystemCryptPasswordHash struct{}
+
+// ComparePassword returns true if the given password matches the
+// encrypted one.
+func (s *SystemCryptPasswordHash) ComparePassword(encrypted, password string) bool {
+	enc2, err := crypt.Crypt(password, encrypted)
+	if err != nil {
+		return false
+	}
+	return subtle.ConstantTimeCompare([]byte(encrypted), []byte(enc2)) == 1
+}
+
+// Encrypt the given password using glibc crypt.
+func (s *SystemCryptPasswordHash) Encrypt(password string) string {
+	salt := fmt.Sprintf("$6$%x$", getRandomBytes(16))
+	enc, err := crypt.Crypt(password, salt)
+	if err != nil {
+		panic(err)
+	}
+	return enc
+}
+
+var (
+	argonKeyLen  uint32 = 32
+	argonSaltLen        = 16
+)
+
+// Argon2PasswordHash uses the Argon2 hashing algorithm.
+type Argon2PasswordHash struct{}
+
+// ComparePassword returns true if the given password matches the
+// encrypted one.
+func (s *Argon2PasswordHash) ComparePassword(encrypted, password string) bool {
+	params, salt, dk, err := decodeArgon2Hash(encrypted)
+	if err != nil {
+		return false
+	}
+	dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
+	//log.Printf("params=%+v, salt=%+v, dk=%v, dk2=%v", params, salt, dk, dk2)
+	return subtle.ConstantTimeCompare(dk, dk2) == 1
+}
+
+// Encrypt the given password with the Argon2 algorithm.
+func (s *Argon2PasswordHash) Encrypt(password string) string {
+	salt := getRandomBytes(argonSaltLen)
+	params := defaultArgon2Params
+
+	dk := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
+
+	return encodeArgon2Hash(params, salt, dk)
+}
+
+type argon2Params struct {
+	Time    uint32
+	Memory  uint32
+	Threads uint8
+}
+
+var defaultArgon2Params = argon2Params{
+	Time:    4,
+	Memory:  32 * 1024,
+	Threads: 1,
+
+	// Test fails with threads > 1 !!
+	//Threads: uint8(runtime.NumCPU()),
+}
+
+func encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
+	return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk)
+}
+
+func decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
+	if !strings.HasPrefix(s, "$a2$") {
+		err = errors.New("not an Argon2 password hash")
+		return
+	}
+
+	parts := strings.SplitN(s[4:], "$", 5)
+	if len(parts) != 5 {
+		err = errors.New("bad encoding")
+		return
+	}
+
+	var i uint64
+
+	if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil {
+		return
+	}
+	params.Time = uint32(i)
+
+	if i, err = strconv.ParseUint(parts[1], 10, 32); err != nil {
+		return
+	}
+	params.Memory = uint32(i)
+
+	if i, err = strconv.ParseUint(parts[2], 10, 8); err != nil {
+		return
+	}
+	params.Threads = uint8(i)
+
+	if salt, err = hex.DecodeString(parts[3]); err != nil {
+		return
+	}
+	dk, err = hex.DecodeString(parts[4])
+	return
+}
+
+var (
+	scryptKeyLen  = 32
+	scryptSaltLen = 16
+)
+
+// ScryptPasswordHash uses the scrypt hashing algorithm.
+type ScryptPasswordHash struct{}
+
+// ComparePassword returns true if the given password matches
+// the encrypted one.
+func (s *ScryptPasswordHash) ComparePassword(encrypted, password string) bool {
+	params, salt, dk, err := decodeScryptHash(encrypted)
+	if err != nil {
+		return false
+	}
+	dk2, err := scrypt.Key([]byte(password), salt, params.N, params.R, params.P, scryptKeyLen)
+	if err != nil {
+		return false
+	}
+	//log.Printf("params=%+v, salt=%+v, dk=%v, dk2=%v", params, salt, dk, dk2)
+	return subtle.ConstantTimeCompare(dk, dk2) == 1
+}
+
+// Encrypt the given password with the scrypt algorithm.
+func (s *ScryptPasswordHash) Encrypt(password string) string {
+	salt := getRandomBytes(scryptSaltLen)
+	params := defaultScryptParams
+
+	dk, err := scrypt.Key([]byte(password), salt, params.N, params.R, params.P, scryptKeyLen)
+	if err != nil {
+		panic(err)
+	}
+
+	return encodeScryptHash(params, salt, dk)
+}
+
+type scryptParams struct {
+	N int
+	R int
+	P int
+}
+
+var defaultScryptParams = scryptParams{
+	N: 16384,
+	R: 8,
+	P: 1,
+}
+
+func encodeScryptHash(params scryptParams, salt, dk []byte) string {
+	return fmt.Sprintf("$s$%d$%d$%d$%x$%x", params.N, params.R, params.P, salt, dk)
+}
+
+func decodeScryptHash(s string) (params scryptParams, salt []byte, dk []byte, err error) {
+	if !strings.HasPrefix(s, "$s$") {
+		err = errors.New("not a scrypt password hash")
+		return
+	}
+
+	parts := strings.SplitN(s[3:], "$", 5)
+	if len(parts) != 5 {
+		err = errors.New("bad encoding")
+		return
+	}
+
+	if params.N, err = strconv.Atoi(parts[0]); err != nil {
+		return
+	}
+
+	if params.R, err = strconv.Atoi(parts[1]); err != nil {
+		return
+	}
+	if params.P, err = strconv.Atoi(parts[2]); err != nil {
+		return
+	}
+
+	if salt, err = hex.DecodeString(parts[3]); err != nil {
+		return
+	}
+	dk, err = hex.DecodeString(parts[4])
+	return
+}
+
+func getRandomBytes(n int) []byte {
+	b := make([]byte, n)
+	_, err := io.ReadFull(rand.Reader, b[:])
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+var prefixRegistry = map[string]PasswordHash{
+	"$1$":  &SystemCryptPasswordHash{},
+	"$5$":  &SystemCryptPasswordHash{},
+	"$6$":  &SystemCryptPasswordHash{},
+	"$s$":  &ScryptPasswordHash{},
+	"$a2$": &Argon2PasswordHash{},
+}
+
+// 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 = &Argon2PasswordHash{}
+
+// Encrypt will encrypt a password with the default algorithm.
+func Encrypt(password string) string {
+	return DefaultEncryptAlgorithm.Encrypt(password)
+}
diff --git a/pwhash/password_test.go b/pwhash/password_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0908962167f5fc076bf1aa13889d89e3628b75b4
--- /dev/null
+++ b/pwhash/password_test.go
@@ -0,0 +1,59 @@
+package pwhash
+
+import "testing"
+
+func TestArgon2(t *testing.T) {
+	testImpl(t, &Argon2PasswordHash{})
+}
+
+func TestScrypt(t *testing.T) {
+	testImpl(t, &ScryptPasswordHash{})
+}
+
+func TestSystemCrypt(t *testing.T) {
+	testImpl(t, &SystemCryptPasswordHash{})
+}
+
+func TestDefault(t *testing.T) {
+	testImpl(t, nil)
+}
+
+func testImpl(t *testing.T, h PasswordHash) {
+	pw1 := "password 1"
+	pw2 := "password 2"
+
+	var enc1, enc2 string
+	if h == nil {
+		enc1 = Encrypt(pw1)
+		enc2 = Encrypt(pw2)
+	} else {
+		enc1 = h.Encrypt(pw1)
+		enc2 = h.Encrypt(pw2)
+	}
+	//t.Logf("enc1=%s", enc1)
+
+	testData := []struct {
+		enc            string
+		pw             string
+		expectedResult bool
+	}{
+		{enc1, pw1, true},
+		{enc2, pw2, true},
+		{enc1, pw2, false},
+		{enc2, pw1, false},
+		{enc1, "", false},
+		{enc1, "foo", false},
+	}
+
+	for _, td := range testData {
+		var result bool
+		if h == nil {
+			result = ComparePassword(td.enc, td.pw)
+		} else {
+			result = h.ComparePassword(td.enc, td.pw)
+		}
+		if result != td.expectedResult {
+			t.Errorf("compare(%s, %s): got %v, expected %v", td.enc, td.pw, result, td.expectedResult)
+		}
+	}
+}