argonize

package module
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2025 License: MIT Imports: 9 Imported by: 13

README

go-argonize

GitHub go.mod Go versionGo Reference

Go package to facilitate the use of the Argon2id password hashing algorithm from the "crypto/argon2" package. This package is tested compatibilities with PHP, Python and C implementations.

[!NOTE] As of v1.6.0, the library defaults use the RFC 9106 SECOND RECOMMENDED parameters (Argon2id, t=3, m=64 MiB, p=4, salt=16 bytes, key=32 bytes).

  • For details see issue #69

Usage

# Install the module
go get "github.com/KEINOS/go-argonize"
// Import the package
import "github.com/KEINOS/go-argonize"
Basic Example
func Example_basic() {
    // Your strong and unpredictable password
    password := []byte("my password")

    // Password-hash your password.
    //
    // By default it uses RFC 9106 SECOND RECOMMENDED parameters with
    // cryptographically random SALT.
    // The `Hash` function will produce different hashes. If you need
    // a static output, use `HashCustom` function with a fixed salt.
    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=3,p=4$ek6ZYdlRm2D5AsGV98TWKA$QAIDZEdIgwohrNX678mHc448LOmD7jGR4BGw/9YMMVU
    // the password is valid
    // the password is invalid
}
Example to use a saved hashed password
func Example_from_saved_password() {
    // Load the hashed password from a file, DB or etc.
    // Note that once hashed, passwords cannot be recovered from it and can only
    // be used to verify.
    savedPasswd := "$argon2id$v=19$m=65536,t=1,p=2$iuIIXq4foOhcGUH1BjE08w$kA+XOAMls8hzWg3J1sYxkeuK/lkU4HDRBf0zchdyllY"

    // Decode the saved password to an `argonize.Hashed` object.
    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
}

By default, the library uses the RFC 9106 SECOND RECOMMENDED parameters (argonize.RFC9106SecondRecommended preset).

This example uses the RFC 9106 FIRST RECOMMENDED preset for hashing. Which uses less iteration but requires more memory.

func Example_hashcustom_firstrecommended() {
    // Your strong and unpredictable password
    password := []byte("my password")

    // Use the RFC 9106 FIRST RECOMMENDED preset for hashing.
    // Note that this preset requires more memory than the default
    // parameters.
    params := argonize.RFC9106FirstRecommended

    // Generate a salt with the preset's salt length.
    //
    // HashCustom requires a random salt to prevent rainbow table attacks and to
    // ensure that users with the same password cannot be distinguished.
    // For consistency during testing, use a fixed salt value.
    salt, err := argonize.NewSalt(params.SaltLength)
    if err != nil {
      log.Fatal(err)
    }

    // Hash using the preset 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:
    // the password is valid
    // the password is invalid
}
Example with user-defined parameters

This example shows how to tweak parameters starting from defaults and use argonize.HashCustom with user-defined Params.

func Example_custom_user_defined_params() {
    password := []byte("my password")

    // Start from defaults and tweak values for this example.
    params := argonize.NewParams()
    params.Iterations = 2
    params.KeyLength = 32
    params.MemoryCost = 32 * 1024 // 32 MiB in KiB
    params.SaltLength = 16
    params.Parallelism = 2

    // HashCustom requires a random salt to prevent rainbow table attacks and to
    // ensure that users with the same password cannot be distinguished.
    salt, err := argonize.NewSalt(params.SaltLength)
    if err != nil {
      log.Fatal(err)
    }

    hashedObj := argonize.HashCustom(password, salt, params)

    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
}

FAQ

  • Q: Can I recover the original password from the hashed password?
    • A: No. Note that once hashed, passwords cannot be recovered and can only be used to verify.

Contributing

GitHub go.mod Go version Go Reference Opened Issues PR

Any Pull-Request for improvement is welcome!

Statuses

UnitTests golangci-lint CodeQL-Analysis PlatformTests

codecov Go Report Card

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: 3
Default key length: 32
Default memory cost: 65536
Default salt length: 16
Default parallelism: 4
the password is valid
the password is invalid
Example (Custom_user_defined_params)

This example shows how to tweak parameters starting from defaults and use `argonize.HashCustom` with user-defined `Params`.

// Your strong and unpredictable password
password := []byte("my password")

// Start from defaults and tweak values for this example.
params := argonize.NewParams()
params.Iterations = 2
params.KeyLength = 32
params.MemoryCost = 32 * 1024 // 32 MiB in KiB
params.SaltLength = 16
params.Parallelism = 2

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

