Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package migrations
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/md5"
  6. "crypto/sha1"
  7. "crypto/sha256"
  8. "crypto/sha512"
  9. "crypto/subtle"
  10. "encoding/base64"
  11. "encoding/binary"
  12. "encoding/hex"
  13. "errors"
  14. "hash"
  15. "strconv"
  16. "github.com/GehirnInc/crypt/md5_crypt"
  17. "golang.org/x/crypto/bcrypt"
  18. "golang.org/x/crypto/pbkdf2"
  19. )
  20. var (
  21. ErrHashInvalid = errors.New("password hash invalid for algorithm")
  22. ErrHashCheckFailed = errors.New("passphrase did not match stored hash")
  23. hmacServerKeyText = []byte("Server Key")
  24. athemePBKDF2V2Prefix = []byte("$z")
  25. athemeRawSHA1Prefix = []byte("$rawsha1$")
  26. )
  27. type PassphraseCheck func(hash, passphrase []byte) (err error)
  28. func CheckAthemePassphrase(hash, passphrase []byte) (err error) {
  29. if bytes.HasPrefix(hash, athemeRawSHA1Prefix) {
  30. return checkAthemeRawSha1(hash, passphrase)
  31. } else if bytes.HasPrefix(hash, athemePBKDF2V2Prefix) {
  32. return checkAthemePBKDF2V2(hash, passphrase)
  33. } else if len(hash) < 60 {
  34. return checkAthemePosixCrypt(hash, passphrase)
  35. } else {
  36. return checkAthemePBKDF2(hash, passphrase)
  37. }
  38. }
  39. func checkAthemePosixCrypt(hash, passphrase []byte) (err error) {
  40. // crypto/posix: the platform's crypt(3) function
  41. // MD5 on linux, DES on MacOS: forget MacOS
  42. md5crypt := md5_crypt.New()
  43. return md5crypt.Verify(string(hash), []byte(passphrase))
  44. }
  45. type pbkdf2v2Algo struct {
  46. Hash func() hash.Hash
  47. OutputSize int
  48. SCRAM bool
  49. SaltB64 bool
  50. }
  51. func athemePBKDF2V2ParseAlgo(algo string) (result pbkdf2v2Algo, err error) {
  52. // https://github.com/atheme/atheme/blob/a11e85efc67d86fc4738e3e2a4f220bfa69153f0/include/atheme/pbkdf2.h#L34-L52
  53. algoInt, err := strconv.Atoi(algo)
  54. if err != nil {
  55. return result, ErrHashInvalid
  56. }
  57. hashCode := algoInt % 10
  58. algoCode := algoInt - hashCode
  59. switch algoCode {
  60. case 0:
  61. // e.g., #define PBKDF2_PRF_HMAC_MD5 3U
  62. // no SCRAM, no SHA256
  63. case 20:
  64. // e.g., #define PBKDF2_PRF_HMAC_MD5_S64 23U
  65. // no SCRAM, base64
  66. result.SaltB64 = true
  67. case 40:
  68. // e.g., #define PBKDF2_PRF_SCRAM_MD5 43U
  69. // SCRAM, no base64
  70. result.SCRAM = true
  71. case 60:
  72. // e.g., #define PBKDF2_PRF_SCRAM_MD5_S64 63U
  73. result.SaltB64 = true
  74. result.SCRAM = true
  75. default:
  76. return result, ErrHashInvalid
  77. }
  78. switch hashCode {
  79. case 3:
  80. result.Hash, result.OutputSize = md5.New, (128 / 8)
  81. case 4:
  82. result.Hash, result.OutputSize = sha1.New, (160 / 8)
  83. case 5:
  84. result.Hash, result.OutputSize = sha256.New, (256 / 8)
  85. case 6:
  86. result.Hash, result.OutputSize = sha512.New, (512 / 8)
  87. default:
  88. return result, ErrHashInvalid
  89. }
  90. return result, nil
  91. }
  92. func checkAthemePBKDF2V2(hash, passphrase []byte) (err error) {
  93. // crypto/pbkdf2v2, the default as of september 2020:
  94. // "the format for pbkdf2v2 is $z$alg$iter$salt$digest
  95. // where the z is literal,
  96. // the alg is one from https://github.com/atheme/atheme/blob/master/include/atheme/pbkdf2.h#L34-L52
  97. // iter is the iteration count.
  98. // if the alg ends in _S64 then the salt is base64-encoded, otherwise taken literally
  99. // (an ASCII salt, inherited from the pbkdf2 module).
  100. // if alg is a SCRAM one, then digest is actually serverkey$storedkey (see RFC 5802).
  101. // digest, serverkey and storedkey are base64-encoded."
  102. parts := bytes.Split(hash, []byte{'$'})
  103. if len(parts) < 6 {
  104. return ErrHashInvalid
  105. }
  106. algo, err := athemePBKDF2V2ParseAlgo(string(parts[2]))
  107. if err != nil {
  108. return err
  109. }
  110. iter, err := strconv.Atoi(string(parts[3]))
  111. if err != nil {
  112. return ErrHashInvalid
  113. }
  114. salt := parts[4]
  115. if algo.SaltB64 {
  116. salt, err = base64.StdEncoding.DecodeString(string(salt))
  117. if err != nil {
  118. return err
  119. }
  120. }
  121. // if SCRAM, parts[5] is ServerKey; otherwise it's the actual PBKDF2 output
  122. // either way, it's what we'll test against
  123. expected, err := base64.StdEncoding.DecodeString(string(parts[5]))
  124. if err != nil {
  125. return err
  126. }
  127. var key []byte
  128. if algo.SCRAM {
  129. if len(parts) != 7 {
  130. return ErrHashInvalid
  131. }
  132. stretch := pbkdf2.Key(passphrase, salt, iter, algo.OutputSize, algo.Hash)
  133. mac := hmac.New(algo.Hash, stretch)
  134. mac.Write(hmacServerKeyText)
  135. key = mac.Sum(nil)
  136. } else {
  137. if len(parts) != 6 {
  138. return ErrHashInvalid
  139. }
  140. key = pbkdf2.Key(passphrase, salt, iter, len(expected), algo.Hash)
  141. }
  142. if subtle.ConstantTimeCompare(key, expected) == 1 {
  143. return nil
  144. } else {
  145. return ErrHashCheckFailed
  146. }
  147. }
  148. func checkAthemePBKDF2(hash, passphrase []byte) (err error) {
  149. // crypto/pbkdf2:
  150. // "SHA2-512, 128000 iterations, 16-ASCII-character salt, hexadecimal encoding of digest,
  151. // digest appended directly to salt, for a single string consisting of only 144 characters"
  152. if len(hash) != 144 {
  153. return ErrHashInvalid
  154. }
  155. salt := hash[:16]
  156. digest := make([]byte, 64)
  157. cnt, err := hex.Decode(digest, hash[16:])
  158. if err != nil || cnt != 64 {
  159. return ErrHashCheckFailed
  160. }
  161. key := pbkdf2.Key(passphrase, salt, 128000, 64, sha512.New)
  162. if subtle.ConstantTimeCompare(key, digest) == 1 {
  163. return nil
  164. } else {
  165. return ErrHashCheckFailed
  166. }
  167. }
  168. func checkAthemeRawSha1(hash, passphrase []byte) (err error) {
  169. return checkRawHash(hash[len(athemeRawSHA1Prefix):], passphrase, sha1.New())
  170. }
  171. func checkRawHash(expected, passphrase []byte, h hash.Hash) (err error) {
  172. var rawExpected []byte
  173. size := h.Size()
  174. if len(expected) == 2*size {
  175. rawExpected = make([]byte, h.Size())
  176. _, err = hex.Decode(rawExpected, expected)
  177. if err != nil {
  178. return ErrHashInvalid
  179. }
  180. } else if len(expected) == size {
  181. rawExpected = expected
  182. } else {
  183. return ErrHashInvalid
  184. }
  185. h.Write(passphrase)
  186. hashedPassphrase := h.Sum(nil)
  187. if subtle.ConstantTimeCompare(rawExpected, hashedPassphrase) == 1 {
  188. return nil
  189. } else {
  190. return ErrHashCheckFailed
  191. }
  192. }
  193. func checkAnopeEncSha256(hashBytes, ivBytes, passphrase []byte) (err error) {
  194. if len(ivBytes) != 32 {
  195. return ErrHashInvalid
  196. }
  197. // https://github.com/anope/anope/blob/2cf507ed662620d0b97c8484fbfbfa09265e86e1/modules/encryption/enc_sha256.cpp#L67
  198. var iv [8]uint32
  199. for i := 0; i < 8; i++ {
  200. iv[i] = binary.BigEndian.Uint32(ivBytes[i*4 : (i+1)*4])
  201. }
  202. result := anopeSum256(passphrase, iv)
  203. if subtle.ConstantTimeCompare(result[:], hashBytes) == 1 {
  204. return nil
  205. } else {
  206. return ErrHashCheckFailed
  207. }
  208. }
  209. func CheckAnopePassphrase(hash, passphrase []byte) (err error) {
  210. pieces := bytes.Split(hash, []byte{':'})
  211. if len(pieces) < 2 {
  212. return ErrHashInvalid
  213. }
  214. switch string(pieces[0]) {
  215. case "plain":
  216. // base64, standard encoding
  217. expectedPassphrase, err := base64.StdEncoding.DecodeString(string(pieces[1]))
  218. if err != nil {
  219. return ErrHashInvalid
  220. }
  221. if subtle.ConstantTimeCompare(passphrase, expectedPassphrase) == 1 {
  222. return nil
  223. } else {
  224. return ErrHashCheckFailed
  225. }
  226. case "md5":
  227. // raw MD5
  228. return checkRawHash(pieces[1], passphrase, md5.New())
  229. case "sha1":
  230. // raw SHA-1
  231. return checkRawHash(pieces[1], passphrase, sha1.New())
  232. case "bcrypt":
  233. if bcrypt.CompareHashAndPassword(pieces[1], passphrase) == nil {
  234. return nil
  235. } else {
  236. return ErrHashCheckFailed
  237. }
  238. case "sha256":
  239. // SHA-256 with an overridden IV
  240. if len(pieces) != 3 {
  241. return ErrHashInvalid
  242. }
  243. hashBytes, err := hex.DecodeString(string(pieces[1]))
  244. if err != nil {
  245. return ErrHashInvalid
  246. }
  247. ivBytes, err := hex.DecodeString(string(pieces[2]))
  248. if err != nil {
  249. return ErrHashInvalid
  250. }
  251. return checkAnopeEncSha256(hashBytes, ivBytes, passphrase)
  252. default:
  253. return ErrHashInvalid
  254. }
  255. }