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  }