From 8628344a6dbd770f9bce79ea484d41a1859d652f Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Thu, 23 Nov 2023 13:57:37 +0100 Subject: [PATCH] Add support for hashing password with BCrypt. --- .../galene-password-generator.go | 50 ++++++++++++++----- group/client.go | 7 +++ group/client_test.go | 23 ++++++++- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/galene-password-generator/galene-password-generator.go b/galene-password-generator/galene-password-generator.go index 413459e..5c6f36c 100644 --- a/galene-password-generator/galene-password-generator.go +++ b/galene-password-generator/galene-password-generator.go @@ -9,22 +9,31 @@ import ( "fmt" "log" "os" + "strings" + "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/pbkdf2" "github.com/jech/galene/group" ) func main() { + var algorithm string var iterations int + var cost int var length int var saltLen int var username string flag.StringVar(&username, "user", "", "generate entry for given `username`") - flag.IntVar(&iterations, "iterations", 4096, "`number` of iterations") - flag.IntVar(&length, "key", 32, "key `length`") - flag.IntVar(&saltLen, "salt", 8, "salt `length`") + flag.StringVar(&algorithm, "hash", "pbkdf2", + "hashing `algorithm`") + flag.IntVar(&iterations, "iterations", 4096, + "`number` of iterations (pbkdf2)") + flag.IntVar(&cost, "cost", bcrypt.DefaultCost, + "`cost` (bcrypt)") + flag.IntVar(&length, "key", 32, "key `length` (pbkdf2)") + flag.IntVar(&saltLen, "salt", 8, "salt `length` (pbkdf2)") flag.Parse() if len(flag.Args()) == 0 { @@ -43,17 +52,34 @@ func main() { if err != nil { log.Fatalf("Salt: %v", err) } - key := pbkdf2.Key( - []byte(pw), salt, iterations, length, sha256.New, - ) + var p group.Password + if strings.EqualFold(algorithm, "pbkdf2") { + key := pbkdf2.Key( + []byte(pw), salt, iterations, length, sha256.New, + ) + p = group.Password{ + Type: "pbkdf2", + Hash: "sha-256", + Key: hex.EncodeToString(key), + Salt: hex.EncodeToString(salt), + Iterations: iterations, + } + } else if strings.EqualFold(algorithm, "bcrypt") { + key, err := bcrypt.GenerateFromPassword( + []byte(pw), cost, + ) + if err != nil { + log.Fatalf("Couldn't hash password: %v", err) + } - p := group.Password{ - Type: "pbkdf2", - Hash: "sha-256", - Key: hex.EncodeToString(key), - Salt: hex.EncodeToString(salt), - Iterations: iterations, + p = group.Password{ + Type: "bcrypt", + Key: string(key), + } + } else { + log.Fatalf("Unknown hash type %v", algorithm) } + e := json.NewEncoder(os.Stdout) if username != "" { creds := group.ClientPattern{ diff --git a/group/client.go b/group/client.go index 2215a34..7c4d088 100644 --- a/group/client.go +++ b/group/client.go @@ -8,6 +8,7 @@ import ( "errors" "hash" + "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/pbkdf2" "github.com/jech/galene/conn" @@ -47,6 +48,12 @@ func (p Password) Match(pw string) (bool, error) { []byte(pw), salt, p.Iterations, len(key), h, ) return bytes.Equal(key, theirKey), nil + case "bcrypt": + err := bcrypt.CompareHashAndPassword([]byte(p.Key), []byte(pw)) + if err == bcrypt.ErrMismatchedHashAndPassword { + return false, nil + } + return err == nil, err default: return false, errors.New("unknown password type") } diff --git a/group/client_test.go b/group/client_test.go index 9352fc6..6b1253c 100644 --- a/group/client_test.go +++ b/group/client_test.go @@ -17,6 +17,10 @@ var pw3 = Password{ Iterations: 4096, } var pw4 = Password{ + Type: "bcrypt", + Key: "$2a$10$afOr2f33onT/nDFFyT3mbOq5FMSw1wWXfyTXQTBMbKvZpBkoD3Qwu", +} +var pw5 = Password{ Type: "bad", } @@ -27,6 +31,9 @@ func TestGood(t *testing.T) { if match, err := pw3.Match("pass"); err != nil || !match { t.Errorf("pw3 doesn't match (%v)", err) } + if match, err := pw4.Match("pass"); err != nil || !match { + t.Errorf("pw4 doesn't match (%v)", err) + } } func TestBad(t *testing.T) { @@ -39,7 +46,10 @@ func TestBad(t *testing.T) { if match, err := pw3.Match("bad"); err != nil || match { t.Errorf("pw3 matches") } - if match, err := pw4.Match("bad"); err == nil || match { + if match, err := pw4.Match("bad"); err != nil || match { + t.Errorf("pw4 matches") + } + if match, err := pw5.Match("bad"); err == nil || match { t.Errorf("pw4 matches") } } @@ -50,7 +60,7 @@ func TestJSON(t *testing.T) { t.Errorf("Expected \"pass\", got %v", string(plain)) } - for _, pw := range []Password{pw1, pw2, pw3, pw4} { + for _, pw := range []Password{pw1, pw2, pw3, pw4, pw5} { j, err := json.Marshal(pw) if err != nil { t.Fatalf("Marshal: %v", err) @@ -85,3 +95,12 @@ func BenchmarkPBKDF2(b *testing.B) { } } } + +func BenchmarkBCrypt(b *testing.B) { + for i := 0; i < b.N; i++ { + match, err := pw4.Match("bad") + if err != nil || match { + b.Errorf("pw3 matched") + } + } +}