1
Fork 0

Add support for hashing password with BCrypt.

This commit is contained in:
Juliusz Chroboczek 2023-11-23 13:57:37 +01:00
parent 2a32ac8f40
commit 8628344a6d
3 changed files with 66 additions and 14 deletions

View File

@ -9,22 +9,31 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"github.com/jech/galene/group" "github.com/jech/galene/group"
) )
func main() { func main() {
var algorithm string
var iterations int var iterations int
var cost int
var length int var length int
var saltLen int var saltLen int
var username string var username string
flag.StringVar(&username, "user", "", flag.StringVar(&username, "user", "",
"generate entry for given `username`") "generate entry for given `username`")
flag.IntVar(&iterations, "iterations", 4096, "`number` of iterations") flag.StringVar(&algorithm, "hash", "pbkdf2",
flag.IntVar(&length, "key", 32, "key `length`") "hashing `algorithm`")
flag.IntVar(&saltLen, "salt", 8, "salt `length`") 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() flag.Parse()
if len(flag.Args()) == 0 { if len(flag.Args()) == 0 {
@ -43,17 +52,34 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("Salt: %v", err) log.Fatalf("Salt: %v", err)
} }
var p group.Password
if strings.EqualFold(algorithm, "pbkdf2") {
key := pbkdf2.Key( key := pbkdf2.Key(
[]byte(pw), salt, iterations, length, sha256.New, []byte(pw), salt, iterations, length, sha256.New,
) )
p = group.Password{
p := group.Password{
Type: "pbkdf2", Type: "pbkdf2",
Hash: "sha-256", Hash: "sha-256",
Key: hex.EncodeToString(key), Key: hex.EncodeToString(key),
Salt: hex.EncodeToString(salt), Salt: hex.EncodeToString(salt),
Iterations: iterations, 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: "bcrypt",
Key: string(key),
}
} else {
log.Fatalf("Unknown hash type %v", algorithm)
}
e := json.NewEncoder(os.Stdout) e := json.NewEncoder(os.Stdout)
if username != "" { if username != "" {
creds := group.ClientPattern{ creds := group.ClientPattern{

View File

@ -8,6 +8,7 @@ import (
"errors" "errors"
"hash" "hash"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"github.com/jech/galene/conn" "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, []byte(pw), salt, p.Iterations, len(key), h,
) )
return bytes.Equal(key, theirKey), nil 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: default:
return false, errors.New("unknown password type") return false, errors.New("unknown password type")
} }

View File

@ -17,6 +17,10 @@ var pw3 = Password{
Iterations: 4096, Iterations: 4096,
} }
var pw4 = Password{ var pw4 = Password{
Type: "bcrypt",
Key: "$2a$10$afOr2f33onT/nDFFyT3mbOq5FMSw1wWXfyTXQTBMbKvZpBkoD3Qwu",
}
var pw5 = Password{
Type: "bad", Type: "bad",
} }
@ -27,6 +31,9 @@ func TestGood(t *testing.T) {
if match, err := pw3.Match("pass"); err != nil || !match { if match, err := pw3.Match("pass"); err != nil || !match {
t.Errorf("pw3 doesn't match (%v)", err) 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) { func TestBad(t *testing.T) {
@ -39,7 +46,10 @@ func TestBad(t *testing.T) {
if match, err := pw3.Match("bad"); err != nil || match { if match, err := pw3.Match("bad"); err != nil || match {
t.Errorf("pw3 matches") 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") t.Errorf("pw4 matches")
} }
} }
@ -50,7 +60,7 @@ func TestJSON(t *testing.T) {
t.Errorf("Expected \"pass\", got %v", string(plain)) 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) j, err := json.Marshal(pw)
if err != nil { if err != nil {
t.Fatalf("Marshal: %v", err) 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")
}
}
}