passwordhash

package
v1.99.1 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2024 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package passwordhash implements a practical model to create and verify a password hashes using a strong one-way hashing algorithm.

The model implements the best advice of OWASP Password Storage Cheat (https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) with specific default settings (parameters) and encoding format. Is uses the Argon2id algorithm (https://www.rfc-editor.org/info/rfc9106) as implemented in the golang.org/x/crypto/argon2 package.

The implementation is based on available standard cryptographic libraries, enforcing standard settings, and the way parameters and salt are encoded alongside the hashed password.

Password Storage Object

  • The input password is checked for min/max length. This prevents any computation if these requirements are not met.

  • The input password is hashed using the Argon2id algorithm with some default parameters and a random salt. Argon2id is currently the best recommendation from OWASP and it is a variant of Argon2 that provides a balanced approach to resisting both side-channel (Argon2i) and GPU-based (Argon2d) attacks.

  • The hashed password is then encoded as base64 and added as a "K" field in a JSON object, alongside the Argon2id parameters and base64 encoded salt.

  • The JSON object is then encoded as a base64 string ready for storage.

Example:

Password: "Test-Password-01234"

{
"P": { < Argon2id parameters.
"A": "argon2id", < Name of the hashing algorithm (always "argon2id").
"V": 19,         < Argon2id algorithm version (0x13).
"K": 32,         < Length of the returned byte-slice that can be used as a cryptographic key.
"S": 16,         < Length of the random password salt.
"T": 3,          < Number of passes over the memory.
"M": 65536,      < Size of the memory in KiB.
"P": 16          < Number of threads used by the hashing algorithm.
},
"S": "wQYm4bfktbHq2omIwFu+4Q==", < base64-encoded random salt of "P.S" length.
"K": "aU8hO900Odq6aKtWiWz3RW9ygn734liJaPtM6ynvkYI=" < base64-encoded Argon2id password hash.
}

While other serialization methods are available, JSON and base64 have been chosen for their extraordinary portability and compatibility with multiple systems and programming languages. The proposed JSON schema is such that it can be easily adapted to other hashing algorithms if required.

NOTE: Custom parameters should be agreed for production following the recommendations at: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-04#section-4 The current reference implementation uses recommended and sensible values by default.

The JSON object is then encoded as base64 string for storage (200 bytes in this example):

eyJQIjp7IkEiOiJhcmdvbjJpZCIsIlYiOjE5LCJLIjozMiwiUyI6MTYsIlQiOjMsIk0iOjY1NTM2LCJQIjoxNn0sIlMiOiJ3UVltNGJma3RiSHEyb21Jd0Z1KzRRPT0iLCJLIjoiYVU4aE85MDBPZHE2YUt0V2lXejNSVzl5Z243MzRsaUphUHRNNnludmtZST0ifQo=

Password Verification

  • The hashed password object string is retrieved from the storage and it is decoded using base64.

  • The resulting JSON is also decoded (unmarshalled) to access the fields values.

  • The field P.A (name of the hashing algorithm) is compared with the one in the library to ensure we are using the correct algorithm.

  • The field P.V (algorithm version) is compared with the one in the library to ensure we are using the correct version.

  • The provided live password is hashed using the same Argon2id algorithm with the parameters extracted from the JSON.

  • The hash of the live password is compared with the one retrieved from the JSON P.K field. The time taken for the comparison is a function of the length of the slices and is independent of the contents. This prevents timing attacks.

Index

Examples

Constants

View Source
const (
	// DefaultAlgo is the default algorithm used to hash the password.
	// It corresponds to Type y=2.
	DefaultAlgo = "argon2id"

	// DefaultKeyLen is the default length of the returned byte-slice that can be used as cryptographic key (Tag length).
	// It must be an integer number of bytes from 4 to 2^(32)-1.
	DefaultKeyLen = 32

	// DefaultSaltLen is the default length of the random password salt (Nonce S).
	// It must be not greater than 2^(32)-1 bytes.
	// The value of 16 bytes is recommended for password hashing.
	DefaultSaltLen = 16

	// DefaultTime (t) is the default number of passes (iterations) over the memory.
	// It must be an integer value from 1 to 2^(32)-1.
	DefaultTime = 3

	// DefaultMemory is the default size of the memory in KiB.
	// It must be an integer number of kibibytes from 8*p to 2^(32)-1.
	// The actual number of blocks is m', which is m rounded down to the nearest multiple of 4*p.
	DefaultMemory = 64 * 1024

	// DefaultMinPasswordLength is the default minimum length of the input password (Message string P).
	// It must have a length not greater than 2^(32)-1 bytes.
	DefaultMinPasswordLength = 8

	// DefaultMaxPasswordLength is the default maximum length of the input password (Message string P).
	// It must have a length not greater than 2^(32)-1 bytes.
	DefaultMaxPasswordLength = 4096
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Hashed

type Hashed struct {
	// Params contains the hashing parameters.
	Params *Params `json:"P"`

	// Salt is the password salt (Nonce S) of length Params.SaltLen.
	// The salt should be unique for each password.
	Salt []byte `json:"S"`

	// Key is the hashed password (Tag) of length Params.KeyLen.
	Key []byte `json:"K"`
}

Hashed contains the hashed password key and hashing parameters.

type Option

type Option func(*Params)

Option is a type alias for a function that configures the password hasher.

func WithKeyLen

func WithKeyLen(v uint32) Option

WithKeyLen overwrites the default length of the returned byte-slice that can be used as cryptographic key (Tag length). It must be an integer number of bytes from 4 to 2^(32)-1. The default value is 32 bytes.

func WithMaxPasswordLength added in v1.88.0

func WithMaxPasswordLength(v uint32) Option

WithMaxPasswordLength overwrites the default maximum length of the input password (Message string P). It must have a length not greater than 2^(32)-1 bytes.

func WithMemory

func WithMemory(v uint32) Option

WithMemory overwrites the default size of the memory in KiB. It must be an integer number of kibibytes from 8*p to 2^(32)-1. The actual number of blocks is m', which is m rounded down to the nearest multiple of 4*p.

func WithMinPasswordLength added in v1.88.0

func WithMinPasswordLength(v uint32) Option

WithMinPasswordLength overwrites the default maximum length of the input password (Message string P). It must have a length not greater than 2^(32)-1 bytes.

func WithSaltLen

func WithSaltLen(v uint32) Option

WithSaltLen overwrites the default length of the random password salt (Nonce S). It must be not greater than 2^(32)-1 bytes. The value of 16 bytes is recommended for password hashing.

func WithThreads

func WithThreads(v uint8) Option

WithThreads overwrites the default number ot threads (p) Threads represents the degree of parallelism p that determines how many independent (but synchronizing) computational chains (lanes) can be run. According to the RFC9106 it must be an integer value from 1 to 2^(24)-1, but in this implementation is limited to 2^(8)-1.

func WithTime

func WithTime(v uint32) Option

WithTime (t) is the default number of passes (iterations) over the memory. It must be an integer value from 1 to 2^(32)-1.

type Params

type Params struct {
	// Algo is the algorithm used to hash the password.
	// It corresponds to Type y=2.
	Algo string `json:"A"`

	// Version is the algorithm version.
	Version uint8 `json:"V"`

	// KeyLen is the length of the returned byte-slice that can be used as cryptographic key (Tag length).
	// It must be an integer number of bytes from 4 to 2^(32)-1.
	KeyLen uint32 `json:"K"`

	// SaltLen is the length of the random password salt (Nonce S).
	// It must be not greater than 2^(32)-1 bytes.
	// The value of 16 bytes is recommended for password hashing.
	SaltLen uint32 `json:"S"`

	// Time (t) is the default number of passes over the memory.
	// It must be an integer value from 1 to 2^(32)-1.
	Time uint32 `json:"T"`

	// Memory is the size of the memory in KiB.
	// It must be an integer number of kibibytes from 8*p to 2^(32)-1.
	// The actual number of blocks is m', which is m rounded down to the nearest multiple of 4*p.
	Memory uint32 `json:"M"`

	// Threads (p) is the degree of parallelism p that determines how many independent
	// (but	synchronizing) computational chains (lanes) can be run.
	// According to the RFC9106 it must be an integer value from 1 to 2^(24)-1,
	// but in this implementation is limited to 2^(8)-1.
	Threads uint8 `json:"P"`
	// contains filtered or unexported fields
}

Params contains the parameters for hashing the password.

func New

func New(opts ...Option) *Params

New creates a new instance of Params with the provided options applied.

func (*Params) EncryptPasswordHash

func (ph *Params) EncryptPasswordHash(key []byte, password string) (string, error)

EncryptPasswordHash extends the PasswordHash method by encrypting the password hash using the provided key (pepper). As the key is not stored along the password hash, it provides an additional layer of protection.

func (*Params) EncryptPasswordVerify

func (ph *Params) EncryptPasswordVerify(key []byte, password, hash string) (bool, error)

EncryptPasswordVerify extends the PasswordVerify method by decrypting the password hash using the provided key (pepper).

Example
package main

import (
	"fmt"
	"log"

	"github.com/Vonage/gosrvlib/pkg/passwordhash"
)

func main() {
	opts := []passwordhash.Option{
		passwordhash.WithKeyLen(32),
		passwordhash.WithSaltLen(16),
		passwordhash.WithTime(3),
		passwordhash.WithMemory(16_384),
		passwordhash.WithThreads(1),
		passwordhash.WithMinPasswordLength(16),
		passwordhash.WithMaxPasswordLength(128),
	}

	p := passwordhash.New(opts...)

	key := []byte("0123456789012345")

	secret := "Example-Password-02"

	hash, err := p.EncryptPasswordHash(key, secret)
	if err != nil {
		log.Fatal(err)
	}

	ok, err := p.EncryptPasswordVerify(key, secret, hash)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(ok)

	ok, err = p.EncryptPasswordVerify(key, "Example-Wrong-Password-02", hash)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(ok)

}
Output:

true
false

func (*Params) PasswordHash

func (ph *Params) PasswordHash(password string) (string, error)

PasswordHash generates a hashed password using the provided password string. It generates a random salt of length ph.SaltLen and uses the argon2id algorithm to hash the password with the salt, using the parameters specified in ph. The resulting hashed password, the salt and the parameters are returned as a json encoded as a base64 string.

func (*Params) PasswordVerify

func (ph *Params) PasswordVerify(password, hash string) (bool, error)

PasswordVerify verifies if a given password matches a hashed password generated with the PasswordHash method. It returns true if the password matches the hashed password, otherwise false.

Example
package main

import (
	"fmt"
	"log"

	"github.com/Vonage/gosrvlib/pkg/passwordhash"
)

func main() {
	opts := []passwordhash.Option{
		passwordhash.WithKeyLen(32),
		passwordhash.WithSaltLen(16),
		passwordhash.WithTime(3),
		passwordhash.WithMemory(16_384),
		passwordhash.WithThreads(1),
		passwordhash.WithMinPasswordLength(16),
		passwordhash.WithMaxPasswordLength(128),
	}

	p := passwordhash.New(opts...)

	secret := "Example-Password-01"

	hash, err := p.PasswordHash(secret)
	if err != nil {
		log.Fatal(err)
	}

	ok, err := p.PasswordVerify(secret, hash)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(ok)

	ok, err = p.PasswordVerify("Example-Wrong-Password-01", hash)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(ok)

}
Output:

true
false

Jump to

Keyboard shortcuts

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