Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
5f5cbeab4f
|
|||
| c2737bef29 | |||
|
6a24de7687
|
|||
| 0cb9cb9359 | |||
|
7f6b35e769
|
@@ -6,6 +6,7 @@ require github.com/mgechev/revive v1.3.3
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
|
||||
github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
@@ -16,6 +17,7 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/tools v0.12.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
|
||||
github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab h1:5JxePczlyGAtj6R1MUEFZ/UFud6FfsOejq7xLC2ZIb0=
|
||||
github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -35,11 +37,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/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.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -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$" }
|
||||
@@ -0,0 +1,216 @@
|
||||
// Package htpasswd contains utilities for manipulating .htpasswd files
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Passwds name => hash
|
||||
type Passwds map[string]string
|
||||
|
||||
// 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
|
||||
func ParseHtpasswdFile(file string) (Passwds, error) {
|
||||
htpasswdBytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseHtpasswd(htpasswdBytes)
|
||||
}
|
||||
|
||||
// ParseHtpasswd parses a slice of bytes in htpasswd style
|
||||
func ParseHtpasswd(htpasswdBytes []byte) (Passwds, error) {
|
||||
lines := strings.Split(string(htpasswdBytes), "\n")
|
||||
passwords := make(map[string]string)
|
||||
var err error
|
||||
|
||||
for lineNumber, line := range lines {
|
||||
line = strings.Trim(line, " ")
|
||||
if len(line) == 0 {
|
||||
// skipping empty lines
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, ":")
|
||||
if ok, err := validLine(parts, lineNumber, line); !ok {
|
||||
return passwords, err
|
||||
}
|
||||
|
||||
parts = trimParts(parts)
|
||||
_, exists := passwords[parts[0]]
|
||||
|
||||
if exists {
|
||||
err = errors.New("invalid htpasswords file - user " +
|
||||
parts[0] + " defined more than once")
|
||||
return passwords, err
|
||||
}
|
||||
passwords[parts[0]] = parts[1]
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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$"):
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package htpasswd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseHtpasswd(t *testing.T) {
|
||||
passwords, err := ParseHtpasswd([]byte("sha:{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E=\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(passwords) != 1 {
|
||||
t.Fatalf("unexpected length in passwords, expected 1 got %v", len(passwords))
|
||||
}
|
||||
const expected = "{SHA}IRRjboXT92QSYXm8lpGPCZUvU1E="
|
||||
if passwords["sha"] != expected {
|
||||
t.Fatalf("sha password was wrong, got %s and expected %s", passwords["sha"], expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
f, err := os.Create("htpasswd_testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testCases := []struct {
|
||||
user, password string
|
||||
hash Hasher
|
||||
expected error
|
||||
}{
|
||||
{"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 {
|
||||
err := CreateUser(f.Name(), tc.user, tc.password, tc.hash)
|
||||
if err != tc.expected {
|
||||
t.Errorf("CreateUser(%s %s, %s, %s) = %d; want %d", f.Name(),
|
||||
tc.user, tc.password, tc.hash, err, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestVerifyPassword(t *testing.T) {
|
||||
f, err := os.Stat("htpasswd_testdata")
|
||||
if err != nil {
|
||||
TestCreateUser(t)
|
||||
}
|
||||
|
||||
_, err = ParseHtpasswdFile(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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 := VerifyUser(f.Name(), tc.user, tc.password)
|
||||
if err != tc.expected {
|
||||
t.Errorf("VerifyUser(%v %v, %v) = %v; want %v",
|
||||
f, tc.user, tc.password, err, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyUser(t *testing.T) {
|
||||
f, err := os.Stat("htpasswd_testdata")
|
||||
if err != nil {
|
||||
TestCreateUser(t)
|
||||
}
|
||||
|
||||
pp, err := ParseHtpasswdFile(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
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