{"record":{"id":"061091538981ba19","repo":"netbirdio/netbird","slug":"invalid-hash-format","errorCode":null,"errorMessage":"invalid hash format","messagePattern":"invalid hash format","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/hash/argon2id/argon2id.go","lineNumber":24,"sourceCode":"\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"golang.org/x/crypto/argon2\"\n)\n\nconst (\n\targon2Memory      = 19456\n\targon2Iterations  = 2\n\targon2Parallelism = 1\n\targon2SaltLength  = 16\n\targon2KeyLength   = 32\n)\n\nvar (\n\t// ErrInvalidHash is returned when the hash string format is invalid\n\tErrInvalidHash = errors.New(\"invalid hash format\")\n\n\t// ErrIncompatibleVersion is returned when the Argon2 version is not supported\n\tErrIncompatibleVersion = errors.New(\"incompatible argon2 version\")\n\n\t// ErrMismatchedHashAndPassword is returned when password verification fails\n\tErrMismatchedHashAndPassword = errors.New(\"password does not match hash\")\n)\n\nfunc Hash(secret string) (string, error) {\n\tsalt := make([]byte, argon2SaltLength)\n\tif _, err := rand.Read(salt); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to generate salt: %w\", err)\n\t}\n\n\thash := argon2.IDKey(\n\t\t[]byte(secret),\n\t\tsalt,\n\t\targon2Iterations,","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/shared/hash/argon2id/argon2id.go#L6-L42","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","If the stored value comes from another algorithm, verify with that algorithm or force a password reset and store a fresh argon2id.Hash result","Fix the storage layer: widen the column, stop truncating, and ensure RawStdEncoding base64 survives round-trips"],"exampleFix":"// before: stored value was produced by bcrypt\nstored := \"$2a$10$abcdef...\" // argon2id.Verify -> invalid hash format\n\n// after: hash with argon2id when setting the password\nstored, err := argon2id.Hash(password) // \"$argon2id$v=19$m=19456,t=2,p=1$...$...\"","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// isArgon2idHash reports whether s is a decodable argon2id PHC string\nfunc isArgon2idHash(s string) bool {\n    parts := strings.Split(s, \"$\")\n    if len(parts) != 6 || parts[1] != \"argon2id\" {\n        return false\n    }\n    var v int\n    if _, err := fmt.Sscanf(parts[2], \"v=%d\", &v); err != nil {\n        return false\n    }\n    var m, t uint32\n    var p uint8\n    if _, err := fmt.Sscanf(parts[3], \"m=%d,t=%d,p=%d\", &m, &t, &p); err != nil {\n        return false\n    }\n    salt, err1 := base64.RawStdEncoding.DecodeString(parts[4])\n    hash, err2 := base64.RawStdEncoding.DecodeString(parts[5])\n    return err1 == nil && err2 == nil && len(salt) > 0 && len(hash) > 0\n}","tryCatchPattern":"if err := argon2id.Verify(password, stored); err != nil {\n    if errors.Is(err, argon2id.ErrInvalidHash) {\n        // data problem, not a wrong password: quarantine the record, log the stored\n        // hash's prefix (never the full value), and force a reset. Do NOT retry.\n    }\n    return err\n}","preventionTips":["Always create stored hashes with argon2id.Hash so the format is canonical","Size the storage column generously (PHC strings are ~80-100 chars) and test round-trips","On login, distinguish ErrInvalidHash (data corruption) from ErrMismatchedHashAndPassword (wrong password); never count a format error as an auth failure"],"tags":["go","argon2","hashing","password","data-corruption"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}