hashedObj := argonize.HashCustom(password, salt, params)

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 (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 (Hashcustom_firstrecommended)

This example shows how to use the RFC 9106 FIRST RECOMMENDED preset for hashing. Note that this preset requires more memory than the default parameters.

// Your strong and unpredictable password
password := []byte("my password")

// Use the RFC 9106 FIRST RECOMMENDED preset for hashing.
params := argonize.RFC9106FirstRecommended

// Generate a salt with the preset's salt length.
salt, err := argonize.NewSalt(params.SaltLength)
if err != nil {
	log.Fatal(err)
}

// Hash using the preset 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:
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=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$DGSsb/y+38VbtrsbfVuD8xEgSNq4EL6/C0h7nEAqMTs
Hashed: 0c64ac6ffcbedfc55bb6bb1b7d5b83f3112048dab810bebf0b487b9c402a313b

Index

Examples

Constants

View Source
const (
	RFCFirstIterations  = 1
	RFCFirstKeyLength   = 32
	RFCFirstMemoryKiB   = 2 * 1024 * 1024 // 2 GiB in KiB
	RFCFirstSaltLength  = 16
	RFCFirstParallelism = 4
)

FIRST RECOMMENDED (RFC 9106) parameter presets. Less iteration but more memory.

View Source
const (
	RFCSecondIterations  = 3
	RFCSecondKeyLength   = 32
	RFCSecondMemoryKiB   = 64 * 1024 // 64 MiB in KiB
	RFCSecondSaltLength  = 16
	RFCSecondParallelism = 4
)

SECOND RECOMMENDED (RFC 9106) parameter presets. Default. More iteration but less memory.

Variables

View Source
var RFC9106FirstRecommended = &Params{
	Iterations:  RFCFirstIterations,
	KeyLength:   RFCFirstKeyLength,
	MemoryCost:  RFCFirstMemoryKiB,
	SaltLength:  RFCFirstSaltLength,
	Parallelism: RFCFirstParallelism,
}

RFC9106FirstRecommended contains a preset Params configured to follow the RFC 9106 "FIRST RECOMMENDED" settings for Argon2id.

Per RFC 9106 Section 4 the FIRST RECOMMENDED option is:

  • t = 1 (passes)
  • p = 4 (lanes / parallelism)
  • m = 2^21 kibibytes = 2 GiB = 2 * 1024 * 1024 KiB = 2,097,152 KiB
  • salt length = 128 bits (16 bytes)
  • tag/key length = 256 bits (32 bytes)

RFC9106FirstRecommended contains a preset Params configured to follow the RFC 9106 "FIRST RECOMMENDED" settings for Argon2id.

The variable is exported on purpose to provide a reusable preset. Disable the gochecknoglobals linter for this intentional global.

View Source
var RFC9106SecondRecommended = &Params{
	Iterations:  RFCSecondIterations,
	KeyLength:   RFCSecondKeyLength,
	MemoryCost:  RFCSecondMemoryKiB,
	SaltLength:  RFCSecondSaltLength,
	Parallelism: RFCSecondParallelism,
}

RFC9106SecondRecommended contains a preset Params configured to follow the RFC 9106 "SECOND RECOMMENDED" settings for Argon2id.

This preset is the default configuration.

Fields:

  • Iterations: number of passes over the memory (t). Default: 3.
  • KeyLength: output tag length in bytes (key length). Default: 32 bytes (256 bits).
  • MemoryCost: memory size in KiB (m). Default: 64 MiB = 64 * 1024 KiB.
  • SaltLength: salt length in bytes. Default: 16 bytes (128 bits).
  • Parallelism: number of lanes/threads (p). Default: 4.

RFC9106SecondRecommended contains a preset Params configured to follow the RFC 9106 "SECOND RECOMMENDED" settings for Argon2id.

The variable is exported on purpose to provide a reusable preset. Disable the gochecknoglobals linter for this intentional global.

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. The returned `n` and `err` values, `n` will be len of the input if `err` is 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.
	// Default is 3 to follow RFC 9106 SECOND RECOMMENDED (t=3).
	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.
	// Default is 64 MiB = 64 * 1024 KiB to follow RFC 9106 SECOND RECOMMENDED (m=64 MiB).
	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.
	// Default is 4 to follow RFC 9106 SECOND RECOMMENDED (p=4).
	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: 3
Default key length: 32
Default memory cost: 65536
Default salt length: 16
Default parallelism: 4

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.

Note that if lenOut is zero, an empty byte slice is returned with no error.

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
=============================================================================== This Go application takes a password and an optional salt as arguments and outputs the hashed password using the Argon2id algorithm.
=============================================================================== This Go application takes a password and an optional salt as arguments and outputs the hashed password using the Argon2id algorithm.

Jump to

Keyboard shortcuts

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