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 }