passgo

package module
v0.0.0-...-f8cd925 Latest Latest
Warning

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

Go to latest
Published: Apr 28, 2026 License: BSL-1.0 Imports: 10 Imported by: 0

README

PassGo

An extensible password hashing library with native support for the features of modern hashing algorithms.

PassGo makes it easy to follow best practices for password storage, including salting and peppering passwords, increasing algorithm work factors over time, and rotating outdated hashes.

It also supports advanced features, such as custom hashing algorithms and password validation logic, easy algorithm switching, and password blocklists.

Installation

You can add PassGo to your Go project by running the following command:

go get codefloe.com/binanary/passgo

Usage

Follow the link below for usage instructions.

Go Reference

Support

Open an issue to report a bug, request a feature, or ask for help using this library. If PassGo gains traction, anonymous support options will be added.

Security

If you find a vulnerability, please open an issue stating that without providing any specifics. A project maintainer will provide you with private contact info.

Contributing

Pull requests that implement new features or improve existing functionality are welcomed. Contributions are subject to the Legal section of this file.

Pull requests are more likely to be accepted if they fix a confirmed bug or implement approved changes.

If you are proposing changes that would remove functionality or change the public-facing API, please open an issue instead.

Testing

Before opening a pull request, please ensure all existing tests pass by running the following command from the project root:

go test ./...

Pull requests that add new features are not required to include formal tests for those features, but must verify functionality locally before submitting.

Project maintainers will write tests for new features as needed, so please document any known or expected edge cases in relevant pull requests.

By submitting proposed changes to this project, you agree to license your contributions under the Boost Software License 1.0. You also grant project maintainers the right to relicense your contributions under more permissive terms.

These grants take effect upon approval and incorporation of your contribution into the project. They are granted irrevocably and in perpetuity.

Acknowledgments

This library was inspired by Alex Edwards' blog post on implementing password hashing with Argon2 in Go. His book, Let's Go, is a great resource for learning how to build production-ready systems with Go.

License

This library is open source under the Boost Software License 1.0. An online copy of the license is available at https://www.boost.org/LICENSE_1_0.txt

Portions of argon2id.go are derived from alexedwards/argon2id and are additionally subject to the MIT license.

Documentation

Overview

Package passgo is an extensible password hashing library with native support for the features of modern password hashing algorithms.

Quick Start

Hashing a password:

hash, err := passgo.Hash(password)
if err != nil {
	// handle error
	return
}
// store hash in database

Comparing a password against a hash:

status, err := passgo.Compare(hash, password)
if err != nil {
	if errors.Is(err, passgo.ErrWrongPassword) {
		// handle incorrect password
		return
	}
	// handle unexpected error
	return
}
// log the user in

When logging a user in, you can optionally take additional measures for security:

if status.ValidationError != nil {
	// prompt the user to update their password
	return
}
if status.OutdatedHash {
	// re-hash the existing password
}

This approach will use a configuration based on recommended defaults, including a default hashing algorithm.

Customization

You can customize password validation and hashing behavior by creating a Hasher. The API remains the same.

import "codefloe.com/binanary/passgo/algorithms"

hasher := passgo.NewHasher(algorithms.Argon2id{}, passgo.HasherOptions{MinChars: 15})
hash, err := hasher.Hash(password) // Returns an error if password is under 15 characters
status, err := hasher.Compare(hash, password)

The HasherOptions struct definition contains more information on algorithm-agnostic customization options.

In general, the parameters passed to NewHasher only impact the creation of new hashes. They will not prevent a correct password from being successfully compared with an existing hash.

For example, this is completely valid:

bcryptHasher := passgo.NewHasher(algorithms.Bcrypt{}, passgo.HasherOptions{})
password := "16characterslong"
bcryptHash, _ := bcryptHasher.Hash(password)

scryptHasher := passgo.NewHasher(algorithms.Scrypt{}, passgo.HasherOptions{MinChars: 17})
_, err := scryptHasher.Compare(bcryptHash, password)
if err == nil {
	// this code will run
}

