argonize

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 12, 2024 License: MIT Imports: 9 Imported by: 12

README

go-argonize

Go Reference

Go package to facilitate the use of the Argon2id password hashing algorithm from the "crypto/argon2" package.

go get "github.com/KEINOS/go-argonize"
func Example() {
    // Your strong and unpredictable password
    password := []byte("my password")

    // Password hash your password
    hashedObj, err := argonize.Hash(password)
    if err != nil {
        log.Fatal(err)
    }

    // View the hashed password
    fmt.Println("Passwd to save:", hashedObj.String())

    // Verify password (golden case)
    if hashedObj.IsValidPassword([]byte("my password")) {
        fmt.Println("the password is valid")
    } else {
        fmt.Println("the password is invalid")
    }

    // Verify password (wrong case)
    if hashedObj.IsValidPassword([]byte("wrong password")) {
        fmt.Println("the password is valid")
    } else {
        fmt.Println("the password is invalid")
    }

    // Output:
    // Passwd to save: $argon2id$v=19$m=65536,t=1,p=2$ek6ZYdlRm2D5AsGV98TWKA$QAIDZEdIgwohrNX678mHc448LOmD7jGR4BGw/9YMMVU
    // the password is valid
    // the password is invalid
}

FAQ

  • Q: "How can I recover the original password from a hashed password?"
    • A: You can't. That is the purpose of hashes. You can only check if a password is valid. Note that hashes do not encrypt values.
  • Q: "If hashed passwords cannot be recovered, does this mean that hashed data is safe from theft?"
    • A: No. Hashing is not synonymous with "theft protection". After password hashing, it is no longer possible to "calculate" the original password, but a brute force attack or rainbow table attack can find the original password. Argon2id is currently the strongest password hashing algorithm, but if a hashed password is stolen, it takes so long to crack it that it only buys time until the next countermeasure can be taken. This is true regardless of the algorithm used. The problem is that the system is designed to enable data theft. If you do not understand this dilemma, the only way to prevent data theft is to not store the data in the first place. It is a strong statement, but it's a question that comes up so often that we had to write about it. 😭

Statuses

UnitTests golangci-lint CodeQL-Analysis PlatformTests

codecov Go Report Card

Contributing

Go Reference Opened Issues PR

Any Pull-Request for improvement is welcome!

  • Branch to PR: main
  • CIs on PR/Push: unit-tests golangci-lint codeQL-analysis platform-tests
  • Security policy

Documentation

Overview

Package argonize is a wrapper for the functions of the "golang.org/x/crypto/argon2" package to facilitate the use of the Argon2id password hashing algorithm.

