passhash

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2018 License: Apache-2.0 Imports: 16 Imported by: 0

README

Passhash Build Status Code Coverage GoDoc Go Report Card GitHub Release Supported Go versions HackerOne

passhash addresses the dismal state of password management in Go by offering easy-to-use APIs to manage credentials (e.g. password hashes)

Note: The exposed surfaces (e.g. interfaces, structs, and struct fields) are in flux until v1.0.0 is released

Features

  • Simple, easy to use API
  • Tunable work factors
  • Auto-upgrading KDFs and work factors
  • Password usage audit log
  • Password policies

Available Password Policies

Password Policy Repo
AtLeastNRunes Included
NotCommonPasswordNaive Included

Available CredentialStores

Credential Store Repo
DummyCredentialStore Included
StringCredentialStore Included (in examples)
StringCredentialPepperedStore Included (in examples)

Available AuditLoggers

Audit Logger Repo
DummyAuditLogger Included
MemoryAuditLogger Included

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

Examples

Constants

This section is empty.

Variables

View Source
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

View Source
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)

View Source
var EmptyIP = net.IP{}

EmptyIP is the canonical value for an empty IP. This value should not be modified

View Source
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

func GetRandReader() io.Reader

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

func SetRandReader added in v0.3.0

func SetRandReader(reader io.Reader)

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 AuditType

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

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

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

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

func (*DummyAuditLogger) Log

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

Log doesn't actually do anything

type DummyCredentialStore

type DummyCredentialStore struct{}

DummyCredentialStore is a dummy CredentialStore that doesn't do anything

func (DummyCredentialStore) Load

Load only returns errors

func (DummyCredentialStore) LoadContext

LoadContext only returns errors

func (DummyCredentialStore) Store

Store doesn't store anything

func (DummyCredentialStore) StoreContext

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 Log

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

Log is an audit log entry

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

func (*MemoryAuditLogger) Log

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

Log will log the AuditLog in memory

type NotCommonPasswordNaive

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

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

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

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

type ScryptWorkFactor struct {
	R int
	P int
	N int
}

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 UserID

type UserID uint64

UserID is the ID of the user being authenticated

type WorkFactor

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

func NewWorkFactorForKdf(kdf Kdf) (WorkFactor, error)

NewWorkFactorForKdf returns an empty new WorkFactor for the given kdf

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL