You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hash.go 6.3KB

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