code.gitea.io/gitea@v1.19.3/modules/auth/password/hash/hash.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package hash
     5  
     6  import (
     7  	"crypto/subtle"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"strings"
    11  	"sync/atomic"
    12  
    13  	"code.gitea.io/gitea/modules/log"
    14  )
    15  
    16  // This package takes care of hashing passwords, verifying passwords, defining
    17  // available password algorithms, defining recommended password algorithms and
    18  // choosing the default password algorithm.
    19  
    20  // PasswordSaltHasher will hash a provided password with the provided saltBytes
    21  type PasswordSaltHasher interface {
    22  	HashWithSaltBytes(password string, saltBytes []byte) string
    23  }
    24  
    25  // PasswordHasher will hash a provided password with the salt
    26  type PasswordHasher interface {
    27  	Hash(password, salt string) (string, error)
    28  }
    29  
    30  // PasswordVerifier will ensure that a providedPassword matches the hashPassword when hashed with the salt
    31  type PasswordVerifier interface {
    32  	VerifyPassword(providedPassword, hashedPassword, salt string) bool
    33  }
    34  
    35  // PasswordHashAlgorithms are named PasswordSaltHashers with a default verifier and hash function
    36  type PasswordHashAlgorithm struct {
    37  	PasswordSaltHasher
    38  	Specification string // The specification that is used to create the internal PasswordSaltHasher
    39  }
    40  
    41  // Hash the provided password with the salt and return the hash
    42  func (algorithm *PasswordHashAlgorithm) Hash(password, salt string) (string, error) {
    43  	var saltBytes []byte
    44  
    45  	// There are two formats for the salt value:
    46  	// * The new format is a (32+)-byte hex-encoded string
    47  	// * The old format was a 10-byte binary format
    48  	// We have to tolerate both here.
    49  	if len(salt) == 10 {
    50  		saltBytes = []byte(salt)
    51  	} else {
    52  		var err error
    53  		saltBytes, err = hex.DecodeString(salt)
    54  		if err != nil {
    55  			return "", err
    56  		}
    57  	}
    58  
    59  	return algorithm.HashWithSaltBytes(password, saltBytes), nil
    60  }
    61  
    62  // Verify the provided password matches the hashPassword when hashed with the salt
    63  func (algorithm *PasswordHashAlgorithm) VerifyPassword(providedPassword, hashedPassword, salt string) bool {
    64  	// Some PasswordSaltHashers have their own specialised compare function that takes into
    65  	// account the stored parameters within the hash. e.g. bcrypt
    66  	if verifier, ok := algorithm.PasswordSaltHasher.(PasswordVerifier); ok {
    67  		return verifier.VerifyPassword(providedPassword, hashedPassword, salt)
    68  	}
    69  
    70  	// Compute the hash of the password.
    71  	providedPasswordHash, err := algorithm.Hash(providedPassword, salt)
    72  	if err != nil {
    73  		log.Error("passwordhash: %v.Hash(): %v", algorithm.Specification, err)
    74  		return false
    75  	}
    76  
    77  	// Compare it against the hashed password in constant-time.
    78  	return subtle.ConstantTimeCompare([]byte(hashedPassword), []byte(providedPasswordHash)) == 1
    79  }
    80  
    81  var (
    82  	lastNonDefaultAlgorithm  atomic.Value
    83  	availableHasherFactories = map[string]func(string) PasswordSaltHasher{}
    84  )
    85  
    86  // MustRegister registers a PasswordSaltHasher with the availableHasherFactories
    87  // Caution: This is not thread safe.
    88  func MustRegister[T PasswordSaltHasher](name string, newFn func(config string) T) {
    89  	if err := Register(name, newFn); err != nil {
    90  		panic(err)
    91  	}
    92  }
    93  
    94  // Register registers a PasswordSaltHasher with the availableHasherFactories
    95  // Caution: This is not thread safe.
    96  func Register[T PasswordSaltHasher](name string, newFn func(config string) T) error {
    97  	if _, has := availableHasherFactories[name]; has {
    98  		return fmt.Errorf("duplicate registration of password salt hasher: %s", name)
    99  	}
   100  
   101  	availableHasherFactories[name] = func(config string) PasswordSaltHasher {
   102  		n := newFn(config)
   103  		return n
   104  	}
   105  	return nil
   106  }
   107  
   108  // In early versions of gitea the password hash algorithm field of a user could be
   109  // empty. At that point the default was `pbkdf2` without configuration values
   110  //
   111  // Please note this is not the same as the DefaultAlgorithm which is used
   112  // to determine what an empty PASSWORD_HASH_ALGO setting in the app.ini means.
   113  // These are not the same even if they have the same apparent value and they mean different things.
   114  //
   115  // DO NOT COALESCE THESE VALUES
   116  const defaultEmptyHashAlgorithmSpecification = "pbkdf2"
   117  
   118  // Parse will convert the provided algorithm specification in to a PasswordHashAlgorithm
   119  // If the provided specification matches the DefaultHashAlgorithm Specification it will be
   120  // used.
   121  // In addition the last non-default hasher will be cached to help reduce the load from
   122  // parsing specifications.
   123  //
   124  // NOTE: No de-aliasing is done in this function, thus any specification which does not
   125  // contain a configuration will use the default values for that hasher. These are not
   126  // necessarily the same values as those obtained by dealiasing. This allows for
   127  // seamless backwards compatibility with the original configuration.
   128  //
   129  // To further labour this point, running `Parse("pbkdf2")` does not obtain the
   130  // same algorithm as setting `PASSWORD_HASH_ALGO=pbkdf2` in app.ini, nor is it intended to.
   131  // A user that has `password_hash_algo='pbkdf2'` in the db means get the original, unconfigured algorithm
   132  // Users will be migrated automatically as they log-in to have the complete specification stored
   133  // in their `password_hash_algo` fields by other code.
   134  func Parse(algorithmSpec string) *PasswordHashAlgorithm {
   135  	if algorithmSpec == "" {
   136  		algorithmSpec = defaultEmptyHashAlgorithmSpecification
   137  	}
   138  
   139  	if DefaultHashAlgorithm != nil && algorithmSpec == DefaultHashAlgorithm.Specification {
   140  		return DefaultHashAlgorithm
   141  	}
   142  
   143  	ptr := lastNonDefaultAlgorithm.Load()
   144  	if ptr != nil {
   145  		hashAlgorithm, ok := ptr.(*PasswordHashAlgorithm)
   146  		if ok && hashAlgorithm.Specification == algorithmSpec {
   147  			return hashAlgorithm
   148  		}
   149  	}
   150  
   151  	// Now convert the provided specification in to a hasherType +/- some configuration parameters
   152  	vals := strings.SplitN(algorithmSpec, "$", 2)
   153  	var hasherType string
   154  	var config string
   155  
   156  	if len(vals) == 0 {
   157  		// This should not happen as algorithmSpec should not be empty
   158  		// due to it being assigned to defaultEmptyHashAlgorithmSpecification above
   159  		// but we should be absolutely cautious here
   160  		return nil
   161  	}
   162  
   163  	hasherType = vals[0]
   164  	if len(vals) > 1 {
   165  		config = vals[1]
   166  	}
   167  
   168  	newFn, has := availableHasherFactories[hasherType]
   169  	if !has {
   170  		// unknown hasher type
   171  		return nil
   172  	}
   173  
   174  	ph := newFn(config)
   175  	if ph == nil {
   176  		// The provided configuration is likely invalid - it will have been logged already
   177  		// but we cannot hash safely
   178  		return nil
   179  	}
   180  
   181  	hashAlgorithm := &PasswordHashAlgorithm{
   182  		PasswordSaltHasher: ph,
   183  		Specification:      algorithmSpec,
   184  	}
   185  
   186  	lastNonDefaultAlgorithm.Store(hashAlgorithm)
   187  
   188  	return hashAlgorithm
   189  }