netbirdio/netbird · error

invalid hash format

Error message

invalid hash format

What it means

Sentinel error argon2id.ErrInvalidHash (shared/hash/argon2id/argon2id.go:24), returned by decodeHash when Verify(secret, encodedHash) receives a string that is not a well-formed Argon2id PHC hash. The expected shape is exactly six $-separated parts: $argon2id$v=19$m=19456,t=2,p=1$<base64 salt>$<base64 hash>. Failures include wrong part count, a non-argon2id prefix, malformed version/parameter segments, or base64-decodable-but-invalid salt/hash fields; each is wrapped with detail (%w).

Source

Thrown at shared/hash/argon2id/argon2id.go:24

	"encoding/base64"
	"errors"
	"fmt"
	"strings"

	"golang.org/x/crypto/argon2"
)

const (
	argon2Memory      = 19456
	argon2Iterations  = 2
	argon2Parallelism = 1
	argon2SaltLength  = 16
	argon2KeyLength   = 32
)

var (
	// ErrInvalidHash is returned when the hash string format is invalid
	ErrInvalidHash = errors.New("invalid hash format")

	// ErrIncompatibleVersion is returned when the Argon2 version is not supported
	ErrIncompatibleVersion = errors.New("incompatible argon2 version")

	// ErrMismatchedHashAndPassword is returned when password verification fails
	ErrMismatchedHashAndPassword = errors.New("password does not match hash")
)

func Hash(secret string) (string, error) {
	salt := make([]byte, argon2SaltLength)
	if _, err := rand.Read(salt); err != nil {
		return "", fmt.Errorf("failed to generate salt: %w", err)
	}

	hash := argon2.IDKey(
		[]byte(secret),
		salt,
		argon2Iterations,

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Inspect the stored hash and compare it to the canonical form $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash> produced by argon2id.Hash
  2. If the stored value comes from another algorithm, verify with that algorithm or force a password reset and store a fresh argon2id.Hash result
  3. Fix the storage layer: widen the column, stop truncating, and ensure RawStdEncoding base64 survives round-trips

Example fix

// before: stored value was produced by bcrypt
stored := "$2a$10$abcdef..." // argon2id.Verify -> invalid hash format

// after: hash with argon2id when setting the password
stored, err := argon2id.Hash(password) // "$argon2id$v=19$m=19456,t=2,p=1$...$..."
Defensive patterns

Strategy: type-guard

Type guard

// isArgon2idHash reports whether s is a decodable argon2id PHC string
func isArgon2idHash(s string) bool {
    parts := strings.Split(s, "$")
    if len(parts) != 6 || parts[1] != "argon2id" {
        return false
    }
    var v int
    if _, err := fmt.Sscanf(parts[2], "v=%d", &v); err != nil {
        return false
    }
    var m, t uint32
    var p uint8
    if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &m, &t, &p); err != nil {
        return false
    }
    salt, err1 := base64.RawStdEncoding.DecodeString(parts[4])
    hash, err2 := base64.RawStdEncoding.DecodeString(parts[5])
    return err1 == nil && err2 == nil && len(salt) > 0 && len(hash) > 0
}

Try / catch

if err := argon2id.Verify(password, stored); err != nil {
    if errors.Is(err, argon2id.ErrInvalidHash) {
        // data problem, not a wrong password: quarantine the record, log the stored
        // hash's prefix (never the full value), and force a reset. Do NOT retry.
    }
    return err
}

Prevention

When it happens

Trigger: Calling argon2id.Verify with a value produced by a different hasher (bcrypt $2b$..., argon2i, SHA), a hash truncated by a DB column, a hash with standard (padded) base64 instead of RawStdEncoding, or an empty/garbage string.

Common situations: Migrating a user store from another password scheme without rehashing; VARCHAR column too short so the stored hash is cut; JSON/YAML round-trips that mangle the $ characters; passing the plaintext instead of the stored hash by mistake.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/061091538981ba19. Report an issue: GitHub.