Compare commits

8 Commits

Author SHA1 Message Date
karasz 4395a40036 Merge pull request 'light clean up' (#4)
Reviewed-on: #4
2023-09-30 01:48:09 +02:00
amery 0fb985026f htpasswd: clean up parser
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-29 19:14:05 +00:00
amery 02235c36bc htpasswd: clean up errors
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-29 18:52:56 +00:00
amery 7b8b0e6700 htpasswd: standarize function names
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-29 18:41:29 +00:00
amery 5941fb013c vscode: add hasher, htpasswd and Passwds to the dictionary
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-29 18:41:29 +00:00
amery 47d0d5d0a4 fix go.mod version
having 1.21.1 there makes go fmt require 1.23 to check the files

Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-29 18:41:29 +00:00
amery afac3a836f Merge pull request 'htpasswd: refactor and add Passwd methods' (#3)
Reviewed-on: #3
2023-09-28 20:18:36 +02:00
Nagy Károly Gábriel 5f5cbeab4f htpasswd: refactor and add Passwd methods
Signed-off-by: Nagy Károly Gábriel <k@jpi.io>
2023-09-28 17:13:22 +03:00
13 changed files with 488 additions and 321 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"cSpell.words": [
"hasher",
"htpasswd",
"Passwds"
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
module asciigoat.org/httools module asciigoat.org/httools
go 1.21.1 go 1.20
require github.com/mgechev/revive v1.3.3 require github.com/mgechev/revive v1.3.3
-1
View File
@@ -40,7 +40,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+22
View File
@@ -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$" }
+26
View File
@@ -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$" }
+49
View File
@@ -0,0 +1,49 @@
package htpasswd
import (
"errors"
"strings"
)
var (
// ErrExists indicates the specified user already exists
ErrExists = errors.New("user already exists")
// ErrNotExists indicates the specified user does not exist
ErrNotExists = errors.New("user does not exist")
// ErrInvalidAlgorithm indicates the htpasswd entry doesn't contain a
// valid algorithm signature
ErrInvalidAlgorithm = errors.New("invalid algorithm")
// ErrInvalidPassword indicates the offered password doesn't match
ErrInvalidPassword = errors.New("invalid password")
)
// UserError indicates an error associated to the specified user
type UserError struct {
Name string
Hint string
Err error
}
func (e *UserError) Error() string {
var buf strings.Builder
var reason string
if e.Hint != "" {
reason = e.Hint
} else {
reason = e.Err.Error()
}
if e.Name == "" {
return reason
}
_, _ = buf.WriteString(e.Name)
_, _ = buf.WriteString(": ")
_, _ = buf.WriteString(reason)
return buf.String()
}
func (e *UserError) Unwrap() error {
return e.Err
}
+121 -299
View File
@@ -2,297 +2,194 @@
package htpasswd package htpasswd
import ( import (
"bytes"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/GehirnInc/crypt/apr1_crypt"
"golang.org/x/crypto/bcrypt"
) )
// Passwds name => hash // Passwds name => hash
type Passwds map[string]string type Passwds map[string]string
// HashAlgorithm enum for hashing algorithms // Hasher interface implemented by hash algos
type HashAlgorithm string type Hasher interface {
Hash(password string) (string, error)
Match(password, hashedPassword string) error
Name() string
Prefix() string
}
const ( // ParseFile parses a .htpasswd file
// 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"
)
// ParseHtpasswdFile parses a .htpasswd file
// and returns a Passwd type // and returns a Passwd type
func ParseHtpasswdFile(file string) (Passwds, error) { func ParseFile(file string) (Passwds, error) {
htpasswdBytes, err := os.ReadFile(file) htpasswdBytes, err := os.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ParseHtpasswd(htpasswdBytes) return Parse(htpasswdBytes)
} }
// ParseHtpasswd parses a slice of bytes in htpasswd style // Parse parses a slice of bytes in htpasswd style
func ParseHtpasswd(htpasswdBytes []byte) (Passwds, error) { func Parse(htpasswdBytes []byte) (Passwds, error) {
lines := strings.Split(string(htpasswdBytes), "\n") lines := strings.Split(string(htpasswdBytes), "\n")
passwords := make(map[string]string) passwords := make(map[string]string)
var err error var err error
for lineNumber, line := range lines { for lineNumber, line := range lines {
line = strings.Trim(line, " ") line = strings.TrimSpace(line)
if len(line) == 0 { if len(line) == 0 {
// skipping empty lines // skipping empty lines
continue continue
} }
parts := strings.Split(line, ":") user, password, err := splitLine(line, lineNumber)
if ok, err := validLine(parts, lineNumber, line); !ok { if err != nil {
return passwords, err return passwords, err
} }
parts = trimParts(parts) _, exists := passwords[user]
_, exists := passwords[parts[0]]
if exists { if exists {
err = errors.New("invalid htpasswords file - user " + err = &UserError{
parts[0] + " defined more than once") Name: user,
Err: ErrExists,
}
return passwords, err return passwords, err
} }
passwords[parts[0]] = parts[1]
passwords[user] = password
} }
return passwords, err return passwords, err
} }
func validLine(parts []string, lineNumber int, line string) (bool, error) {
var err error
if len(parts) != 2 {
err = errors.New(fmt.Sprintln("invalid line", lineNumber+1,
"unexpected number of parts split by", ":", len(parts),
"instead of 2 in\"", line, "\""))
return false, err
}
return true, nil
}
func trimParts(parts []string) []string {
for i, part := range parts {
parts[i] = strings.Trim(part, " ")
}
return parts
}
// CreateUser creates a record in the named file with // CreateUser creates a record in the named file with
// the named password and hash algorithm // the named password and hash algorithm
func CreateUser(file, user, passwd string, algo HashAlgorithm) error { func CreateUser(file, user, passwd string, algo Hasher) error {
pp, err := ParseHtpasswdFile(file) pp, err := ParseFile(file)
if err != nil { if err != nil {
return err return err
} }
if _, exists := pp[user]; exists { err = pp.CreateUser(user, passwd, algo)
return fmt.Errorf("user %s already exists", user) if err != nil {
return err
} }
h, err := createHash(passwd, algo) return pp.WriteFile(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 &UserError{
Name: user,
Err: ErrExists,
}
}
h, err := algo.Hash(passwd)
if err != nil { if err != nil {
return err return err
} }
pp[user] = h pp[user] = h
return os.WriteFile(file, pp.toByte(), os.ModePerm) return nil
} }
// UpdateUser will update the password for the named user // UpdateUser will update the password for the named user
// in the named file // in the named file
func UpdateUser(file, user, passwd string, algo HashAlgorithm) error { func UpdateUser(file, user, passwd string, algo Hasher) error {
pp, err := ParseHtpasswdFile(file) pp, err := ParseFile(file)
if err != nil { if err != nil {
return err return err
} }
if _, exists := pp[user]; !exists { err = pp.UpdateUser(user, passwd, algo)
return fmt.Errorf("user %s does not exist", user) if err != nil {
return err
} }
h, err := createHash(passwd, algo) return pp.WriteFile(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 &UserError{
Name: user,
Err: ErrNotExists,
}
}
h, err := algo.Hash(passwd)
if err != nil { if err != nil {
return err return err
} }
pp[user] = h pp[user] = h
return os.WriteFile(file, pp.toByte(), os.ModePerm) return nil
} }
// DeleteUser deletes the named user from the named file // DeleteUser deletes the named user from the named file
func DeleteUser(file, user string) error { func DeleteUser(file, user string) error {
pp, err := ParseHtpasswdFile(file) pp, err := ParseFile(file)
if err != nil { if err != nil {
return err return err
} }
err = pp.DeleteUser(user)
if err != nil {
return err
}
return pp.WriteFile(file)
}
// DeleteUser deletes the named user from the named file
func (pp Passwds) DeleteUser(user string) error {
if _, exists := pp[user]; !exists { if _, exists := pp[user]; !exists {
return fmt.Errorf("user %s does not exist", user) return &UserError{
Name: user,
Err: ErrNotExists,
}
} }
delete(pp, user) delete(pp, user)
return os.WriteFile(file, pp.toByte(), os.ModePerm) return nil
} }
// VerifyUser will check if the given user and password are matching // VerifyUser will check if the given user and password are matching
// with the content of the given file // with the content of the given file
func VerifyUser(file, user, passwd string) error { func VerifyUser(file, user, passwd string) error {
pp, err := ParseHtpasswdFile(file) pp, err := ParseFile(file)
if err != nil { if err != nil {
return err return err
} }
return pp.Verify(user, passwd) return pp.VerifyUser(user, passwd)
} }
// Verify will check if the given user and password are matching // VerifyUser will check if the given user and password are matching
// with the given Passwd object content // with the given Passwd object
func (pp Passwds) Verify(user, passwd string) error { func (pp Passwds) VerifyUser(user, passwd string) error {
if _, ok := pp[user]; !ok { if _, ok := pp[user]; !ok {
return fmt.Errorf("user %s does not exist", user) return &UserError{
Name: user,
Err: ErrNotExists,
}
} }
alg, err := identifyHash(pp[user])
if err != nil { alg := identifyHash(pp[user])
return fmt.Errorf("cannot identify algo %v", alg) if alg == nil {
return &UserError{
Name: user,
Err: ErrInvalidAlgorithm,
}
} }
return verifyPass(passwd, pp[user], alg)
return alg.Match(passwd, pp[user])
} }
func verifyPass(pass, hash string, alg HashAlgorithm) error { // WriteFile will write the Passwds object to the given file
switch alg { func (pp Passwds) WriteFile(file string) error {
case HashAPR1: return os.WriteFile(file, pp.Bytes(), os.ModePerm)
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 { // Bytes will return the Passwd as a byte slice
return apr1_crypt.New().Verify(hashedPassword, []byte(password)) func (pp Passwds) Bytes() []byte {
}
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{} pass := []byte{}
for name, hash := range pp { for name, hash := range pp {
pass = append(pass, []byte(name+":"+hash+"\n")...) pass = append(pass, []byte(name+":"+hash+"\n")...)
@@ -300,113 +197,38 @@ func (pp Passwds) toByte() []byte {
return pass return pass
} }
func identifyHash(h string) (HashAlgorithm, error) { func identifyHash(h string) Hasher {
switch { switch {
case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"), case strings.HasPrefix(h, "$2a$"), strings.HasPrefix(h, "$2y$"),
strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"): strings.HasPrefix(h, "$2x$"), strings.HasPrefix(h, "$2b$"):
return HashBCrypt, nil return new(Bcrypt)
case strings.HasPrefix(h, "$apr1$"): case strings.HasPrefix(h, "$apr1$"):
return HashAPR1, nil return new(Apr1)
case strings.HasPrefix(h, "{SHA}"): case strings.HasPrefix(h, "{SHA}"):
return HashSHA, nil return new(Sha)
case strings.HasPrefix(h, "{SSHA}"): case strings.HasPrefix(h, "{SSHA}"):
return HashSSHA, nil return new(Ssha)
case strings.HasPrefix(h, "$5$"): case strings.HasPrefix(h, "$5$"):
return HashSHA256, nil return new(Sha256)
case strings.HasPrefix(h, "$6$"): case strings.HasPrefix(h, "$6$"):
return HashSHA512, nil return new(Sha512)
default:
return nil
} }
return "", fmt.Errorf("unsupported hash algorithm")
} }
func createHash(passwd string, algo HashAlgorithm) (string, error) { func splitLine(line string, lineNumber int) (user, password string, err error) {
hash := "" user, password, ok := strings.Cut(line, ":")
prefix := "" if !ok {
var err error return "", "", fmt.Errorf("invalid line %v", lineNumber+1)
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 user = strings.TrimSpace(user)
} password = strings.TrimSpace(password)
func hashApr1(passwd string) (string, error) { if h := identifyHash(password); h != nil {
return apr1_crypt.New().Generate([]byte(passwd), nil) return "", "", fmt.Errorf("invalid algorithm on line %v", lineNumber+1)
} }
func hashBcrypt(passwd string) (string, error) { return user, password, nil
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
} }
+31 -16
View File
@@ -5,8 +5,8 @@ import (
"testing" "testing"
) )
func TestParseHtpassd(t *testing.T) { func TestParseHtpasswd(t *testing.T) {
passwords, err := ParseHtpasswd([]byte("sha:{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E=\n")) passwords, err := Parse([]byte("sha:{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E=\n"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -26,15 +26,15 @@ func TestCreateUser(t *testing.T) {
} }
testCases := []struct { testCases := []struct {
user, password string user, password string
hash HashAlgorithm hash Hasher
expected error expected error
}{ }{
{"apr1", "123456@", HashAPR1, nil}, {"apr1", "123456@", new(Apr1), nil},
{"bcrypt", "123456@", HashBCrypt, nil}, {"bcrypt", "123456@", new(Bcrypt), nil},
{"ssha", "123456@", HashSSHA, nil}, {"ssha", "123456@", new(Ssha), nil},
{"sha", "123456@", HashSHA, nil}, {"sha", "123456@", new(Sha), nil},
{"sha256", "123456@", HashSHA256, nil}, {"sha256", "123456@", new(Sha256), nil},
{"sha512", "123456@", HashSHA512, nil}, {"sha512", "123456@", new(Sha512), nil},
} }
for _, tc := range testCases { for _, tc := range testCases {
@@ -51,7 +51,7 @@ func TestVerifyPassword(t *testing.T) {
TestCreateUser(t) TestCreateUser(t)
} }
_, err = ParseHtpasswdFile(f.Name()) _, err = ParseFile(f.Name())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -71,24 +71,39 @@ func TestVerifyPassword(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
err := VerifyUser(f.Name(), tc.user, tc.password) err := VerifyUser(f.Name(), tc.user, tc.password)
if err != tc.expected { 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) f, tc.user, tc.password, err, tc.expected)
} }
} }
} }
func TestVerify(t *testing.T) { func TestVerifyUser(t *testing.T) {
f, err := os.Stat("htpasswd_testdata") f, err := os.Stat("htpasswd_testdata")
if err != nil { if err != nil {
TestCreateUser(t) TestCreateUser(t)
} }
pp, err := ParseHtpasswdFile(f.Name()) pp, err := ParseFile(f.Name())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = pp.Verify("bcrypt", "123456@") testCases := []struct {
if err != nil { user, password string
t.Fatalf(err.Error()) 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)
}
} }
} }
+4 -4
View File
@@ -1,6 +1,6 @@
apr1:$apr1$v0FnbGJM$b2P3y1ltZYHakDaHrWx3N1 bcrypt:$2a$10$YNEmQcVCrvcA8m1DNCwR9eXaTySDEa1sC/T5xUUTFnVnP.RIP3O2u
bcrypt:$2a$10$pBSUext6NDsFYrm8GviW.OFe6SczH91INRC3YmsfE3HJp/fPmRaee ssha:{SSHA}fSLbdty+JHr+q3p/lHAPvNkOU3H8NLmI
ssha:{SSHA}LoTRQgCdeGIeJ3nxDQMCmQSWdnMEsLqj
sha512:$6$78RjySv19bx/knbdL6q1cpoV8WblZwc3x+wmPGQUvrSycxc4liKbksvDr9HZj76hgRuZZCyEngP+WEJmePArCQ==
sha:{SHA}YEn9/RmoXLdbyB9TEDJ0OqWoPy8= sha:{SHA}YEn9/RmoXLdbyB9TEDJ0OqWoPy8=
sha256:$5$ufJ2S16IOhB6XLTGrVLqGQBKv/odjE3rypxnUDLqaS0= sha256:$5$ufJ2S16IOhB6XLTGrVLqGQBKv/odjE3rypxnUDLqaS0=
sha512:$6$78RjySv19bx/knbdL6q1cpoV8WblZwc3x+wmPGQUvrSycxc4liKbksvDr9HZj76hgRuZZCyEngP+WEJmePArCQ==
apr1:$apr1$mO.FA9Gg$1LbPaKe7HCVHezEYzMCnn.
+52
View File
@@ -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}" }
+52
View File
@@ -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$" }
+52
View File
@@ -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$" }
+71
View File
@@ -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}" }