passhash: github.com/dhui/passhash Index | Examples | Files

package passhash

import "github.com/dhui/passhash"

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.

Code:

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
}

Index

Examples

Package Files

audit_logger.go config.go credential.go doc.go errors.go passhash.go password_policies.go store.go

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")
)

func GetRandReader Uses

func GetRandReader() io.Reader

GetRandReader gets the io.Reader responsible for generating random bytes used by passhash

func SetRandReader Uses

func SetRandReader(reader io.Reader)

SetRandReader sets the io.Reader used by passhash to generate random bytes

func WorkFactorsEqual Uses

func WorkFactorsEqual(a, b WorkFactor) bool

WorkFactorsEqual determines if 2 WorkFactors are equivalent

type AtLeastNRunes Uses

type AtLeastNRunes struct {
    N int
}

AtLeastNRunes is a PasswordPolicy that ensures that the password is at least N runes in length

func (AtLeastNRunes) PasswordAcceptable Uses

func (pp AtLeastNRunes) PasswordAcceptable(password string) error

PasswordAcceptable accepts passwords that are at least N runes in length

type AuditLogger Uses

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 AuditType Uses

type AuditType uint

AuditType represents the type of Audit that's logged

const (
    // AuthnSucceeded means authorization succeeded
    AuthnSucceeded AuditType = iota + 1
    // AuthnFailed means authorization failed
    AuthnFailed
    // UpgradedKdf means the key derivation function was updated
    UpgradedKdf
)

type BcryptWorkFactor Uses

type BcryptWorkFactor struct {
    Cost int
}

BcryptWorkFactor specifies the work/cost parameters for bcrypt

func (*BcryptWorkFactor) Marshal Uses

func (wf *BcryptWorkFactor) Marshal() ([]int, error)

Marshal returns the marshaled WorkFactor

func (*BcryptWorkFactor) Unmarshal Uses

func (wf *BcryptWorkFactor) Unmarshal(p []int) error

Unmarshal unmarshals the WorkFactor

type Config Uses

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 Uses

func (c Config) NewCredential(userID UserID, password string) (*Credential, error)

NewCredential creates a new Credential with the provided Config

type Credential Uses

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 Uses

func NewCredential(userID UserID, password string) (*Credential, error)

NewCredential creates a new Credential with sane/recommended defaults

func (*Credential) ChangePassword Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

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 Uses

func (c *Credential) NeedsUpdate() bool

NeedsUpdate determines if the Credential meets the recommended safe key derivation function and parameters

func (*Credential) Reset Uses

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 Uses

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 Uses

func (c *Credential) ResetWithConfigAndIP(config Config, newPassword string, ip net.IP) error

ResetWithConfigAndIP resets the password for the given Credential and updates the Credential to meet the Config parameters if necessary

func (*Credential) ResetWithIP Uses

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 Uses

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

nolint: dupl

Code:

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)

}

nolint: dupl

Code:

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)

}

type DummyAuditLogger Uses

type DummyAuditLogger struct{}

DummyAuditLogger is a dummy AuditLogger. e.g. it doesn't track any audit logs

func (*DummyAuditLogger) LastN Uses

func (al *DummyAuditLogger) LastN(userID UserID, n int) []Log

LastN doesn't actually do anything

func (*DummyAuditLogger) LastNWithTypes Uses

func (al *DummyAuditLogger) LastNWithTypes(userID UserID, n int, auditTypes ...AuditType) []Log

LastNWithTypes doesn't actually do anything

func (*DummyAuditLogger) Log Uses

func (al *DummyAuditLogger) Log(UserID, AuditType, net.IP)

Log doesn't actually do anything

type DummyCredentialStore Uses

type DummyCredentialStore struct{}

DummyCredentialStore is a dummy CredentialStore that doesn't do anything

func (DummyCredentialStore) Load Uses

func (d DummyCredentialStore) Load(UserID) (*Credential, error)

Load only returns errors