The only exception is HasherOptions.Pepper. Adding or changing this in a system with existing hashes will invalidate all passwords.

The behavior described above makes it easy to switch to a new hashing algorithm. Simply change the algorithm passed to NewHasher and future hashes will use the new algorithm. Existing hashes will continue to work normally.

Consider infrastructure constraints before switching to a new algorithm. For example, a database configured to store the fixed-length outputs of the bcrypt algorithm may truncate or reject the longer, variable-length hashes produced by argon2id.

Outdated Hash Detection

Modern best practices for credential storage involve increasing the security of hashing algorithms over time as hardware becomes more powerful. This includes recreating existing hashes using the more secure configuration the next time each user logs in.

This library assumes that the current algorithm is more secure than any others used previously. Therefore, hashes created using a different algorithm than the hasher performing the comparison are always considered outdated.

bcryptHash := "$2a$10$0J/kHBT7ucbO0O6I9dJgXu9fMIYUOmn0QV5.SKDSD0hO4tYQC4ryS"

argon2idHasher := passgo.NewHasher(algorithms.Argon2id{}, passgo.HasherOptions{})
status, err := argon2idHasher.Compare(bcryptHash, "password")
if err == nil && status.OutdatedHash {
	// this code will run
}

Hashes created using the same hashing algorithm are considered outdated if they use less secure work factors.

oldBcryptHasher := passgo.NewHasher(algorithms.Bcrypt{Cost: 10}, passgo.HasherOptions{})
insecureHash, _ := oldBcryptHasher.Hash(password)

newBcryptHasher := passgo.NewHasher(algorithms.Bcrypt{Cost: 12}, passgo.HasherOptions{})
status, err := newBcryptHasher.Compare(insecureHash, password)
if err == nil && status.OutdatedHash {
	// this code will run
}

An increase to any work factor is assumed to improve security. Increasing some work factors and changing others such that overall security is not increased will result in false positives for outdated hashes. This behavior can be altered using custom algorithms.

Secure by Default

Throughout this library, unset/zero values will often be replaced with defaults recommended by authoritative sources, including:

  • Internet Engineering Task Force (IETF) RFCs
  • Internet Research Task Force (IRTF) RFCs
  • National Institute of Standards and Technology (NIST) Special Publications
  • Open Web Application Security Project (OWASP) Cheat Sheets
  • RFCs produced by very large open source projects

All defaults are documented with a rationale or link to the recommendation in the source code. Expect defaults to change over time, including in minor and patch releases.

This library does not constrain manually set values. It will let you use insecure configurations if you don't know what you are doing. Read the documentation for each algorithm you use and every parameter you set.

When in doubt, OWASP's recommendations are good minimums.

Other Features

This summary covers core functionality. There are many other features not described here, including:

Index

Constants

This section is empty.

Variables

View Source
var ErrBlockedPassword = errors.New("passgo: attempted to hash a blocked password")

ErrBlockedPassword is returned when attempting to hash a password on the blocklist

View Source
var ErrInvalidHash error = errors.New("passgo: could not extract id from hash")

ErrInvalidHash is returned when a hashed password does not follow the expected format

View Source
var ErrWrongPassword error = errors.New("passgo: password and hash do not match")

ErrWrongPassword is returned when a password was not used to create the hash it is being compared against

Functions

func Hash

func Hash(password string) (hash string, err error)

Hash validates a password and returns the associated hash. An error is returned if the password fails validation.

Types

type ErrPasswordTooLong

type ErrPasswordTooLong struct {
	Max  int
	Unit string
}

ErrPasswordTooLong is returned when attempting to hash a password that violates HasherOptions.MaxChars or HasherOptions.MaxBytes

func (ErrPasswordTooLong) Error

func (e ErrPasswordTooLong) Error() string

type ErrPasswordTooShort

type ErrPasswordTooShort struct {
	Min  int
	Unit string
}

