Documentation ¶
Overview ¶
Package passhash helps you safely hash passwords for storage using well-known adaptive hash functions (a.k.a. key derivation functions e.g. KDFs) provided by golang.org/x/crypto
Features:
- Simple, easy to use API
- Tunable work factors
- Auto-upgrading KDFs and work factors
- Password usage audit log
- Password policies
passhash gets out of your way, yet is also flexibile to meet your security needs.
Experts may modify defaults (e.g. via init()) but need to exercise caution to ensure that the new parameters (Kdf and CostFactor) are in fact secure.
Example ¶
credential, err := passhash.NewCredential(passhash.UserID(0), testPassword) if err != nil { // Handle error gettings credential } matched, updated := credential.MatchesPassword(testPassword) if !matched { // Handle invalid password } if updated { // store := SomeStorage() // SomeStorage implements the CredentialStore interface // store.Marshal(credential) } newPassword := "newinsecurepassword" if err = credential.ChangePassword(testPassword, newPassword); err != nil { // Handle PasswordPoliciesNotMet error } newPassword2 := "newinsecurepassword2" if err = credential.Reset(newPassword2); err != nil { // Handle PasswordPoliciesNotMet error }
Output:
Index ¶
- Variables
- func GetRandReader() io.Reader
- func SetRandReader(reader io.Reader)
- func WorkFactorsEqual(a, b WorkFactor) bool
- type AtLeastNRunes
- type AuditLogger
- type AuditType
- type BcryptWorkFactor
- type Config
- type Credential
- func (c *Credential) ChangePassword(oldPassword, newPassword string) error
- func (c *Credential) ChangePasswordWithConfig(config Config, oldPassword, newPassword string) error
- func (c *Credential) ChangePasswordWithConfigAndIP(config Config, oldPassword, newPassword string, ip net.IP) error
- func (c *Credential) ChangePasswordWithIP(oldPassword, newPassword string, ip net.IP) error
- func (c *Credential) MatchesPassword(password string) (matched, updated bool)
- func (c *Credential) MatchesPasswordWithConfig(config Config, password string) (matched, updated bool)
- func (c *Credential) MatchesPasswordWithConfigAndIP(config Config, password string, ip net.IP) (matched, updated bool)
- func (c *Credential) MatchesPasswordWithIP(password string, ip net.IP) (matched, updated bool)
- func (c *Credential) MeetsConfig(config Config) bool
- func (c *Credential) NeedsUpdate() bool
- func (c *Credential) Reset(newPassword string) error
- func (c *Credential) ResetWithConfig(config Config, newPassword string) error
- func (c *Credential) ResetWithConfigAndIP(config Config, newPassword string, ip net.IP) error
- func (c *Credential) ResetWithIP(newPassword string, ip net.IP) error
- type CredentialStore
- type DummyAuditLogger
- type DummyCredentialStore
- type Kdf
- type Log
- type MemoryAuditLogger
- type NotCommonPasswordNaive
- type PasswordPoliciesNotMet
- type PasswordPolicy
- type PasswordPolicyError
- type Pbkdf2WorkFactor
- type ScryptWorkFactor
- type UserID
- type WorkFactor
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultConfig = Config{ Kdf: Scrypt, WorkFactor: DefaultWorkFactor[Scrypt], SaltSize: 16, KeyLength: 32, AuditLogger: &DummyAuditLogger{}, Store: DummyCredentialStore{}, PasswordPolicies: []PasswordPolicy{ AtLeastNRunes{N: 10}, }, }
DefaultConfig is a safe default configuration for managing credentials
var DefaultWorkFactor = map[Kdf]WorkFactor{ Pbkdf2Sha256: &Pbkdf2WorkFactor{Iter: 100000}, Pbkdf2Sha512: &Pbkdf2WorkFactor{Iter: 100000}, Pbkdf2Sha3_256: &Pbkdf2WorkFactor{Iter: 100000}, Pbkdf2Sha3_512: &Pbkdf2WorkFactor{Iter: 100000}, Bcrypt: &BcryptWorkFactor{Cost: 12}, Scrypt: &ScryptWorkFactor{N: 32768, R: 16, P: 1}, }
DefaultWorkFactor provides the default WorkFactor for a specific Kdf. Do not modify unless you're an expert. Note: DefaultWorkFactor returns a pointer so do not use Unmarshal w/ a WorkFactor from DefaultWorkFactor. Use NewWorkFactorForKdf() instead. TODO: Determine/tune DefaultWorkFactor (aim for 150+ms hash time on API server hardware)
var EmptyIP = net.IP{}
EmptyIP is the canonical value for an empty IP. This value should not be modified
var ( // ErrPasswordUnchanged is used when a Credential.ChangePassword*() method is called with the same old and new // password ErrPasswordUnchanged = errors.New("Password unchanged") )
Functions ¶
func GetRandReader ¶ added in v0.3.0
GetRandReader gets the io.Reader responsible for generating random bytes used by passhash
func SetRandReader ¶ added in v0.3.0
SetRandReader sets the io.Reader used by passhash to generate random bytes
func WorkFactorsEqual ¶
func WorkFactorsEqual(a, b WorkFactor) bool
WorkFactorsEqual determines if 2 WorkFactors are equivalent
Types ¶
type AtLeastNRunes ¶
type AtLeastNRunes struct {
N int
}
AtLeastNRunes is a PasswordPolicy that ensures that the password is at least N runes in length
func (AtLeastNRunes) PasswordAcceptable ¶
func (pp AtLeastNRunes) PasswordAcceptable(password string) error
PasswordAcceptable accepts passwords that are at least N runes in length
type AuditLogger ¶
type AuditLogger interface { // Log logs an authorization action/result Log(UserID, AuditType, net.IP) // LastN gets the last N logs for a user LastN(userID UserID, n int) []Log // LastNWithTypes gets the last N logs for a user with the specified types LastNWithTypes(userID UserID, n int, auditTypes ...AuditType) []Log }
AuditLogger is an interface for storing Specs with an audit trail
type BcryptWorkFactor ¶
type BcryptWorkFactor struct {
Cost int
}
BcryptWorkFactor specifies the work/cost parameters for bcrypt
func (*BcryptWorkFactor) Marshal ¶
func (wf *BcryptWorkFactor) Marshal() ([]int, error)
Marshal returns the marshaled WorkFactor
func (*BcryptWorkFactor) Unmarshal ¶
func (wf *BcryptWorkFactor) Unmarshal(p []int) error
Unmarshal unmarshals the WorkFactor
type Config ¶
type Config struct { Kdf Kdf // The key derivation function WorkFactor WorkFactor // The work factor for the kdf SaltSize int // The size of the salt in bytes KeyLength int // The size of the output key (e.g. hash) in bytes AuditLogger AuditLogger // The AuditLogger to use Store CredentialStore // The CredentialStore to use PasswordPolicies []PasswordPolicy // The password policies to enforce }
Config provides configuration for managing credentials. e.g. creation, storing, verifying, and auditing
func (Config) NewCredential ¶
func (c Config) NewCredential(userID UserID, password string) (*Credential, error)
NewCredential creates a new Credential with the provided Config
type Credential ¶
type Credential struct { UserID UserID Kdf Kdf WorkFactor WorkFactor Salt []byte Hash []byte }
Credential is a password specification. It contains all of the parameters necessary to generate and verify a password for a user
func NewCredential ¶
func NewCredential(userID UserID, password string) (*Credential, error)
NewCredential creates a new Credential with sane/recommended defaults
func (*Credential) ChangePassword ¶ added in v0.2.0
func (c *Credential) ChangePassword(oldPassword, newPassword string) error
ChangePassword changes the password for the given Credential and updates the Credential to use the recommended safe key derivation function and parameters
func (*Credential) ChangePasswordWithConfig ¶ added in v0.2.0
func (c *Credential) ChangePasswordWithConfig(config Config, oldPassword, newPassword string) error
ChangePasswordWithConfig changes the password for the given Credential and updates the Credential to meet the Config parameters if necessary
func (*Credential) ChangePasswordWithConfigAndIP ¶ added in v0.2.0
func (c *Credential) ChangePasswordWithConfigAndIP(config Config, oldPassword, newPassword string, ip net.IP) error
ChangePasswordWithConfigAndIP changes the password for the given Credential and updates the Credential to meet the Config parameters if necessary
func (*Credential) ChangePasswordWithIP ¶ added in v0.2.0
func (c *Credential) ChangePasswordWithIP(oldPassword, newPassword string, ip net.IP) error
ChangePasswordWithIP changes the password for the given Credential and updates the Credential to use the recommended safe key derivation function and parameters
func (*Credential) MatchesPassword ¶
func (c *Credential) MatchesPassword(password string) (matched, updated bool)
MatchesPassword checks if the provided password matches the Credential and updates the Credential to use the recommended safe key derivation function and parameters
func (*Credential) MatchesPasswordWithConfig ¶
func (c *Credential) MatchesPasswordWithConfig(config Config, password string) (matched, updated bool)
MatchesPasswordWithConfig checks if the provided password matches the Credential and updates the Credential to meet the Config parameters if necessary
func (*Credential) MatchesPasswordWithConfigAndIP ¶
func (c *Credential) MatchesPasswordWithConfigAndIP(config Config, password string, ip net.IP) (matched, updated bool)
MatchesPasswordWithConfigAndIP checks if the provided password matches the Credential and updates the Credential to meet the Config parameters if necessary
func (*Credential) MatchesPasswordWithIP ¶
func (c *Credential) MatchesPasswordWithIP(password string, ip net.IP) (matched, updated bool)
MatchesPasswordWithIP checks if the provided password matches the Credential and updates the Credential to use the recommended safe key derivation function and parameters
func (*Credential) MeetsConfig ¶
func (c *Credential) MeetsConfig(config Config) bool
MeetsConfig returns true if the Credential meets the parameters specified in the given Config and returns false otherwise
func (*Credential) NeedsUpdate ¶
func (c *Credential) NeedsUpdate() bool
NeedsUpdate determines if the Credential meets the recommended safe key derivation function and parameters
func (*Credential) Reset ¶
func (c *Credential) Reset(newPassword string) error
Reset resets the password for the given Credential and updates the Credential to use the recommended safe key derivation function and parameters
func (*Credential) ResetWithConfig ¶
func (c *Credential) ResetWithConfig(config Config, newPassword string) error
ResetWithConfig resets the password for the given Credential and updates the Credential to meet the Config parameters if necessary
func (*Credential) ResetWithConfigAndIP ¶
ResetWithConfigAndIP resets the password for the given Credential and updates the Credential to meet the Config parameters if necessary
func (*Credential) ResetWithIP ¶
func (c *Credential) ResetWithIP(newPassword string, ip net.IP) error
ResetWithIP resets the password for the given Credential and updates the Credential to use the recommended safe key derivation function and parameters
type CredentialStore ¶
type CredentialStore interface { Store(*Credential) error StoreContext(context.Context, *Credential) error Load(UserID) (*Credential, error) LoadContext(context.Context, UserID) (*Credential, error) }
CredentialStore is an interfance for customizing Credential storage
Example ¶
nolint: dupl
package main import ( "bytes" "context" "fmt" "strconv" "strings" ) import ( "github.com/dhui/passhash" ) const ( storeCredentialFormat string = "%d %s %x %x" // Using space as a separator for Sscanf compatibility ) // StringCredentialStore is an example CredentialStore that stores the Credential as a string type StringCredentialStore struct { StoredCredential string } func (store *StringCredentialStore) Store(credential *passhash.Credential) error { cfParams, _ := credential.WorkFactor.Marshal() cfStrParams := make([]string, 0, len(cfParams)) for _, param := range cfParams { cfStrParams = append(cfStrParams, strconv.Itoa(param)) } cfStore := strings.Join(cfStrParams, ",") store.StoredCredential = fmt.Sprintf(storeCredentialFormat, credential.Kdf, cfStore, string(credential.Salt), string(credential.Hash)) return nil } func (store *StringCredentialStore) StoreContext(ctx context.Context, credential *passhash.Credential) error { return store.Store(credential) } func (store *StringCredentialStore) Load(passhash.UserID) (*passhash.Credential, error) { credential := passhash.Credential{} var cfStore string fmt.Sscanf(store.StoredCredential, storeCredentialFormat, &credential.Kdf, &cfStore, &credential.Salt, &credential.Hash) cfStrParams := strings.Split(cfStore, ",") cfParams := make([]int, 0, len(cfStrParams)) for _, paramStr := range cfStrParams { i, err := strconv.Atoi(paramStr) if err != nil { return nil, err } cfParams = append(cfParams, i) } wf, err := passhash.NewWorkFactorForKdf(credential.Kdf) if err != nil { return nil, err } if err := wf.Unmarshal(cfParams); err != nil { return nil, err } credential.WorkFactor = wf return &credential, nil } func (store *StringCredentialStore) LoadContext(ctx context.Context, userID passhash.UserID) (*passhash.Credential, error) { return store.Load(userID) } // nolint: dupl func main() { userID := passhash.UserID(0) password := "insecurepassword" origCredential, err := passhash.NewCredential(userID, password) if err != nil { fmt.Println("Error creating credential.", err) return } store := StringCredentialStore{} if err := store.Store(origCredential); err != nil { fmt.Println("Error storing credential.", err) return } newCredential, err := store.Load(userID) if err != nil { fmt.Println("Error loading credential.", err) return } credentialEqual := newCredential == origCredential kdfEqual := newCredential.Kdf == origCredential.Kdf cfEqual := newCredential.WorkFactor == origCredential.WorkFactor // Not equal due to pointer comparison saltEqual := bytes.Equal(newCredential.Salt, origCredential.Salt) hashEqual := bytes.Equal(newCredential.Hash, origCredential.Hash) matched, updated := newCredential.MatchesPassword(password) fmt.Println("credentialEqual:", credentialEqual) fmt.Println("kdfEqual:", kdfEqual) fmt.Println("cfEqual:", cfEqual) fmt.Println("saltEqual:", saltEqual) fmt.Println("hashEqual:", hashEqual) fmt.Println("newCredential.MatchesPassword (matched):", matched) fmt.Println("newCredential.MatchesPassword (updated):", updated) }
Output: credentialEqual: false kdfEqual: true cfEqual: false saltEqual: true hashEqual: true newCredential.MatchesPassword (matched): true newCredential.MatchesPassword (updated): false
Example (Peppered) ¶
nolint: dupl
package main import ( "bytes" "context" "crypto/aes" "crypto/cipher" "crypto/rand" "fmt" "io" ) import ( "github.com/dhui/passhash" ) var pepperGrinder PepperGrinder type PepperGrinder struct { cipher cipher.Block } func (p PepperGrinder) Encrypt(text []byte) (nonce, encrypted []byte) { nonce = make([]byte, 12) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err.Error()) } aesgcm, err := cipher.NewGCM(p.cipher) if err != nil { panic(err.Error()) } return nonce, aesgcm.Seal(nil, nonce, text, nil) } func (p PepperGrinder) Decrypt(nonce, text []byte) []byte { aesgcm, err := cipher.NewGCM(p.cipher) if err != nil { panic(err.Error()) } decryptedText, err := aesgcm.Open(nil, nonce, text, nil) if err != nil { panic(err.Error()) } return decryptedText } func NewPepperGrinder(key []byte) (PepperGrinder, error) { cipher, err := aes.NewCipher(key) if err != nil { return PepperGrinder{}, err } return PepperGrinder{cipher}, nil } func init() { var err error pepperGrinder, err = NewPepperGrinder(KeyForPepperID[DefaultPepperID]) if err != nil { panic(err.Error()) } } type PepperID uint const DefaultPepperID PepperID = 1 var KeyForPepperID = map[PepperID][]byte{ 1: []byte("AES256Key-32Characters1234567890"), } type StringCredentialPepperedStore struct { StringCredentialStore PepperID PepperID // Versions pepper key/method Nonce []byte } func (store *StringCredentialPepperedStore) Store(credential *passhash.Credential) error { store.PepperID = DefaultPepperID store.Nonce, credential.Hash = pepperGrinder.Encrypt(credential.Hash) return store.StringCredentialStore.Store(credential) } func (store *StringCredentialPepperedStore) StoreContext(ctx context.Context, credential *passhash.Credential) error { return store.Store(credential) } func (store *StringCredentialPepperedStore) Load(id passhash.UserID) (*passhash.Credential, error) { credential, err := store.StringCredentialStore.Load(id) if err != nil { return nil, err } switch store.PepperID { case 1: credential.Hash = pepperGrinder.Decrypt(store.Nonce, credential.Hash) default: return nil, fmt.Errorf("Unsupported PepperID %v", store.PepperID) } return credential, nil } func (store *StringCredentialPepperedStore) LoadContext(id passhash.UserID) (*passhash.Credential, error) { return store.Load(id) } // nolint: dupl func main() { userID := passhash.UserID(0) password := "insecurepassword" origCredential, err := passhash.NewCredential(userID, password) if err != nil { fmt.Println("Error creating credential.", err) return } store := StringCredentialPepperedStore{} if err := store.Store(origCredential); err != nil { fmt.Println("Error storing credential.", err) return } newCredential, err := store.Load(userID) if err != nil { fmt.Println("Error loading credential.", err) return } credentialEqual := newCredential == origCredential kdfEqual := newCredential.Kdf == origCredential.Kdf cfEqual := newCredential.WorkFactor == origCredential.WorkFactor // Not equal due to pointer comparison saltEqual := bytes.Equal(newCredential.Salt, origCredential.Salt) hashEqual := bytes.Equal(newCredential.Hash, origCredential.Hash) matched, updated := newCredential.MatchesPassword(password) fmt.Println("credentialEqual:", credentialEqual) fmt.Println("kdfEqual:", kdfEqual) fmt.Println("cfEqual:", cfEqual) fmt.Println("saltEqual:", saltEqual) fmt.Println("hashEqual:", hashEqual) // Not equal due to peppering fmt.Println("newCredential.MatchesPassword (matched):", matched) fmt.Println("newCredential.MatchesPassword (updated):", updated) }
Output: credentialEqual: false kdfEqual: true cfEqual: false saltEqual: true hashEqual: false newCredential.MatchesPassword (matched): true newCredential.MatchesPassword (updated): false
type DummyAuditLogger ¶
type DummyAuditLogger struct{}
DummyAuditLogger is a dummy AuditLogger. e.g. it doesn't track any audit logs
func (*DummyAuditLogger) LastN ¶
func (al *DummyAuditLogger) LastN(userID UserID, n int) []Log
LastN doesn't actually do anything
func (*DummyAuditLogger) LastNWithTypes ¶
func (al *DummyAuditLogger) LastNWithTypes(userID UserID, n int, auditTypes ...AuditType) []Log
LastNWithTypes doesn't actually do anything
type DummyCredentialStore ¶
type DummyCredentialStore struct{}
DummyCredentialStore is a dummy CredentialStore that doesn't do anything
func (DummyCredentialStore) Load ¶
func (d DummyCredentialStore) Load(UserID) (*Credential, error)
Load only returns errors
func (DummyCredentialStore) LoadContext ¶
func (d DummyCredentialStore) LoadContext(context.Context, UserID) (*Credential, error)
LoadContext only returns errors
func (DummyCredentialStore) Store ¶
func (d DummyCredentialStore) Store(*Credential) error
Store doesn't store anything
func (DummyCredentialStore) StoreContext ¶
func (d DummyCredentialStore) StoreContext(context.Context, *Credential) error
StoreContext doesn't store anything
type Kdf ¶
type Kdf uint
Kdf is a Key Derivation Function
const ( // Pbkdf2Sha256 is the PBKDF2 using SHA-256 as the HMAC. To use this, you'll need to register the sha256 package by importing "crypto/sha256". Pbkdf2Sha256 Kdf = iota + 1 // Pbkdf2Sha512 is the PBKDF2 using SHA-512 as the HMAC. To use this, you'll need to register the sha256 package by importing "crypto/sha256". Pbkdf2Sha512 // Pbkdf2Sha3_256 is the PBKDF2 using SHA-3 256 bit block size as the HMAC Pbkdf2Sha3_256 // Pbkdf2Sha3_512 is the PBKDF2 using SHA-3 512 bit block size as the HMAC Pbkdf2Sha3_512 // Bcrypt is the bcrypt kdf Bcrypt // Scrypt is the scrypt kdf Scrypt )
type MemoryAuditLogger ¶
type MemoryAuditLogger struct {
// contains filtered or unexported fields
}
MemoryAuditLogger is an AuditLogger that stores all of it's logs in memory It is not recommended that you use this AuditLogger in production since the logs are not persisted and concurrent access is not supported
func (*MemoryAuditLogger) LastN ¶
func (al *MemoryAuditLogger) LastN(userID UserID, n int) []Log
LastN gets the last N logs for a user
func (*MemoryAuditLogger) LastNWithTypes ¶
func (al *MemoryAuditLogger) LastNWithTypes(userID UserID, n int, auditTypes ...AuditType) (logs []Log)
LastNWithTypes gets the last N logs for a user with the specified types
type NotCommonPasswordNaive ¶
NotCommonPasswordNaive is a PasswordPolicy that ensures that the password is not a common password. The method of checking is naive in that only exact password matches are rejected
func (NotCommonPasswordNaive) PasswordAcceptable ¶
func (pp NotCommonPasswordNaive) PasswordAcceptable(password string) error
PasswordAcceptable accepts passwords that are not common passwords
type PasswordPoliciesNotMet ¶
type PasswordPoliciesNotMet struct {
UnMetPasswordPolicies []PasswordPolicyError
}
PasswordPoliciesNotMet satisfies the error interface and tracks the unmet password policies
func (PasswordPoliciesNotMet) Error ¶
func (e PasswordPoliciesNotMet) Error() string
type PasswordPolicy ¶
PasswordPolicy is an interface used to determine if a password is acceptable. e.g. meets the given policy
type PasswordPolicyError ¶
type PasswordPolicyError struct { PasswordPolicy PasswordPolicy Err error }
PasswordPolicyError satisfies the error interface and describes the reason for a PasswordPolicy check failure
func (PasswordPolicyError) Error ¶
func (e PasswordPolicyError) Error() string
type Pbkdf2WorkFactor ¶
type Pbkdf2WorkFactor struct {
Iter int
}
Pbkdf2WorkFactor specifies the work/cost parameters for PBKDF2
func (*Pbkdf2WorkFactor) Marshal ¶
func (wf *Pbkdf2WorkFactor) Marshal() ([]int, error)
Marshal returns the marshaled WorkFactor
func (*Pbkdf2WorkFactor) Unmarshal ¶
func (wf *Pbkdf2WorkFactor) Unmarshal(p []int) error
Unmarshal unmarshals the WorkFactor
type ScryptWorkFactor ¶
ScryptWorkFactor specifies the work/cost parameters for scrypt
func (*ScryptWorkFactor) Marshal ¶
func (wf *ScryptWorkFactor) Marshal() ([]int, error)
Marshal returns the marshaled WorkFactor
func (*ScryptWorkFactor) Unmarshal ¶
func (wf *ScryptWorkFactor) Unmarshal(p []int) error
Unmarshal unmarshals the WorkFactor
type WorkFactor ¶
WorkFactor describes the work/cost for a KDF The interface is similar to Go's "encoding" Marshaler/Unmarshalers
func NewWorkFactorForKdf ¶
func NewWorkFactorForKdf(kdf Kdf) (WorkFactor, error)
NewWorkFactorForKdf returns an empty new WorkFactor for the given kdf