passwd

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2019 License: BSD-3-Clause Imports: 14 Imported by: 0

README

Documentation License builds.sr.ht status

passwd

A simple golang password hashing package.

Description

New "password hashing" algorithms (PHC) have emerged in order to counter the increase in hardware assets available to crack passwords easily when they are stolen.

Those new algorithms increase the cost of an attack and slow down the discovery of the original password. Unfortunately they requires various non-trivial parameters to be used properly.

This package attempts to provide a safe and easy interface to produce/verify a hashed password, while giving the ability to tune for specific/custom needs if necessary.

3 algorithms are used:

  • bcrypt (using x/crypto/bcrypt, FOR LEGACY reasons)
  • scrypt (using x/crypto/scrypt)
  • argon2id (using x/crypto/argon2)

To keep things simple and to avoid a user to shoot himself in the foot, parameters choices are (for now) limited/translated into 2 static "profiles" for each algorithms:

  • Default
  • Paranoid

You can also decide to use your own Argon2 or Scrypt custom parameters with this package.

How to Use the package

About Hashing Profiles

It is an attempt to dimension crypto parameters to common use cases (interactive/web auth/blabla) vs file storage

  • Default : ~interactive.
  • Paranoid : file storage.

Custom profiles allow a user to define its own hashing parameters if those default don't fit.

Public vs Masked parameters

Commonly password hashing includes hashing parameters in order to provide interoperability.

if no interoperability is needed (outside your authentication needs) and to make things slightly more annoying for a regular attacker, you might "mask" your parameters (instead of embedding them in the resulting hash).

Public parameters (common practice)

Public parameters will embbed the derivation parameters in the resulting hash. This allows you to simply use the passwd.Compare() function against a hash without the need of a profile object.

Masked parameters

Masked parameters will remove the derivation parameters from the resulting hash. This requires you to passwd.NewMasked() before calling the profile Compare() method.

An attacker would have to not only grab the stored password, but also to guess the parameters you use with your key derivation in order to attack it offline.

!!! IMPORTANT !!! This is NOT what makes your hash safe, it just makes it slightly harder to attack offline.

Key'ed Hash

Key'd hashes makes it impossible to bruteforce (unless collision in hashes of course or the secret leaks), key'd hashes try to guarantee that leaked password cannot be attacked offline.

We used the method described here.

This requires you to <profile>.SetKey() before call the Hash() or Compare() function.

Examples

Password Hashing (public parameters):

create a password hashing object with Argon2 default profile:

p, err := passwd.New(passwd.Argon2idDefault)
if err != nil {
	// handle error
}
hashed, err := p.Hash( []byte("my1337p4ssw0rd!") )
if err != nil {
	// handle error
}
// hashed value: $2id$GlQX3F.KSYw1JLVv.LKDT.$1$65536$8$32$97DO7W9m/I8CTEQFKDa.VvEBTX1WepVv4qaWlt0OqH6

