mirror of
https://github.com/lejianwen/rustdesk-api.git
synced 2026-02-03 04:44:12 +00:00
feat(password): Password hashing with bcrypt (#290)
* feat(password): add configurable password hashing with md5 and bcrypt * docs: add password hashing algorithm configuration (bcrypt/md5) * feat(password): better bcrypt fallback and minor refactoring * feat(password): handle errors in password encryption and verification * feat(password): remove password hashing algorithm configuration
This commit is contained in:
42
utils/password.go
Normal file
42
utils/password.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// EncryptPassword hashes the input password using bcrypt.
|
||||
// An error is returned if hashing fails.
|
||||
func EncryptPassword(password string) (string, error) {
|
||||
bs, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
// VerifyPassword checks the input password against the stored hash.
|
||||
// When a legacy MD5 hash is provided, the password is rehashed with bcrypt
|
||||
// and the new hash is returned. Any internal bcrypt error is returned.
|
||||
func VerifyPassword(hash, input string) (bool, string, error) {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(input))
|
||||
if err == nil {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
var invalidPrefixErr bcrypt.InvalidHashPrefixError
|
||||
if errors.As(err, &invalidPrefixErr) || errors.Is(err, bcrypt.ErrHashTooShort) {
|
||||
// Try fallback to legacy MD5 hash verification
|
||||
if hash == Md5(input+"rustdesk-api") {
|
||||
newHash, err2 := bcrypt.GenerateFromPassword([]byte(input), bcrypt.DefaultCost)
|
||||
if err2 != nil {
|
||||
return true, "", err2
|
||||
}
|
||||
return true, string(newHash), nil
|
||||
}
|
||||
}
|
||||
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||
return false, "", nil
|
||||
}
|
||||
return false, "", err
|
||||
}
|
||||
40
utils/password_test.go
Normal file
40
utils/password_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestVerifyPasswordMD5(t *testing.T) {
|
||||
hash := Md5("secret" + "rustdesk-api")
|
||||
ok, newHash, err := VerifyPassword(hash, "secret")
|
||||
if err != nil {
|
||||
t.Fatalf("md5 verify failed: %v", err)
|
||||
}
|
||||
if !ok || newHash == "" {
|
||||
t.Fatalf("md5 migration failed")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(newHash), []byte("secret")) != nil {
|
||||
t.Fatalf("invalid rehash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPasswordBcrypt(t *testing.T) {
|
||||
b, _ := bcrypt.GenerateFromPassword([]byte("pass"), bcrypt.DefaultCost)
|
||||
ok, newHash, err := VerifyPassword(string(b), "pass")
|
||||
if err != nil || !ok || newHash != "" {
|
||||
t.Fatalf("bcrypt verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPasswordMigrate(t *testing.T) {
|
||||
md5hash := Md5("mypass" + "rustdesk-api")
|
||||
ok, newHash, err := VerifyPassword(md5hash, "mypass")
|
||||
if err != nil || !ok || newHash == "" {
|
||||
t.Fatalf("expected bcrypt rehash")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(newHash), []byte("mypass")) != nil {
|
||||
t.Fatalf("rehash not valid bcrypt")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user