pwdhash

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Jan 17, 2026 License: MIT Imports: 4 Imported by: 0

README

go-pwdhash

Go Report Card go.dev reference

go-pwdhash is a Go-first password hashing helper that embraces the PHC (Password Hashing Competition) format, wraps Argon2id with safe defaults, and surfaces a minimal API for hashing, verification, and upgrades.

Argon2id Only

pwdhash intentionally supports Argon2id only. Algorithms that have already been superseded by Argon2id will not be added, reducing the chance of accidentally selecting outdated primitives. If a superior successor to Argon2id emerges, pwdhash will adopt it behind the same API surface.

Password Policies

pwdhash ships with opinionated Argon2id policies so applications can select a strength profile without touching raw parameters:

  • Interactive – user login flows where latency matters most.
  • Moderate – API keys, service-to-service calls, and privileged automation.
  • Sensitive – infrastructure secrets, root accounts, and long-lived credentials.

Policies prevent insecure configurations by clamping the underlying Argon2id memory, iteration, and parallelism values to vetted presets.

Highlights

  • PHC-compliant output – hashes look like $argon2id$v=19$... and parse cleanly across ecosystems.
  • Deterministic upgrade pathNeedsRehash compares stored parameters to the active policy so callers know exactly when to re-encode.
  • Extensible registry – advanced users may inject tuned Argon2id instances or alternate hashers via the option system.
  • Constant-time verification – comparisons use helpers under internal/subtle to avoid timing leaks.

Zeroization

pwdhash zeroizes password bytes, salts, and derived keys as soon as they are no longer needed. Due to Go runtime behavior this is a best-effort mitigation, but it significantly shortens the memory exposure window for sensitive material.

Installation

go get github.com/allisson/go-pwdhash

The module targets Go 1.24, depends on golang.org/x/crypto, and uses stretchr/testify solely for tests.

Quick Start

package main

import (
    "fmt"

    "github.com/allisson/go-pwdhash"
)

func main() {
    hasher, err := pwdhash.New(
        pwdhash.WithPolicy(pwdhash.PolicyInteractive),
    )
    if err != nil {
        panic(err)
    }

    encoded, err := hasher.Hash([]byte("s3cret"))
    if err != nil {
        panic(err)
    }

    ok, err := hasher.Verify([]byte("s3cret"), encoded)
    if err != nil {
        panic(err)
    }
    fmt.Println("password matches?", ok)

    rehash, err := hasher.NeedsRehash(encoded)
    if err != nil {
        panic(err)
    }
    fmt.Println("should upgrade hash?", rehash)
}

Usage Examples

Login Handler with Rehashes
func login(ctx context.Context, email, candidate string) error {
    storedHash := loadHashFromDB(ctx, email)

    hasher, err := pwdhash.New(pwdhash.WithPolicy(pwdhash.PolicyModerate))
    if err != nil {
        return fmt.Errorf("pwdhash init: %w", err)
    }

    ok, err := hasher.Verify([]byte(candidate), storedHash)
    if err != nil {
        return fmt.Errorf("pwdhash verify: %w", err)
    }
    if !ok {
        return errInvalidCredentials
    }

    needsUpgrade, err := hasher.NeedsRehash(storedHash)
    if err != nil {
        return fmt.Errorf("pwdhash rehash check: %w", err)
    }
    if needsUpgrade {
        upgraded, err := hasher.Hash([]byte(candidate))
        if err != nil {
            return fmt.Errorf("pwdhash rehash: %w", err)
        }
        persistHash(ctx, email, upgraded)
    }

    return nil
}
Role-Based Policies
type account struct {
    Email string
    Role  string
}

func policyForRole(role string) pwdhash.Policy {
    switch role {
    case "sre", "root":
        return pwdhash.PolicySensitive
    case "service", "api":
        return pwdhash.PolicyModerate
    default:
        return pwdhash.PolicyInteractive
    }
}

func hashForAccount(acct account, password []byte) (string, error) {
    policy := policyForRole(acct.Role)

    hasher, err := pwdhash.New(pwdhash.WithPolicy(policy))
    if err != nil {
        return "", err
    }

    return hasher.Hash(password)
}
Verifying External Hashes

When migrating from another service, you may only have PHC-formatted hashes. You can still apply the pwdhash registry to validate them while planning a gradual rehash:

