1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
package sha1crypt
import (
"fmt"
"github.com/go-crypt/x/crypt"
"github.com/go-crypt/crypt/algorithm"
"github.com/go-crypt/crypt/internal/random"
)
// New returns a *sha1crypt.Hasher with the additional opts applied if any.
func New(opts ...Opt) (hasher *Hasher, err error) {
hasher = &Hasher{}
if err = hasher.WithOptions(opts...); err != nil {
return nil, err
}
if err = hasher.Validate(); err != nil {
return nil, err
}
return hasher, nil
}
// Hasher is a crypt.Hash for sha1crypt which can be initialized via sha1crypt.New using a functional options pattern.
type Hasher struct {
iterations uint32
i bool
bytesSalt int
d bool
}
// WithOptions applies the provided functional options provided as a sha1crypt.Opt to the sha1crypt.Hasher.
func (h *Hasher) WithOptions(opts ...Opt) (err error) {
for _, opt := range opts {
if err = opt(h); err != nil {
return err
}
}
return nil
}
// Hash performs the hashing operation and returns either a algorithm.Digest or an error.
func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) {
h.defaults()
if digest, err = h.hash(password); err != nil {
return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err)
}
return digest, nil
}
// MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to
// utilize the Validate method first or handle the panic appropriately.
func (h *Hasher) MustHash(password string) (digest algorithm.Digest) {
var err error
if digest, err = h.Hash(password); err != nil {
panic(err)
}
return digest
}
// HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the
// salt size and let this be a random value generated using crypto/rand.
func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) {
h.defaults()
if digest, err = h.hashWithSalt(password, salt); err != nil {
return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err)
}
return digest, nil
}
// Validate checks the settings/parameters for this sha1crypt.Hasher and returns an error.
func (h *Hasher) Validate() (err error) {
h.defaults()
return nil
}
func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) {
var salt []byte
if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil {
return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err)
}
return h.hashWithSalt(password, salt)
}
func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) {
if s := len(salt); s > SaltLengthMax || s < SaltLengthMin {
return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt))
}
d := &Digest{
iterations: h.iterations,
i: h.i,
salt: salt,
}
d.defaults()
d.key = crypt.KeySHA1Crypt([]byte(password), d.salt, d.iterations)
return d, nil
}
func (h *Hasher) defaults() {
if h.d {
return
}
h.d = true
if h.bytesSalt < SaltLengthMin {
h.bytesSalt = SaltLengthDefault
}
}
|