github.com/argoproj/argo-cd/v3@v3.2.1/util/password/password.go (about)

     1  package password
     2  
     3  import (
     4  	"crypto/subtle"
     5  	"errors"
     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 (
    25  	_ PasswordHasher = DummyPasswordHasher{}
    26  	_ PasswordHasher = BcryptPasswordHasher{0}
    27  )
    28  
    29  // 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.
    30  var preferredHashers = []PasswordHasher{
    31  	BcryptPasswordHasher{},
    32  }
    33  
    34  // HashPasswordWithHashers hashes an entered password using the first hasher in the provided list of hashers.
    35  func hashPasswordWithHashers(password string, hashers []PasswordHasher) (string, error) {
    36  	// Even though good hashers will disallow blank passwords, let's be explicit that ALL BLANK PASSWORDS ARE INVALID.  Full stop.
    37  	if password == "" {
    38  		return "", errors.New("blank passwords are not allowed")
    39  	}
    40  	return hashers[0].HashPassword(password)
    41  }
    42  
    43  // 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).
    44  func verifyPasswordWithHashers(password, hashedPassword string, hashers []PasswordHasher) (bool, bool) {
    45  	// Even though good hashers will disallow blank passwords, let's be explicit that ALL BLANK PASSWORDS ARE INVALID.  Full stop.
    46  	if password == "" {
    47  		return false, false
    48  	}
    49  
    50  	valid, stale := false, false
    51  
    52  	for idx, hasher := range hashers {
    53  		if hasher.VerifyPassword(password, hashedPassword) {
    54  			valid = true
    55  			if idx > 0 {
    56  				stale = true
    57  			}
    58  			break
    59  		}
    60  	}
    61  
    62  	return valid, stale
    63  }
    64  
    65  // HashPassword hashes against the current preferred hasher.
    66  func HashPassword(password string) (string, error) {
    67  	return hashPasswordWithHashers(password, preferredHashers)
    68  }
    69  
    70  // 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).
    71  func VerifyPassword(password, hashedPassword string) (valid, stale bool) {
    72  	valid, stale = verifyPasswordWithHashers(password, hashedPassword, preferredHashers)
    73  	return
    74  }
    75  
    76  // 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.
    77  func (h DummyPasswordHasher) HashPassword(password string) (string, error) {
    78  	return password, nil
    79  }
    80  
    81  // VerifyPassword validates whether a one-way digest ("hash") of a password was created from a given plaintext password.
    82  func (h DummyPasswordHasher) VerifyPassword(password, hashedPassword string) bool {
    83  	return subtle.ConstantTimeCompare([]byte(password), []byte(hashedPassword)) == 1
    84  }
    85  
    86  // 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.
    87  func (h BcryptPasswordHasher) HashPassword(password string) (string, error) {
    88  	cost := h.Cost
    89  	if cost < bcrypt.DefaultCost {
    90  		cost = bcrypt.DefaultCost
    91  	}
    92  	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), cost)
    93  	if err != nil {
    94  		hashedPassword = []byte("")
    95  	}
    96  	return string(hashedPassword), err
    97  }
    98  
    99  // VerifyPassword validates whether a one-way digest ("hash") of a password was created from a given plaintext password.
   100  func (h BcryptPasswordHasher) VerifyPassword(password, hashedPassword string) bool {
   101  	err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
   102  	return err == nil
   103  }