From 388589331630337273243cad1377c890a9b26f51 Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Sun, 30 Apr 2023 21:44:48 +0200 Subject: [PATCH] db: split password & user --- internal/db/password.go | 72 +++++++++++++++++++++++++++++++++++++++++ internal/db/user.go | 62 ----------------------------------- 2 files changed, 72 insertions(+), 62 deletions(-) create mode 100644 internal/db/password.go diff --git a/internal/db/password.go b/internal/db/password.go new file mode 100644 index 0000000..ed7d038 --- /dev/null +++ b/internal/db/password.go @@ -0,0 +1,72 @@ +package db + +import ( + "crypto/rand" + "crypto/subtle" + "encoding/base64" + "errors" + "fmt" + "strings" + + "golang.org/x/crypto/argon2" +) + +const ( + pwdSaltSize = 16 + pwdHashSize = 32 + + // chosen from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + pwdMemory = 64 * 1024 + pwdThreads = 1 + pwdIterations = 2 + pwdParams = "m=65536,t=2,p=1,v=19" + pwdVersion = "v=19" + pwdAlgo = "argon2id" +) + +func HashPassword(password string) (string, error) { + if argon2.Version != 19 { + // go has no static asserts + panic("Unexpected argon2 version") + } + // Generate a salt for the password hash + salt := make([]byte, pwdSaltSize) + if _, err := rand.Read(salt); err != nil { + return "", err + } + + // Hash the password using argon2id + hash := argon2.IDKey([]byte(password), salt, pwdIterations, pwdMemory, pwdThreads, pwdHashSize) + + // Encode the salt and hash as a string in PHC format + encodedSalt := base64.RawStdEncoding.EncodeToString(salt) + encodedHash := base64.RawStdEncoding.EncodeToString(hash) + return fmt.Sprintf("$%s$%s$%s$%s$%s", pwdAlgo, pwdVersion, pwdParams, encodedSalt, encodedHash), nil +} + +var errInvalidDBPasswordFormat = errors.New("invalid password format in db") + +func comparePassword(hashedPassword string, password string) (bool, error) { + // Extract the salt and hash from the hashed password string + fields := strings.Split(hashedPassword, "$") + if len(fields) != 6 || fields[1] != pwdAlgo || fields[2] != pwdVersion || fields[3] != pwdParams { + return false, errInvalidDBPasswordFormat + } + encodedSalt, encodedHash := fields[4], fields[5] + + // Decode the salt and hash from base64 + salt, err := base64.RawStdEncoding.DecodeString(encodedSalt) + if err != nil { + return false, err + } + hash, err := base64.RawStdEncoding.DecodeString(encodedHash) + if err != nil { + return false, err + } + + // Hash the password using the extracted salt and parameters + computedHash := argon2.IDKey([]byte(password), salt, pwdIterations, pwdMemory, pwdThreads, pwdHashSize) + + // Compare the computed hash with the stored hash + return subtle.ConstantTimeCompare(hash, computedHash) == 1, nil +} diff --git a/internal/db/user.go b/internal/db/user.go index 54dcf1b..ce8417f 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -2,15 +2,12 @@ package db import ( "crypto/rand" - "crypto/subtle" "encoding/base64" "errors" "fmt" - "strings" "time" "github.com/google/uuid" - "golang.org/x/crypto/argon2" "gorm.io/gorm" ) @@ -66,65 +63,6 @@ func Authenticate(db *gorm.DB, username string, password string) (*User, error) return &user, nil } -const ( - pwdSaltSize = 16 - pwdHashSize = 32 - - // chosen from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html - pwdMemory = 64 * 1024 - pwdThreads = 1 - pwdIterations = 2 - pwdParams = "m=65536,t=2,p=1,v=19" - pwdVersion = "v=19" - pwdAlgo = "argon2id" -) - -func HashPassword(password string) (string, error) { - if argon2.Version != 19 { - // go has no static asserts - panic("Unexpected argon2 version") - } - // Generate a salt for the password hash - salt := make([]byte, pwdSaltSize) - if _, err := rand.Read(salt); err != nil { - return "", err - } - - // Hash the password using argon2id - hash := argon2.IDKey([]byte(password), salt, pwdIterations, pwdMemory, pwdThreads, pwdHashSize) - - // Encode the salt and hash as a string in PHC format - encodedSalt := base64.RawStdEncoding.EncodeToString(salt) - encodedHash := base64.RawStdEncoding.EncodeToString(hash) - return fmt.Sprintf("$%s$%s$%s$%s$%s", pwdAlgo, pwdVersion, pwdParams, encodedSalt, encodedHash), nil -} - -var errInvalidDBPasswordFormat = errors.New("invalid password format in db") -func comparePassword(hashedPassword string, password string) (bool, error) { - // Extract the salt and hash from the hashed password string - fields := strings.Split(hashedPassword, "$") - if len(fields) != 6 || fields[1] != pwdAlgo || fields[2] != pwdVersion || fields[3] != pwdParams { - return false, errInvalidDBPasswordFormat - } - encodedSalt, encodedHash := fields[4], fields[5] - - // Decode the salt and hash from base64 - salt, err := base64.RawStdEncoding.DecodeString(encodedSalt) - if err != nil { - return false, err - } - hash, err := base64.RawStdEncoding.DecodeString(encodedHash) - if err != nil { - return false, err - } - - // Hash the password using the extracted salt and parameters - computedHash := argon2.IDKey([]byte(password), salt, pwdIterations, pwdMemory, pwdThreads, pwdHashSize) - - // Compare the computed hash with the stored hash - return subtle.ConstantTimeCompare(hash, computedHash) == 1, nil -} - // DeleteUser deletes a user with the specified username from the database. func DeleteUser(db *gorm.DB, username string) error { // Find the user by username