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_test.go 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package hash
  4. import (
  5. "encoding/hex"
  6. "strconv"
  7. "strings"
  8. "testing"
  9. "github.com/stretchr/testify/assert"
  10. )
  11. type testSaltHasher string
  12. func (t testSaltHasher) HashWithSaltBytes(password string, salt []byte) string {
  13. return password + "$" + string(salt) + "$" + string(t)
  14. }
  15. func Test_registerHasher(t *testing.T) {
  16. MustRegister("Test_registerHasher", func(config string) testSaltHasher {
  17. return testSaltHasher(config)
  18. })
  19. assert.Panics(t, func() {
  20. MustRegister("Test_registerHasher", func(config string) testSaltHasher {
  21. return testSaltHasher(config)
  22. })
  23. })
  24. assert.Error(t, Register("Test_registerHasher", func(config string) testSaltHasher {
  25. return testSaltHasher(config)
  26. }))
  27. assert.Equal(t, "password$salt$",
  28. Parse("Test_registerHasher").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
  29. assert.Equal(t, "password$salt$config",
  30. Parse("Test_registerHasher$config").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
  31. delete(availableHasherFactories, "Test_registerHasher")
  32. }
  33. func TestParse(t *testing.T) {
  34. hashAlgorithmsToTest := []string{}
  35. for plainHashAlgorithmNames := range availableHasherFactories {
  36. hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
  37. }
  38. for _, aliased := range aliasAlgorithmNames {
  39. if strings.Contains(aliased, "$") {
  40. hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
  41. }
  42. }
  43. for _, algorithmName := range hashAlgorithmsToTest {
  44. t.Run(algorithmName, func(t *testing.T) {
  45. algo := Parse(algorithmName)
  46. assert.NotNil(t, algo, "Algorithm %s resulted in an empty algorithm", algorithmName)
  47. })
  48. }
  49. }
  50. func TestHashing(t *testing.T) {
  51. hashAlgorithmsToTest := []string{}
  52. for plainHashAlgorithmNames := range availableHasherFactories {
  53. hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
  54. }
  55. for _, aliased := range aliasAlgorithmNames {
  56. if strings.Contains(aliased, "$") {
  57. hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
  58. }
  59. }
  60. runTests := func(password, salt string, shouldPass bool) {
  61. for _, algorithmName := range hashAlgorithmsToTest {
  62. t.Run(algorithmName, func(t *testing.T) {
  63. output, err := Parse(algorithmName).Hash(password, salt)
  64. if shouldPass {
  65. assert.NoError(t, err)
  66. assert.NotEmpty(t, output, "output for %s was empty", algorithmName)
  67. } else {
  68. assert.Error(t, err)
  69. }
  70. assert.Equal(t, Parse(algorithmName).VerifyPassword(password, output, salt), shouldPass)
  71. })
  72. }
  73. }
  74. // Test with new salt format.
  75. runTests(strings.Repeat("a", 16), hex.EncodeToString([]byte{0x01, 0x02, 0x03}), true)
  76. // Test with legacy salt format.
  77. runTests(strings.Repeat("a", 16), strings.Repeat("b", 10), true)
  78. // Test with invalid salt.
  79. runTests(strings.Repeat("a", 16), "a", false)
  80. }
  81. // vectors were generated using the current codebase.
  82. var vectors = []struct {
  83. algorithms []string
  84. password string
  85. salt string
  86. output string
  87. shouldfail bool
  88. }{
  89. {
  90. algorithms: []string{"bcrypt", "bcrypt$10"},
  91. password: "abcdef",
  92. salt: strings.Repeat("a", 10),
  93. output: "$2a$10$fjtm8BsQ2crym01/piJroenO3oSVUBhSLKaGdTYJ4tG0ePVCrU0G2",
  94. shouldfail: false,
  95. },
  96. {
  97. algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
  98. password: "abcdef",
  99. salt: strings.Repeat("a", 10),
  100. output: "3b571d0c07c62d42b7bad3dbf18fb0cd67d4d8cd4ad4c6928e1090e5b2a4a84437c6fd2627d897c0e7e65025ca62b67a0002",
  101. shouldfail: false,
  102. },
  103. {
  104. algorithms: []string{"argon2", "argon2$2$65536$8$50"},
  105. password: "abcdef",
  106. salt: strings.Repeat("a", 10),
  107. output: "551f089f570f989975b6f7c6a8ff3cf89bc486dd7bbe87ed4d80ad4362f8ee599ec8dda78dac196301b98456402bcda775dc",
  108. shouldfail: false,
  109. },
  110. {
  111. algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
  112. password: "abcdef",
  113. salt: strings.Repeat("a", 10),
  114. output: "ab48d5471b7e6ed42d10001db88c852ff7303c788e49da5c3c7b63d5adf96360303724b74b679223a3dea8a242d10abb1913",
  115. shouldfail: false,
  116. },
  117. {
  118. algorithms: []string{"bcrypt", "bcrypt$10"},
  119. password: "abcdef",
  120. salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
  121. output: "$2a$10$qhgm32w9ZpqLygugWJsLjey8xRGcaq9iXAfmCeNBXxddgyoaOC3Gq",
  122. shouldfail: false,
  123. },
  124. {
  125. algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
  126. password: "abcdef",
  127. salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
  128. output: "25fe5f66b43fa4eb7b6717905317cd2223cf841092dc8e0a1e8c75720ad4846cb5d9387303e14bc3c69faa3b1c51ef4b7de1",
  129. shouldfail: false,
  130. },
  131. {
  132. algorithms: []string{"argon2", "argon2$2$65536$8$50"},
  133. password: "abcdef",
  134. salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
  135. output: "9c287db63a91d18bb1414b703216da4fc431387c1ae7c8acdb280222f11f0929831055dbfd5126a3b48566692e83ec750d2a",
  136. shouldfail: false,
  137. },
  138. {
  139. algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
  140. password: "abcdef",
  141. salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
  142. output: "45d6cdc843d65cf0eda7b90ab41435762a282f7df013477a1c5b212ba81dbdca2edf1ecc4b5cb05956bb9e0c37ab29315d78",
  143. shouldfail: false,
  144. },
  145. {
  146. algorithms: []string{"pbkdf2$320000$50"},
  147. password: "abcdef",
  148. salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
  149. output: "84e233114499e8721da80e85568e5b7b5900b3e49a30845fcda9d1e1756da4547d70f8740ac2b4a5d82f88cebcd27f21bfe2",
  150. shouldfail: false,
  151. },
  152. {
  153. algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
  154. password: "abcdef",
  155. salt: "",
  156. output: "",
  157. shouldfail: true,
  158. },
  159. }
  160. // Ensure that the current code will correctly verify against the test vectors.
  161. func TestVectors(t *testing.T) {
  162. for i, vector := range vectors {
  163. for _, algorithm := range vector.algorithms {
  164. t.Run(strconv.Itoa(i)+": "+algorithm, func(t *testing.T) {
  165. pa := Parse(algorithm)
  166. assert.Equal(t, !vector.shouldfail, pa.VerifyPassword(vector.password, vector.output, vector.salt))
  167. })
  168. }
  169. }
  170. }