Password Compare (non-masked parameters / non-key'ed / bcrypt) :

check a hash against a password:

err := passwd.Compare(hashed, []byte("password"))
if err != nil {
	// handle error
}

Password Hashing (masked parameters):

p, err := passwd.NewMasked(passwd.Argon2idDefault)
if err != nil {
	// handle error
}

hashed, err := p.Hash( []byte("my1337p4ssw0rd!") )
if err != nil {
	// handle error
}
// hashed value: $2id$ihFFCGUfBHTqUfvUIos6X.$AmClxc.3uj6LsxjVGqpOZggyqIL.wQJ9zjY23ztsETK

Password Hashing (key'ed hashing + masked parameters)

p, err := passwd.NewMasked(passwd.Argon2idDefault)
if err != nil {
	// handle error
}

// set the hashing key.
err = p.SetKey([]byte("myhashingsecret")
if err != nil {
	// handle error
}

hashed, err := p.Hash( []byte("my1337p4ssw0rd!") )
if err != nil {
	// handle error
}
// hashed value: $2id$ihFFCGUfBHTqUfvUIos6X.$AmClxc.3uj6LsxjVGqpOZggyqIL.wQJ9zjY23ztsETK

Password Compare :

check a hash against a password, use the profile you defined to compare:

err := p.Compare(hashed, []byte("password"))
if err != nil {
	// handle error
}

Status

This package exclusively uses crypto algorithm implementations shipped in go extended crypto packages (located in x/crypto/)

This is a work in progress and the default are still being worked out/verified (feedback is welcome).

An example tool is provided in this repository:

Note: small issue with git.sr.ht that prevents for now to go get subpackages inside a module, i'm trying untangle the cause and provide a patch..

hopefully this helps understanding how to use this package.

Changelog

  • v0.2.1: (MASTER BRANCH / NOT RELEASED/TAGGED THIS IS JUST MASTER).

    • write key'd hash tests & concurrency tests. (ON GOING)
  • v0.2.0:

    • added key'd hash ability (using sha3-256/384 instead sha1/sha256) based on facebook key'd hmac salt/hash using SetKey() on the profile before any operation. more understanding
    • fixed the build.
    • added some tests for key'd hashes.
    • minor code rearrangement.
    • can be used concurrently lock-free.
    • bugfixes and code cleaning.
    • initial key'd hash tests & compatibility tests.
  • v0.1.3:

    • fix a salted+masked+custom profile comparison issue.
    • NewCustom example+tests (issue #3)
    • NewMasked tests (issue #1)
    • bcrypt is NOT supported in masked params mode.
    • minor cleanups.
  • v0.1.2: fix hash parsing.

  • v0.1.1: fix /issues/1

  • v0.1.0: initial release

Important Notes

bcrypt is just a wrapper to x/crypto/bcrypt, only scrypt and argon support masked parameters.

bcrypt is implemented for migration purposes, passwd.Compare() will works perfectly with x/crypto/bcrypt hashed passwords while using a modern profile to store new passwords.

Featuring (because there is always a star in your production..)

  • Argon2 for today key stretching.
  • Scrypt for key stretching.
  • Go because it works.

Resources

Project resources

Send patches and questions to ~eau/passwd@lists.sr.ht.

Subscribe to release announcements on ~eau/passwd-announce

Bugs & todo here: ~eau/passwd

Documentation

Overview

Package passwd provides simple primitives for hashing and verifying password.

Index

Examples

Constants

View Source
const (
	// Argon2id constant is to select the argon flavor in Argon2Params version field
	Argon2id = iota // default
	// Argon2i constant is to select argon flavor in Argon2Params version field
	Argon2i
)
View Source
const (

	// ErrParse when a parse error happened
	ErrParse = Error("parse error")
	// ErrHash when a hashing error occurs
	ErrHash = Error("hash error")
	// ErrUnsupported when a feature is not supported
	ErrUnsupported = Error("unsupported")
	// ErrMismatch is returned when Compare() call does not match
	ErrMismatch = Error("mismatch")
	// ErrUnsafe is to notify of password hashing parameters strength
	ErrUnsafe = Error("unsafe parameters")
)
View Source
const (
	Argon2idDefault HashProfile = iota
	Argon2idParanoid
	ScryptDefault
	ScryptParanoid
	BcryptDefault
	BcryptParanoid
	Argon2Custom // value for custom
	ScryptCustom // value for custom
	BcryptCustom // value for custom

	Argon int = iota
	Scrypt
	Bcrypt
)

Password hashing profiles available

Variables

This section is empty.

Functions

func Compare

func Compare(hashed, password []byte) error

Compare verify a non-key'd & non-mask'd hash values against a plaintext password.

Example
hashedPassword := []byte("$2id$aiOE.rPFUFkkehxc6utWY.$1$65536$8$32$Wv1IMP6xwaqVaQGOX6Oxe.eSEbozeRJLzln8ZlthZfS")
err := Compare(hashedPassword, []byte("prout"))
if err != nil {
	fmt.Printf("err: %v\n", err)
}
fmt.Printf("compared")
Output:

compared

Types

type Argon2Params

type Argon2Params struct {
	Version int
	Time    uint32
	Memory  uint32
	Saltlen uint32
	Keylen  uint32
	Thread  uint8
	Masked  bool // are parameters private
	// contains filtered or unexported fields
}

Argon2Params are the parameters for the argon2 key derivation.

type BcryptParams

type BcryptParams struct {
	Cost   int
	Masked bool // XXX UNUSED
}

BcryptParams are the parameters for the bcrypt key derivation.

type Error

type Error string

Error is the type helping defining errors as constants.

func (Error) Error

func (e Error) Error() string

type HashProfile added in v0.1.3

type HashProfile int

HashProfile is the type that describes the exported profile type available in this package

type Profile

type Profile struct {
	// contains filtered or unexported fields
}

Profile define the hashing profile you have select and is created using New() / NewMasked() / NewCustom()

func New

func New(profile HashProfile) (*Profile, error)

New instantiate a new Profile

Example
p, err := New(Argon2idDefault)
if err != nil {
	panic(err)
}

_, err = p.Hash([]byte("mylamepass!!"))
if err != nil {
	panic(err)
}
fmt.Printf("argon hashed")
Output:

argon hashed

func NewCustom

func NewCustom(params interface{}) (*Profile, error)

NewCustom instanciates a new Profile using user defined hash parameters

Example
customParams := Argon2Params{
	Version: Argon2id,
	Time:    1,
	Memory:  32 * 1024,
	Thread:  8,
	Saltlen: 16,
	Keylen:  32,
	Masked:  true,
}
p, err := NewCustom(&customParams)
if err != nil {
	panic(err)
}
_, err = p.Hash([]byte("mylamepass!!"))
if err != nil {
	panic(err)
}
fmt.Printf("custom argon hashed")
Output:

custom argon hashed

func NewMasked

func NewMasked(profile HashProfile) (*Profile, error)

NewMasked instanciates a new masked Profile. "masked" translate to the fact that no hash parameters will be provided in the resulting hash.

Example
p, err := NewMasked(Argon2idDefault)
if err != nil {
	panic(err)
}
_, err = p.Hash([]byte("mylamepass!!"))
if err != nil {
	panic(err)
}
fmt.Printf("argon hashed")
Output:

argon hashed

func (*Profile) Compare

func (p *Profile) Compare(hashed, password []byte) error

Compare method compared a computed hash against a plaintext password for the associated profile. This function is mainly here to allow to work with "masked" hashes where we don't provide the Hash parameters in the hashed values.

fixed / another code path might come if (for example): - profile is BcryptSomething - compared hash is $2id$salt$...

Example
hashedPassword := []byte("$2id$PEKC7uef8j09Tx8WHKrpwu$e1ZINFSkJz4/hM0mytQCjrEmVRRb6VtkXwAZibZhgYm")
p, err := NewMasked(Argon2idDefault)
if err != nil {
	panic(err)
}
err = p.Compare(hashedPassword, []byte("prout"))
if err != nil {
	//panic(err)
	fmt.Printf("err: %v\n", err)
}
fmt.Printf("compared")
Output:

compared

func (*Profile) Derive added in v0.1.3

func (p *Profile) Derive(password, salt []byte) ([]byte, error)

Derive is the Profile's method for computing a cryptographic key usable with symmetric AEAD using the user provided Profile, password and salt it will return the derived key.

func (*Profile) Hash

func (p *Profile) Hash(password []byte) ([]byte, error)

Hash is the Profile's method for computing the hash value respective of the selected profile. it takes the plaintext password to hash and output its hashed value ready for storage

func (*Profile) SetKey added in v0.2.0

func (p *Profile) SetKey(secret []byte) error

SetKey setup a secret associated with the profile currently in use following produced hashes, will use the new key'ed hashing algorithm

type ScryptParams

type ScryptParams struct {
	N       uint32 // cpu memory cost must be > 1 && %2 == 0
	R       uint32 // parallelization cost param -> r*p < 2^30 (go implementation specific)
	P       uint32 // parallelization cost param -> r*p < 2^30 (go implementation specific)
	Saltlen uint32 // 128 bits min.
	Keylen  uint32 // 128 bits min.
	Masked  bool   // are parameters private
	// contains filtered or unexported fields
}

ScryptParams are the parameters for the scrypt key derivation.

Jump to

Keyboard shortcuts

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