func verifyLegacy(hash string, password []byte) (bool, error) {
    hasher, err := pwdhash.New()
    if err != nil {
        return false, err
    }

    ok, err := hasher.Verify(password, hash)
    if err != nil {
        return false, err
    }

    // Decide later whether to rehash with NeedsRehash.
    return ok, nil
}

Configuration

pwdhash.New accepts functional options:

  • pwdhash.WithPolicy selects one of the built-in presets.
  • pwdhash.WithHasher installs a custom pwdhash.Hasher (useful for bespoke Argon2id tuning or for experimenting with future algorithms).

Example of injecting custom parameters:

import (
    "github.com/allisson/go-pwdhash"
    "github.com/allisson/go-pwdhash/argon2"
)

func tunedHasher() (*pwdhash.PasswordHasher, error) {
    argon := &argon2.Argon2idHasher{
        Memory:      128 * 1024,
        Iterations:  4,
        Parallelism: 2,
        SaltLength:  16,
        KeyLength:   32,
    }

    return pwdhash.New(pwdhash.WithHasher(argon))
}

PHC Encoding

pwdhash serializes encoding.EncodedHash values using the canonical PHC layout:

$argon2id$v=19$m=65536,t=3,p=4$<base64(salt)>$<base64(hash)>

The parser validates algorithm identifiers, versions, parameter pairs, and Base64 payloads before handing them to the active hasher.

Testing & Tooling

go test ./...

Or use the convenience targets:

make lint   # golangci-lint run -v --fix
make test   # go test -covermode=count -coverprofile=count.out -v ./...

Contributing

  1. Fork and clone the repo.
  2. Run go test ./... (and make lint) before sending patches.
  3. Keep exports documented, prefer table-driven tests, and stick to gofmt/goimports.
  4. Argon2id is the focus; proposals for new algorithms should include rationale plus end-to-end tests.

License

MIT licensed. See LICENSE for details.

Documentation

Overview

Package pwdhash manages password hashing via configurable algorithms.

Package pwdhash manages password hashing via configurable algorithms.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidHash indicates that an encoded hash cannot be parsed.
	ErrInvalidHash = errors.New("invalid encoded hash")
)

Functions

This section is empty.

Types

type Hasher

type Hasher interface {
	ID() string
	Hash(password []byte) (string, error)
	Verify(password []byte, encoded string) (bool, error)
	NeedsRehash(encoded string) (bool, error)
}

Hasher represents a password hashing algorithm implementation.

type Option

type Option func(*config)

Option configures PasswordHasher construction.

func WithHasher

func WithHasher(h Hasher) Option

WithHasher overrides the default hashing algorithm.

func WithPolicy added in v0.2.0

func WithPolicy(p Policy) Option

WithPolicy selects a preset Argon2id configuration for the PasswordHasher.

type PasswordHasher

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

PasswordHasher manages password hashing operations via registered algorithms.

func New

func New(opts ...Option) (*PasswordHasher, error)

New constructs a PasswordHasher configured via the provided options.

func (*PasswordHasher) Hash

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

Hash encodes the provided password using the active hasher.

func (*PasswordHasher) NeedsRehash

func (p *PasswordHasher) NeedsRehash(encoded string) (bool, error)

NeedsRehash reports whether the encoded hash should be regenerated.

func (*PasswordHasher) Verify

func (p *PasswordHasher) Verify(password []byte, encoded string) (bool, error)

Verify checks whether the encoded hash matches the provided password.

type Policy added in v0.2.0

type Policy int

Policy represents a password hashing strength preset.

const (
	// PolicyInteractive balances CPU cost with low latency for login flows.
	PolicyInteractive Policy = iota
	// PolicyModerate increases cost for privileged accounts or admin portals.
	PolicyModerate
	// PolicySensitive maximizes cost for high-value secrets.
	PolicySensitive
)

Directories

Path Synopsis
Package argon2 contains the Argon2id hasher implementation.
Package argon2 contains the Argon2id hasher implementation.
internal
cast
Package cast provides narrow numeric conversion helpers.
Package cast provides narrow numeric conversion helpers.
encoding
Package encoding handles serialization and parsing of PHC strings.
Package encoding handles serialization and parsing of PHC strings.
subtle
Package subtle provides wrappers for constant-time operations.
Package subtle provides wrappers for constant-time operations.

Jump to

Keyboard shortcuts

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