* This package is strongly influenced by an article by Alex Edwards (https://www.alexedwards.net/).

Example
// The password to be hashed.
// Note that once hashed, passwords cannot be recovered and can only be
// verified.
password := []byte("my password")

// Password hashing using the Argon2id algorithm.
// The parameters use the settings recommended in the draft Argon2 RFC.
// To customize the parameters, use the argonize.HashCustom() function.
hashedObj, err := argonize.Hash(password)
if err != nil {
	log.Fatal(err)
}

// Use the Hashed.String() function to obtain the hash to be stored in the
// database as a string.
//
//   hashed := hashedObj.String()

// Validate the password against the hashed password.
if hashedObj.IsValidPassword([]byte("my password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}

if hashedObj.IsValidPassword([]byte("wrong password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}
Output:

the password is valid
the password is invalid
Example (Custom_params)
password := []byte("my password")

params := argonize.NewParams()
fmt.Println("Default iterations:", params.Iterations)
fmt.Println("Default key length:", params.KeyLength)
fmt.Println("Default memory cost:", params.MemoryCost)
fmt.Println("Default salt length:", params.SaltLength)
fmt.Println("Default parallelism:", params.Parallelism)

salt, err := argonize.NewSalt(params.SaltLength)
if err != nil {
	log.Fatal(err)
}

salt.AddPepper([]byte("my pepper"))

// Hash the password using the Argon2id algorithm with the custom parameters.
hashedObj := argonize.HashCustom(password, salt, params)

// Validate the password against the hashed password.
if hashedObj.IsValidPassword([]byte("my password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}

if hashedObj.IsValidPassword([]byte("wrong password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}
Output:

Default iterations: 1
Default key length: 32
Default memory cost: 65536
Default salt length: 16
Default parallelism: 2
the password is valid
the password is invalid
Example (From_saved_password)
// Load the hashed password from a file, DB or etc.
//nolint:gosec // hardcoded credentials as an example
savedPasswd := "$argon2id$v=19$m=65536,t=1,p=2$iuIIXq4foOhcGUH1BjE08w$kA+XOAMls8hzWg3J1sYxkeuK/lkU4HDRBf0zchdyllY"

// Decode the saved password to a Hashed object.
// Note that once hashed, passwords cannot be recovered and can only be
// verified.
hashObj, err := argonize.DecodeHashStr(savedPasswd)
if err != nil {
	log.Fatal(err)
}

// Validate the password against the hashed password.
if hashObj.IsValidPassword([]byte("my password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}

if hashObj.IsValidPassword([]byte("wrong password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}
Output:

the password is valid
the password is invalid
Example (Gob_encode_and_decode)
exitOnError := func(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

// The password to be hashed.
password := []byte("my secret password")

// Password hashing using the Argon2id algorithm with default parameters.
hashedObj1, err := argonize.Hash(password)
exitOnError(err)

// Obtain the Hashed object as a gob encoded byte slice. Useful when hashes
// are stored in the database in bytes. Also see the DecodeHashStr() example.
gobEnc, err := hashedObj1.Gob()
exitOnError(err)

// Re-create the Hashed object from the gob encoded byte slice. Suppose the
// gobEnc is the value stored in a database.
hashedObj2, err := argonize.DecodeHashGob(gobEnc)
exitOnError(err)

// The recovered Hashed object works as a validator.
if hashedObj2.IsValidPassword([]byte("my secret password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}

if hashedObj2.IsValidPassword([]byte("my bad password")) {
	fmt.Println("the password is valid")
} else {
	fmt.Println("the password is invalid")
}
Output:

the password is valid
the password is invalid
Example (Static_output)

Example_static_output demonstrates how to obtain a static output from the Argon2 algorithm for testing purposes.

Note that it is not recommended to use the static output as a password hash.

// Backup and defer restoring the random reader.
oldRandRead := argonize.RandRead
defer func() {
	argonize.RandRead = oldRandRead
}()

// Set/mock the random reader function as a static reader.
//
// Note that it is not recommended to use the static output as a password
// hash. The static output is only useful for testing purposes.
argonize.RandRead = func(b []byte) (int, error) {
	return copy(b, []byte("0123456789abcdef")), nil
}

pwd := "my very strong password"

hashedObj, err := argonize.Hash([]byte(pwd))
if err != nil {
	log.Panic(err)
}

fmt.Println("String:", hashedObj.String())
fmt.Printf("Hashed: %x\n", hashedObj.Hash)
Output:

String: $argon2id$v=19$m=65536,t=1,p=2$MDEyMzQ1Njc4OWFiY2RlZg$ytVHh/XAyQmzALFYvBRKET/7GswiVnDdubchuBeU/Yw
Hashed: cad54787f5c0c909b300b158bc144a113ffb1acc225670ddb9b721b81794fd8c

Index

Examples

Constants

View Source
const (
	// IterationsDefault is the default number of iterations of the parameter used by the Argon2id algorithm.
	IterationsDefault = uint32(1)
	// KeyLengthDefault is the default key length used in the Argon2id algorithm parameters.
	KeyLengthDefault = uint32(32)
	// MemoryCostDefault is the default amount of memory (KiB) used by the algorithm parameters.
	MemoryCostDefault = uint32(64 * 1024)
	// ParallelismDefault is the default number of threads used in the algorithm parameters.
	ParallelismDefault = uint8(2)
	// SaltLengthDefault is the default length of the salt used in the Argon2id algorithm parameters.
	SaltLengthDefault = uint32(16)
)

Variables

View Source
var RandRead = rand.Read

RandRead is a copy of `crypto.rand.Read` to ease testing. It is a helper function that calls Reader.Read using io.ReadFull. On return, n == len(b) if and only if err == nil.

Functions

func RandomBytes

func RandomBytes(lenOut uint32) ([]byte, error)

RandomBytes returns a random number of byte slice with the given length. It is a cryptographically secure random number generated from `crypto.rand` package.

If it is determined that a cryptographically secure number cannot be generated, an error is returned. Also note that if lenOut is zero, an empty byte slice is returned with no error.

Example
// Generate 32 byte length random value.
r1, err := argonize.RandomBytes(32)
if err != nil {
	log.Fatal(err)
}

// Generate 32 byte length random value.
r2, err := argonize.RandomBytes(32)
if err != nil {
	log.Fatal(err)
}

// Require that the two random values are different.
if bytes.Equal(r1, r2) {
	log.Fatal("random bytes are not random")
}

fmt.Println("OK")
Output:

OK

Types

type Hashed

type Hashed struct {
	Params *Params
	Salt   Salt
	Hash   []byte
}

Hashed holds the Argon2id hash value and its parameters.

func DecodeHashGob

func DecodeHashGob(gobEncHash []byte) (*Hashed, error)

DecodeHashGob decodes gob-encoded byte slice into a Hashed object. The argument should be the value from Hashed.Gob() method.

Note that the password remains hashed even if the object is decoded. Once hashed, the original password cannot be recovered in any case.

func DecodeHashStr

func DecodeHashStr(encodedHash string) (*Hashed, error)

DecodeHashStr decodes an Argon2id formatted hash string into a Hashed object. Which is the value returned by Hashed.String() method.

Note that the password remains hashed even if the object is decoded. Once hashed, the original password cannot be recovered in any case.

Example
// The Argon2id hash string to be decoded.
hashed := "$argon2id$v=19$m=65536,t=3,p=2$Woo1mErn1s7AHf96ewQ8Uw$D4TzIwGO4XD2buk96qAP+Ed2baMo/KbTRMqXX00wtsU"

// Decode the standard encoded hash representation of the Argon2 algorithm
// to a Hashed object.
hashObj, err := argonize.DecodeHashStr(hashed)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Hash: %x\n", hashObj.Hash)
fmt.Printf("Salt: %x\n", hashObj.Salt)
fmt.Println("Params:")
fmt.Println("- Iterations:", hashObj.Params.Iterations)
fmt.Println("- Key length:", hashObj.Params.KeyLength)
fmt.Println("- Memory cost:", hashObj.Params.MemoryCost)
fmt.Println("- Salt length:", hashObj.Params.SaltLength)
fmt.Println("- Parallelism:", hashObj.Params.Parallelism)

// Print the hashed string representation of the Argon2id algorithm.
// It should return the same string as the one passed to the DecodeHashStr()
// function.
fmt.Println("Stringer:", hashObj.String())
Output:

Hash: 0f84f323018ee170f66ee93deaa00ff847766da328fca6d344ca975f4d30b6c5
Salt: 5a8a35984ae7d6cec01dff7a7b043c53
Params:
- Iterations: 3
- Key length: 32
- Memory cost: 65536
- Salt length: 16
- Parallelism: 2
Stringer: $argon2id$v=19$m=65536,t=3,p=2$Woo1mErn1s7AHf96ewQ8Uw$D4TzIwGO4XD2buk96qAP+Ed2baMo/KbTRMqXX00wtsU

func Hash

func Hash(password []byte) (*Hashed, error)

Hash returns a Hashed object from the password using the Argon2id algorithm.

Note that this function, by its nature, consumes memory and CPU.

func HashCustom

func HashCustom(password []byte, salt []byte, parameters *Params) *Hashed

HashCustom returns a Hashed object from the password using the Argon2id algorithm.

Similar to the Hash() function, but allows you to specify the algorithm parameters.

func (Hashed) Gob

func (h Hashed) Gob() ([]byte, error)

Gob returns the gob-encoded byte slice of the current Hashed object. This is useful when hashes are stored in the database in bytes.

func (*Hashed) IsValidPassword

func (h *Hashed) IsValidPassword(password []byte) bool

IsValidPassword returns true if the given password is valid.

Note that the parameters must be the same as those used to generate the hash.

func (Hashed) String

func (h Hashed) String() string

String returns the encoded hash string using the standard encoded hash representation of the Argon2 algorithm.

To decode to a Hashed object, use the DecodeHashStr() function.

type Params

type Params struct {
	// Iterations is the number of iterations or passes over the memory.
	// Defaults to 1 which is the sensible number from the Argon2's draft RFC
	// recommends[2].
	Iterations uint32
	// KeyLength is the length of the key used in Argon2.
	// Defaults to 32.
	KeyLength uint32
	// MemoryCost is the amount of memory used by the algorithm in KiB.
	// Defaults to 64 * 1024 KiB = 64 MiB. Which is the sensible number from
	// the Argon2's draft RFC recommends[2].
	MemoryCost uint32
	// SaltLength is the length of the salt used in Argon2.
	// Defaults to 16.
	SaltLength uint32
	// Parallelism is the number of threads or lanes used by the algorithm.
	// Defaults to 2.
	Parallelism uint8
}

Params holds the parameters for the Argon2id algorithm.

func NewParams

func NewParams() *Params

NewParams returns a new Params object with default values.

Example
params := argonize.NewParams()

fmt.Println("Default iterations:", params.Iterations)
fmt.Println("Default key length:", params.KeyLength)
fmt.Println("Default memory cost:", params.MemoryCost)
fmt.Println("Default salt length:", params.SaltLength)
fmt.Println("Default parallelism:", params.Parallelism)
Output:

Default iterations: 1
Default key length: 32
Default memory cost: 65536
Default salt length: 16
Default parallelism: 2

func (*Params) SetDefault

func (p *Params) SetDefault()

SetDefault sets the fields to default values.

type Salt

type Salt []byte

Salt holds the salt value. You can add a pepper value to the salt through the AddPepper() method.

func NewSalt

func NewSalt(lenOut uint32) (Salt, error)

NewSalt returns a new Salt object with a random salt and given length.

func (*Salt) AddPepper

func (s *Salt) AddPepper(pepper []byte)

AddPepper add/appends a pepper value to the salt.

Example
// Create 16 byte length random salt.
salt, err := argonize.NewSalt(16)
if err != nil {
	log.Fatal(err)
}

noPepper := salt[:]

salt.AddPepper([]byte("pepper"))

withPepper := salt[:]

// Require peppered salt to be different from the original salt.
if bytes.Equal(noPepper, withPepper) {
	log.Fatal("salt and salt+pepper values should be different")
}

fmt.Println("OK")
Output:

OK

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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