github.com/argoproj/argo-cd@v1.8.7/util/password/password.go (about) 1 package password 2 3 import ( 4 "crypto/subtle" 5 "fmt" 6 7 "golang.org/x/crypto/bcrypt" 8 ) 9 10 // PasswordHasher is an interface type to declare a general-purpose password management tool. 11 type PasswordHasher interface { 12 HashPassword(string) (string, error) 13 VerifyPassword(string, string) bool 14 } 15 16 // DummyPasswordHasher is for testing ONLY. DO NOT USE in a production context. 17 type DummyPasswordHasher struct{} 18 19 // BcryptPasswordHasher handles password hashing with Bcrypt. Create with `0` as the work factor to default to bcrypt.DefaultCost at hashing time. The Cost field represents work factor. 20 type BcryptPasswordHasher struct { 21 Cost int 22 } 23 24 var _ PasswordHasher = DummyPasswordHasher{} 25 var _ PasswordHasher = BcryptPasswordHasher{0} 26 27 // PreferredHashers holds the list of preferred hashing algorithms, in order of most to least preferred. Any password that does not validate with the primary algorithm will be considered "stale." DO NOT ADD THE DUMMY HASHER FOR USE IN PRODUCTION. 28 var preferredHashers = []PasswordHasher{ 29 BcryptPasswordHasher{}, 30 } 31 32 // HashPasswordWithHashers hashes an entered password using the first hasher in the provided list of hashers. 33 func hashPasswordWithHashers(password string, hashers []PasswordHasher) (string, error) { 34 // Even though good hashers will disallow blank passwords, let's be explicit that ALL BLANK PASSWORDS ARE INVALID. Full stop. 35 if password == "" { 36 return "", fmt.Errorf("blank passwords are not allowed") 37 } 38 return hashers[0].HashPassword(password) 39 } 40 41 // VerifyPasswordWithHashers verifies an entered password against a hashed password using one or more algorithms. It returns whether the hash is "stale" (i.e., was verified using something other than the FIRST hasher specified). 42 func verifyPasswordWithHashers(password, hashedPassword string, hashers []PasswordHasher) (bool, bool) { 43 // Even though good hashers will disallow blank passwords, let's be explicit that ALL BLANK PASSWORDS ARE INVALID. Full stop. 44 if password == "" { 45 return false, false 46 } 47 48 valid, stale := false, false 49 50 for idx, hasher := range hashers { 51 if hasher.VerifyPassword(password, hashedPassword) { 52 valid = true 53 if idx > 0 { 54 stale = true 55 } 56 break 57 } 58 } 59 60 return valid, stale 61 } 62 63 // HashPassword hashes against the current preferred hasher. 64 func HashPassword(password string) (string, error) { 65 return hashPasswordWithHashers(password, preferredHashers) 66 } 67 68 // VerifyPassword verifies an entered password against a hashed password and returns whether the hash is "stale" (i.e., was verified using the FIRST preferred hasher above). 69 func VerifyPassword(password, hashedPassword string) (valid, stale bool) { 70 valid, stale = verifyPasswordWithHashers(password, hashedPassword, preferredHashers) 71 return 72 } 73 74 // HashPassword creates a one-way digest ("hash") of a password. In the case of Bcrypt, a pseudorandom salt is included automatically by the underlying library. 75 func (h DummyPasswordHasher) HashPassword(password string) (string, error) { 76 return password, nil 77 } 78 79 // VerifyPassword validates whether a one-way digest ("hash") of a password was created from a given plaintext password. 80 func (h DummyPasswordHasher) VerifyPassword(password, hashedPassword string) bool { 81 return 1 == subtle.ConstantTimeCompare([]byte(password), []byte(hashedPassword)) 82 } 83 84 // HashPassword creates a one-way digest ("hash") of a password. In the case of Bcrypt, a pseudorandom salt is included automatically by the underlying library. For security reasons, the work factor is always at _least_ bcrypt.DefaultCost. 85 func (h BcryptPasswordHasher) HashPassword(password string) (string, error) { 86 cost := h.Cost 87 if cost < bcrypt.DefaultCost { 88 cost = bcrypt.DefaultCost 89 } 90 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), cost) 91 if err != nil { 92 hashedPassword = []byte("") 93 } 94 return string(hashedPassword), err 95 } 96 97 // VerifyPassword validates whether a one-way digest ("hash") of a password was created from a given plaintext password. 98 func (h BcryptPasswordHasher) VerifyPassword(password, hashedPassword string) bool { 99 err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) 100 return err == nil 101 }