Files
GraceSolutions 19289170da test(crypto): round-trip HashPassword and VerifyPassword
Guards the Argon2id parsing path against a regression where the encoded
hash (which contains dollar-delimited base64) was fed through fmt.Sscanf
and silently rejected valid passwords. Tests cover round-trip with a
variety of passwords including unicode, rejection of wrong passwords,
the six-segment encoded shape, and malformed-hash cases.
2026-04-23 16:39:51 -04:00

83 lines
2.4 KiB
Go

package crypto
import (
"strings"
"testing"
)
// TestHashVerifyRoundTrip guards the Argon2id parser: the encoded hash contains
// base64 fields that may include `$` or `+` characters, and an earlier version
// used fmt.Sscanf whose %s verb does not stop at `$`, silently returning a
// false negative for any valid password. See VerifyPassword docstring.
func TestHashVerifyRoundTrip(t *testing.T) {
cases := []string{
"admin",
"DevAdmin!2026",
"a",
"this is a reasonably long password with spaces 12345",
"πåß∂ƒ©˙∆˚¬",
}
for _, pw := range cases {
encoded, err := HashPassword(pw)
if err != nil {
t.Fatalf("HashPassword(%q) error: %v", pw, err)
}
ok, err := VerifyPassword(pw, encoded)
if err != nil {
t.Fatalf("VerifyPassword(%q) error: %v", pw, err)
}
if !ok {
t.Fatalf("VerifyPassword(%q) returned false for correct password", pw)
}
}
}
func TestVerifyPasswordRejectsWrongPassword(t *testing.T) {
encoded, err := HashPassword("correct-horse-battery-staple")
if err != nil {
t.Fatalf("HashPassword error: %v", err)
}
ok, err := VerifyPassword("wrong-password", encoded)
if err != nil {
t.Fatalf("VerifyPassword error: %v", err)
}
if ok {
t.Fatalf("VerifyPassword returned true for wrong password")
}
}
// TestHashEncodedShape asserts the encoded string has the exact six-segment
// `$`-delimited layout the parser depends on. A regression in HashPassword
// that changed the shape would silently break every login.
func TestHashEncodedShape(t *testing.T) {
encoded, err := HashPassword("x")
if err != nil {
t.Fatalf("HashPassword error: %v", err)
}
parts := strings.Split(encoded, "$")
if len(parts) != 6 {
t.Fatalf("expected 6 `$`-delimited segments, got %d: %q", len(parts), encoded)
}
if parts[0] != "" || parts[1] != "argon2id" || parts[2] != "v=19" {
t.Fatalf("unexpected prefix: %q", encoded)
}
if !strings.HasPrefix(parts[3], "m=") {
t.Fatalf("expected parameters segment to start with m=, got %q", parts[3])
}
}
func TestVerifyPasswordRejectsMalformedHash(t *testing.T) {
cases := []string{
"",
"not-a-hash",
"$argon2id$v=19$m=65536,t=1,p=4$onlysalt", // 5 segments
"$argon2id$v=18$m=65536,t=1,p=4$salt$hash",
"$scrypt$v=19$m=65536,t=1,p=4$salt$hash",
}
for _, bad := range cases {
if _, err := VerifyPassword("x", bad); err == nil {
t.Errorf("VerifyPassword(%q) expected error, got nil", bad)
}
}
}