htpasswd: refactor and add Passwd methods #3
@@ -0,0 +1,22 @@
|
||||
package htpasswd
|
||||
|
||||
import "github.com/GehirnInc/crypt/apr1_crypt"
|
||||
|
||||
// Apr1 facilitates apr1 style hashing
|
||||
type Apr1 struct{}
|
||||
|
||||
// Hash returns the hashed variant of the password or an error
|
||||
func (*Apr1) Hash(passwd string) (string, error) {
|
||||
return apr1_crypt.New().Generate([]byte(passwd), nil)
|
||||
}
|
||||
|
||||
// Match verifier the hashed password using the original
|
||||
func (*Apr1) Match(password, hashedPassword string) error {
|
||||
return apr1_crypt.New().Verify(hashedPassword, []byte(password))
|
||||
}
|
||||
|
||||
// Name returns the name of the hasher
|
||||
func (*Apr1) Name() string { return "apr1" }
|
||||
|
||||
// Prefix returns the hasher's prefix
|
||||
func (*Apr1) Prefix() string { return "$apr1$" }
|
||||
@@ -0,0 +1,26 @@
|
||||
package htpasswd
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
// Bcrypt facilitates bcrypt style hashing
|
||||
type Bcrypt struct{}
|
||||
|
||||
// Hash returns the hashed variant of the password or an error
|
||||
func (*Bcrypt) Hash(password string) (string, error) {
|
||||
passwordBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(passwordBytes), nil
|
||||
}
|
||||
|
||||
// Match verifier the hashed password using the original
|
||||
func (*Bcrypt) Match(password, hashedPassword string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
}
|
||||
|
||||
// Name returns the name of the hasher
|
||||
func (*Bcrypt) Name() string { return "bcrypt" }
|
||||
|
||||
// Prefix returns the hasher's prefix
|
||||
func (*Bcrypt) Prefix() string { return "$2a$" }
|
||||
+144
-340
@@ -2,41 +2,22 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/GehirnInc/crypt/apr1_crypt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Passwds name => hash
|
||||
type Passwds map[string]string
|
||||
|
||||
// HashAlgorithm enum for hashing algorithms
|
||||
type HashAlgorithm string
|
||||
|
||||
const (
|
||||
// HashAPR1 Apache MD5 crypt
|
||||
HashAPR1 HashAlgorithm = "apr1"
|
||||
// HashBCrypt bcrypt
|
||||
HashBCrypt HashAlgorithm = "bcrypt"
|
||||
// HashSHA SHA
|
||||
HashSHA HashAlgorithm = "sha"
|
||||
// HashSSHA Salted SHA
|
||||
HashSSHA HashAlgorithm = "ssha"
|
||||
// HashSHA256 256 variant of SHA
|
||||
HashSHA256 HashAlgorithm = "sha256"
|
||||
// HashSHA512 512 variant of SHA
|
||||
HashSHA512 HashAlgorithm = "sha512"
|
||||
)
|
||||
// Hasher interface implemented by hash algos
|
||||
type Hasher interface {
|
||||
Hash(password string) (string, error)
|
||||
Match(password, hashedPassword string) error
|
||||
Name() string
|
||||
Prefix() string
|
||||
}
|
||||
|
||||
// ParseHtpasswdFile parses a .htpasswd file
|
||||
// and returns a Passwd type
|
||||
@@ -79,6 +60,143 @@ func ParseHtpasswd(htpasswdBytes []byte) (Passwds, error) {
|
||||
return passwords, err
|
||||
}
|
||||
|
||||
// CreateUser creates a record in the named file with
|
||||
// the named password and hash algorithm
|
||||
func CreateUser(file, user, passwd string, algo Hasher) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pp.CreateUser(user, passwd, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pp.Write(file)
|
||||
}
|
||||
|
||||
// CreateUser will create a new user in the given Passwd object
|
||||
// using the given name, password and hashing algorithm
|
||||
func (pp Passwds) CreateUser(user, passwd string, algo Hasher) error {
|
||||
if _, exists := pp[user]; exists {
|
||||
return fmt.Errorf("user %s already exists", user)
|
||||
}
|
||||
h, err := algo.Hash(passwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pp[user] = h
|
||||
return nil
|
||||
}
|
||||
|
karasz marked this conversation as resolved
Outdated
|
||||
|
||||
// UpdateUser will update the password for the named user
|
||||
// in the named file
|
||||
func UpdateUser(file, user, passwd string, algo Hasher) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pp.UpdateUser(user, passwd, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
|
amery
commented
we may want errors that can be tested without parsing the content we may want errors that can be tested without parsing the content
karasz
commented
Not sure what you mean Not sure what you mean
amery
commented
usage or wrapping predefined errors so one can use errors.Is() and similar usage or wrapping predefined errors so one can use errors.Is() and similar
|
||||
}
|
||||
return pp.Write(file)
|
||||
}
|
||||
|
||||
// UpdateUser will update the password for the named user
|
||||
// using the given name, password and hashing algorithm
|
||||
func (pp Passwds) UpdateUser(user, passwd string, algo Hasher) error {
|
||||
if _, exists := pp[user]; !exists {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
}
|
||||
h, err := algo.Hash(passwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pp[user] = h
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes the named user from the named file
|
||||
func DeleteUser(file, user string) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pp.DeleteUser(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pp.Write(file)
|
||||
}
|
||||
|
||||
// DeleteUser deletes the named user from the named file
|
||||
func (pp Passwds) DeleteUser(user string) error {
|
||||
if _, exists := pp[user]; !exists {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
}
|
||||
|
||||
delete(pp, user)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyUser will check if the given user and password are matching
|
||||
// with the content of the given file
|
||||
func VerifyUser(file, user, passwd string) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pp.VerifyUser(user, passwd)
|
||||
}
|
||||
|
||||
// VerifyUser will check if the given user and password are matching
|
||||
// with the given Passwd object
|
||||
func (pp Passwds) VerifyUser(user, passwd string) error {
|
||||
if _, ok := pp[user]; !ok {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
}
|
||||
alg, err := identifyHash(pp[user])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot identify algo %v", alg)
|
||||
}
|
||||
return alg.Match(passwd, pp[user])
|
||||
}
|
||||
|
||||
// Write will cwrite the Passwd object to the given file
|
||||
func (pp Passwds) Write(file string) error {
|
||||
return os.WriteFile(file, pp.Bytes(), os.ModePerm)
|
||||
}
|
||||
|
||||
// Bytes will return the Passwd as a byte slice
|
||||
func (pp Passwds) Bytes() []byte {
|
||||
pass := []byte{}
|
||||
for name, hash := range pp {
|
||||
pass = append(pass, []byte(name+":"+hash+"\n")...)
|
||||
}
|
||||
return pass
|
||||
}
|
||||
|
||||
func identifyHash(h string) (Hasher, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"),
|
||||
strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"):
|
||||
return new(Bcrypt), nil
|
||||
case strings.HasPrefix(h, "$apr1$"):
|
||||
return new(Apr1), nil
|
||||
case strings.HasPrefix(h, "{SHA}"):
|
||||
return new(Sha), nil
|
||||
case strings.HasPrefix(h, "{SSHA}"):
|
||||
return new(Ssha), nil
|
||||
case strings.HasPrefix(h, "$5$"):
|
||||
|
karasz marked this conversation as resolved
Outdated
amery
commented
standard is standard is `Bytes()` as the returned value contains many.
`WriteTo()` is also a nice interface
|
||||
return new(Sha256), nil
|
||||
case strings.HasPrefix(h, "$6$"):
|
||||
return new(Sha512), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
func validLine(parts []string, lineNumber int, line string) (bool, error) {
|
||||
var err error
|
||||
if len(parts) != 2 {
|
||||
@@ -96,317 +214,3 @@ func trimParts(parts []string) []string {
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
// CreateUser creates a record in the named file with
|
||||
// the named password and hash algorithm
|
||||
func CreateUser(file, user, passwd string, algo HashAlgorithm) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exists := pp[user]; exists {
|
||||
return fmt.Errorf("user %s already exists", user)
|
||||
}
|
||||
h, err := createHash(passwd, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pp[user] = h
|
||||
return os.WriteFile(file, pp.toByte(), os.ModePerm)
|
||||
}
|
||||
|
||||
// UpdateUser will update the password for the named user
|
||||
// in the named file
|
||||
func UpdateUser(file, user, passwd string, algo HashAlgorithm) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exists := pp[user]; !exists {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
}
|
||||
h, err := createHash(passwd, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pp[user] = h
|
||||
return os.WriteFile(file, pp.toByte(), os.ModePerm)
|
||||
}
|
||||
|
||||
// DeleteUser deletes the named user from the named file
|
||||
func DeleteUser(file, user string) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exists := pp[user]; !exists {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
}
|
||||
|
||||
delete(pp, user)
|
||||
return os.WriteFile(file, pp.toByte(), os.ModePerm)
|
||||
}
|
||||
|
||||
// VerifyUser will check if the given user and password are matching
|
||||
// with the content of the given file
|
||||
func VerifyUser(file, user, passwd string) error {
|
||||
pp, err := ParseHtpasswdFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pp.Verify(user, passwd)
|
||||
}
|
||||
|
||||
// Verify will check if the given user and password are matching
|
||||
// with the given Passwd object content
|
||||
func (pp Passwds) Verify(user, passwd string) error {
|
||||
if _, ok := pp[user]; !ok {
|
||||
return fmt.Errorf("user %s does not exist", user)
|
||||
}
|
||||
alg, err := identifyHash(pp[user])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot identify algo %v", alg)
|
||||
}
|
||||
return verifyPass(passwd, pp[user], alg)
|
||||
}
|
||||
|
||||
func verifyPass(pass, hash string, alg HashAlgorithm) error {
|
||||
switch alg {
|
||||
case HashAPR1:
|
||||
return verifyAPR1(pass, hash)
|
||||
case HashBCrypt:
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pass))
|
||||
case HashSHA:
|
||||
return verifySHA(pass, hash)
|
||||
case HashSSHA:
|
||||
return verifySSHA(pass, hash)
|
||||
case HashSHA256:
|
||||
return verifySHA256(pass, hash)
|
||||
case HashSHA512:
|
||||
return verifySHA512(pass, hash)
|
||||
}
|
||||
return fmt.Errorf("unsupported hash algorithm %v", alg)
|
||||
}
|
||||
|
||||
func verifyAPR1(password, hashedPassword string) error {
|
||||
return apr1_crypt.New().Verify(hashedPassword, []byte(password))
|
||||
}
|
||||
|
||||
func verifySHA(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[5:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
sha := sha1.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySSHA(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[6:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
salt := hash[len(hash)-4:]
|
||||
sha := sha1.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sha.Write(salt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash[:len(hash)-4]) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySHA256(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[3:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
sha := sha256.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySHA512(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[3:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
sha := sha512.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp Passwds) toByte() []byte {
|
||||
pass := []byte{}
|
||||
for name, hash := range pp {
|
||||
pass = append(pass, []byte(name+":"+hash+"\n")...)
|
||||
}
|
||||
return pass
|
||||
}
|
||||
|
||||
func identifyHash(h string) (HashAlgorithm, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"),
|
||||
strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"):
|
||||
return HashBCrypt, nil
|
||||
case strings.HasPrefix(h, "$apr1$"):
|
||||
return HashAPR1, nil
|
||||
case strings.HasPrefix(h, "{SHA}"):
|
||||
return HashSHA, nil
|
||||
case strings.HasPrefix(h, "{SSHA}"):
|
||||
return HashSSHA, nil
|
||||
case strings.HasPrefix(h, "$5$"):
|
||||
return HashSHA256, nil
|
||||
case strings.HasPrefix(h, "$6$"):
|
||||
return HashSHA512, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
func createHash(passwd string, algo HashAlgorithm) (string, error) {
|
||||
hash := ""
|
||||
prefix := ""
|
||||
var err error
|
||||
switch algo {
|
||||
case HashAPR1:
|
||||
hash, err = hashApr1(passwd)
|
||||
case HashBCrypt:
|
||||
hash, err = hashBcrypt(passwd)
|
||||
case HashSHA:
|
||||
prefix = "{SHA}"
|
||||
hash, err = hashSha(passwd)
|
||||
case HashSSHA:
|
||||
prefix = "{SSHA}"
|
||||
hash, err = hashSSha(passwd)
|
||||
case HashSHA256:
|
||||
prefix = "$5$"
|
||||
hash, err = hashSha256(passwd)
|
||||
case HashSHA512:
|
||||
prefix = "$6$"
|
||||
hash, err = hashSha512(passwd)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return prefix + hash, nil
|
||||
}
|
||||
|
||||
func hashApr1(passwd string) (string, error) {
|
||||
return apr1_crypt.New().Generate([]byte(passwd), nil)
|
||||
}
|
||||
|
||||
func hashBcrypt(passwd string) (string, error) {
|
||||
passwordBytes, err := bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(passwordBytes), nil
|
||||
}
|
||||
|
||||
func hashSha(passwd string) (string, error) {
|
||||
s := sha1.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||
}
|
||||
|
||||
func hashSha256(passwd string) (string, error) {
|
||||
s := sha256.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||
}
|
||||
|
||||
func hashSha512(passwd string) (string, error) {
|
||||
s := sha512.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||
}
|
||||
|
||||
func hashSSha(passwd string) (string, error) {
|
||||
s := sha1.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
salt := make([]byte, 4)
|
||||
_, err = rand.Read(salt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = s.Write([]byte(salt))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
ret := append(passwordSum, salt...)
|
||||
return base64.StdEncoding.EncodeToString(ret), nil
|
||||
}
|
||||
|
||||
+28
-13
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseHtpassd(t *testing.T) {
|
||||
func TestParseHtpasswd(t *testing.T) {
|
||||
passwords, err := ParseHtpasswd([]byte("sha:{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E=\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -26,15 +26,15 @@ func TestCreateUser(t *testing.T) {
|
||||
}
|
||||
testCases := []struct {
|
||||
user, password string
|
||||
hash HashAlgorithm
|
||||
hash Hasher
|
||||
expected error
|
||||
}{
|
||||
{"apr1", "123456@", HashAPR1, nil},
|
||||
{"bcrypt", "123456@", HashBCrypt, nil},
|
||||
{"ssha", "123456@", HashSSHA, nil},
|
||||
{"sha", "123456@", HashSHA, nil},
|
||||
{"sha256", "123456@", HashSHA256, nil},
|
||||
{"sha512", "123456@", HashSHA512, nil},
|
||||
{"apr1", "123456@", new(Apr1), nil},
|
||||
{"bcrypt", "123456@", new(Bcrypt), nil},
|
||||
{"ssha", "123456@", new(Ssha), nil},
|
||||
{"sha", "123456@", new(Sha), nil},
|
||||
{"sha256", "123456@", new(Sha256), nil},
|
||||
{"sha512", "123456@", new(Sha512), nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -71,13 +71,13 @@ func TestVerifyPassword(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
err := VerifyUser(f.Name(), tc.user, tc.password)
|
||||
if err != tc.expected {
|
||||
t.Errorf("VerifyUser(%s %s, %s) = %v; want %v",
|
||||
t.Errorf("VerifyUser(%v %v, %v) = %v; want %v",
|
||||
f, tc.user, tc.password, err, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
func TestVerifyUser(t *testing.T) {
|
||||
f, err := os.Stat("htpasswd_testdata")
|
||||
if err != nil {
|
||||
TestCreateUser(t)
|
||||
@@ -87,8 +87,23 @@ func TestVerify(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = pp.Verify("bcrypt", "123456@")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
testCases := []struct {
|
||||
user, password string
|
||||
expected error
|
||||
}{
|
||||
{"apr1", "123456@", nil},
|
||||
{"bcrypt", "123456@", nil},
|
||||
{"ssha", "123456@", nil},
|
||||
{"sha", "123456@", nil},
|
||||
{"sha256", "123456@", nil},
|
||||
{"sha512", "123456@", nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := pp.VerifyUser(tc.user, tc.password)
|
||||
if err != tc.expected {
|
||||
t.Errorf("VerifyUser(%s, %s) = %v; want %v",
|
||||
tc.user, tc.password, err, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
apr1:$apr1$v0FnbGJM$b2P3y1ltZYHakDaHrWx3N1
|
||||
bcrypt:$2a$10$pBSUext6NDsFYrm8GviW.OFe6SczH91INRC3YmsfE3HJp/fPmRaee
|
||||
ssha:{SSHA}LoTRQgCdeGIeJ3nxDQMCmQSWdnMEsLqj
|
||||
sha512:$6$78RjySv19bx/knbdL6q1cpoV8WblZwc3x+wmPGQUvrSycxc4liKbksvDr9HZj76hgRuZZCyEngP+WEJmePArCQ==
|
||||
bcrypt:$2a$10$YNEmQcVCrvcA8m1DNCwR9eXaTySDEa1sC/T5xUUTFnVnP.RIP3O2u
|
||||
ssha:{SSHA}fSLbdty+JHr+q3p/lHAPvNkOU3H8NLmI
|
||||
sha:{SHA}YEn9/RmoXLdbyB9TEDJ0OqWoPy8=
|
||||
sha256:$5$ufJ2S16IOhB6XLTGrVLqGQBKv/odjE3rypxnUDLqaS0=
|
||||
sha512:$6$78RjySv19bx/knbdL6q1cpoV8WblZwc3x+wmPGQUvrSycxc4liKbksvDr9HZj76hgRuZZCyEngP+WEJmePArCQ==
|
||||
apr1:$apr1$mO.FA9Gg$1LbPaKe7HCVHezEYzMCnn.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Sha facilitates sha1 style hashing
|
||||
type Sha struct{}
|
||||
|
||||
// Hash returns the hashed variant of the password or an error
|
||||
func (ss *Sha) Hash(passwd string) (string, error) {
|
||||
s := sha1.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||
}
|
||||
|
||||
// Match verifier the hashed password using the original
|
||||
func (*Sha) Match(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[5:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
sha := sha1.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the hasher
|
||||
func (*Sha) Name() string { return "sha" }
|
||||
|
||||
// Prefix returns the hasher's prefix
|
||||
func (*Sha) Prefix() string { return "{SHA}" }
|
||||
@@ -0,0 +1,52 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Sha256 facilitates sha256 style hashing
|
||||
type Sha256 struct{}
|
||||
|
||||
// Hash returns the hashed variant of the password or an error
|
||||
func (ss *Sha256) Hash(passwd string) (string, error) {
|
||||
s := sha256.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||
}
|
||||
|
||||
// Match verifier the hashed password using the original
|
||||
func (*Sha256) Match(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[3:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
sha := sha256.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the hasher
|
||||
func (*Sha256) Name() string { return "sha256" }
|
||||
|
||||
// Prefix returns the hasher's prefix
|
||||
func (*Sha256) Prefix() string { return "$5$" }
|
||||
@@ -0,0 +1,52 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Sha512 facilitates sha512 style hashing
|
||||
type Sha512 struct{}
|
||||
|
||||
// Hash returns the hashed variant of the password or an error
|
||||
func (ss *Sha512) Hash(passwd string) (string, error) {
|
||||
s := sha512.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(passwordSum), nil
|
||||
}
|
||||
|
||||
// Match verifier the hashed password using the original
|
||||
func (*Sha512) Match(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[3:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
sha := sha512.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the hasher
|
||||
func (*Sha512) Name() string { return "sha512" }
|
||||
|
||||
// Prefix returns the hasher's prefix
|
||||
func (*Sha512) Prefix() string { return "$6$" }
|
||||
@@ -0,0 +1,71 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Ssha facilitates ssha style hashing
|
||||
type Ssha struct{}
|
||||
|
||||
// Hash returns the hashed variant of the password or an error
|
||||
func (ss *Ssha) Hash(passwd string) (string, error) {
|
||||
s := sha1.New()
|
||||
_, err := s.Write([]byte(passwd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
salt := make([]byte, 4)
|
||||
_, err = rand.Read(salt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = s.Write([]byte(salt))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
passwordSum := []byte(s.Sum(nil))
|
||||
ret := append(passwordSum, salt...)
|
||||
return ss.Prefix() + base64.StdEncoding.EncodeToString(ret), nil
|
||||
}
|
||||
|
||||
// Match verifier the hashed password using the original
|
||||
func (*Ssha) Match(password, hashedPassword string) error {
|
||||
eppS := hashedPassword[6:]
|
||||
hash, err := base64.StdEncoding.DecodeString(eppS)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot base64 decode")
|
||||
}
|
||||
|
||||
salt := hash[len(hash)-4:]
|
||||
sha := sha1.New()
|
||||
_, err = sha.Write([]byte(password))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sha.Write(salt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, hash[:len(hash)-4]) {
|
||||
return fmt.Errorf("wrong password")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the hasher
|
||||
func (*Ssha) Name() string { return "ssha" }
|
||||
|
||||
// Prefix returns the hasher's prefix
|
||||
func (*Ssha) Prefix() string { return "{SSHA}" }
|
||||
Reference in New Issue
Block a user
as it's a map, newpp and pp are actually the same map and both contain the new user.
if we really want a new map this should be via a
newpp, err := pp.Clone().AddUser(...)