func (DummyCredentialStore) LoadContext Uses

func (d DummyCredentialStore) LoadContext(context.Context, UserID) (*Credential, error)

LoadContext only returns errors

func (DummyCredentialStore) Store Uses

func (d DummyCredentialStore) Store(*Credential) error

Store doesn't store anything

func (DummyCredentialStore) StoreContext Uses

func (d DummyCredentialStore) StoreContext(context.Context, *Credential) error

StoreContext doesn't store anything

type Kdf Uses

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 Log Uses

type Log struct {
    UserID UserID
    Time   time.Time
    Type   AuditType
    IP     net.IP
}

Log is an audit log entry

type MemoryAuditLogger Uses

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 Uses

func (al *MemoryAuditLogger) LastN(userID UserID, n int) []Log

LastN gets the last N logs for a user

func (*MemoryAuditLogger) LastNWithTypes Uses

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

func (*MemoryAuditLogger) Log Uses

func (al *MemoryAuditLogger) Log(userID UserID, at AuditType, ip net.IP)

Log will log the AuditLog in memory

type NotCommonPasswordNaive Uses

type NotCommonPasswordNaive struct {
    CommonPasswords map[string]bool
}

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 Uses

func (pp NotCommonPasswordNaive) PasswordAcceptable(password string) error

PasswordAcceptable accepts passwords that are not common passwords

type PasswordPoliciesNotMet Uses

type PasswordPoliciesNotMet struct {
    UnMetPasswordPolicies []PasswordPolicyError
}

PasswordPoliciesNotMet satisfies the error interface and tracks the unmet password policies

func (PasswordPoliciesNotMet) Error Uses

func (e PasswordPoliciesNotMet) Error() string

type PasswordPolicy Uses

type PasswordPolicy interface {
    PasswordAcceptable(string) error
}

PasswordPolicy is an interface used to determine if a password is acceptable. e.g. meets the given policy

type PasswordPolicyError Uses

type PasswordPolicyError struct {
    PasswordPolicy PasswordPolicy
    Err            error
}

PasswordPolicyError satisfies the error interface and describes the reason for a PasswordPolicy check failure

func (PasswordPolicyError) Error Uses

func (e PasswordPolicyError) Error() string

type Pbkdf2WorkFactor Uses

type Pbkdf2WorkFactor struct {
    Iter int
}

Pbkdf2WorkFactor specifies the work/cost parameters for PBKDF2

func (*Pbkdf2WorkFactor) Marshal Uses

func (wf *Pbkdf2WorkFactor) Marshal() ([]int, error)

Marshal returns the marshaled WorkFactor

func (*Pbkdf2WorkFactor) Unmarshal Uses

func (wf *Pbkdf2WorkFactor) Unmarshal(p []int) error

Unmarshal unmarshals the WorkFactor

type ScryptWorkFactor Uses

type ScryptWorkFactor struct {
    R   int
    P   int
    N   int
}

ScryptWorkFactor specifies the work/cost parameters for scrypt

func (*ScryptWorkFactor) Marshal Uses

func (wf *ScryptWorkFactor) Marshal() ([]int, error)

Marshal returns the marshaled WorkFactor

func (*ScryptWorkFactor) Unmarshal Uses

func (wf *ScryptWorkFactor) Unmarshal(p []int) error

Unmarshal unmarshals the WorkFactor

type UserID Uses

type UserID uint64

UserID is the ID of the user being authenticated

type WorkFactor Uses

type WorkFactor interface {
    Marshal() ([]int, error)
    Unmarshal([]int) error
}

WorkFactor describes the work/cost for a KDF The interface is similar to Go's "encoding" Marshaler/Unmarshalers

func NewWorkFactorForKdf Uses

func NewWorkFactorForKdf(kdf Kdf) (WorkFactor, error)

NewWorkFactorForKdf returns an empty new WorkFactor for the given kdf

Package passhash imports 16 packages (graph). Updated 2018-11-10. Refresh now. Tools for package owners.