ErrPasswordTooShort is returned when attempting to hash a password that violates HasherOptions.MinChars or HasherOptions.MinBytes

func (ErrPasswordTooShort) Error

func (e ErrPasswordTooShort) Error() string

type ErrUnsupportedAlgorithm

type ErrUnsupportedAlgorithm string

ErrUnsupportedAlgorithm is returned when a hashed password uses an unknown algorithms.HashID

func (ErrUnsupportedAlgorithm) Error

func (e ErrUnsupportedAlgorithm) Error() string

type Hasher

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

Hasher provides custom hashing behavior via Hasher.Hash and Hasher.Compare

If you don't need a specific configuration, use the package-level Hash and Compare functions for recommended defaults.

func NewHasher

func NewHasher(a algorithms.Algorithm, o HasherOptions) *Hasher

NewHasher returns a Hasher that uses the configuration specified by the parameters. Pass a nil algorithm to use the recommended default.

func (*Hasher) Compare

func (p *Hasher) Compare(hash string, password string) (status Status, err error)

Compare checks a plaintext password against a hash and determines the status of the hash. This method works on properly-formatted hashes created using any registered algorithms.Algorithm.

func (*Hasher) Hash

func (p *Hasher) Hash(password string) (hash string, err error)

Hash validates a password and returns the associated hash. An error is returned if the password fails validation.

type HasherOptions

type HasherOptions struct {
	// SaltLen sets the length (in bytes) of the salt applied to passwords.
	// This will be ignored for algorithms that perform their own salting or do not support salting.
	//
	// A value below zero will disable salt generation.
	SaltLen int

	// Pepper is appended to all passwords before hashing and comparing.
	//
	// WARNING: Existing hashes will fail to match if this is changed.
	Pepper []byte

	// MinChars sets the minimum number of characters (runes) required in a password.
	// The Hash method will return ErrPasswordTooShort if this is violated.
	MinChars int

	// MinBytes sets the minimum number of bytes permitted in a password.
	// The Hash method will return ErrPasswordTooShort if this is violated.
	//
	// MinChars is generally more intuitive for users.
	MinBytes int

	// MaxChars sets the maximum number of characters (runes) permitted in a password.
	// The Hash method will return ErrPasswordTooLong if this is violated.
	//
	// Avoid setting this if possible, as it can harm security.
	MaxChars int

	// MaxBytes sets the maximum number of bytes permitted in a password.
	// The Hash method will return ErrPasswordTooLong if this is violated.
	//
	// Avoid setting this if possible, as it can harm security.
	MaxBytes int

	// A case-sensitive list disallowed passwords.
	// The Hash method will return ErrBlockedPassword if a password appears on this list.
	//
	// Unlike other values that impact security, this field has no meaningful default.
	Blocklist []string

	// Custom validation logic to run when hashing a password.
	// For otherwise acceptable passwords, the Hash method will return the same error (if any) as this function.
	Validate func(password string) (err error)
}

HasherOptions holds algorithm-agnostic hashing options. Leave fields unset to use recommended defaults.

type Status

type Status struct {
	// OutdatedHash indicates whether the Hasher is configured to produce more secure hashes than the one provided.
	//
	// Consider creating a new hash for the password if this is true (unless the user is already being asked to update their password).
	OutdatedHash bool

	// ValidationError is the error the Hash method would return for the same password given the Hasher's current configuration.
	//
	// Consider prompting the user to update their password if this is not nil.
	ValidationError error
}

Status holds information about a hashed password

func Compare

func Compare(hash string, password string) (status Status, err error)

Compare checks a plaintext password against a hash and determines the status of the hash.

Directories

Path Synopsis
Package algorithms defines an interface for password hashing algorithms and provides implementations of several popular algorithms using this interface.
Package algorithms defines an interface for password hashing algorithms and provides implementations of several popular algorithms using this interface.
internal

Jump to

Keyboard shortcuts

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