argon.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // MIT (c) alexedwards [https://gist.github.com/alexedwards/34277fae0f48abe36822b375f0f6a621]
  2. // move to https://github.com/alexedwards/argon2id instead of vendoring
  3. package main
  4. import (
  5. "crypto/rand"
  6. "crypto/subtle"
  7. "encoding/base64"
  8. "errors"
  9. "fmt"
  10. "strings"
  11. "golang.org/x/crypto/argon2"
  12. )
  13. var (
  14. ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
  15. ErrIncompatibleVersion = errors.New("incompatible version of argon2")
  16. )
  17. type params struct {
  18. memory uint32
  19. iterations uint32
  20. parallelism uint8
  21. saltLength uint32
  22. keyLength uint32
  23. }
  24. func GenerateFromPassword(password string) (encodedHash string, err error) {
  25. p := &params{
  26. memory: 64 * 1024,
  27. iterations: 3,
  28. parallelism: 2,
  29. saltLength: 16,
  30. keyLength: 32,
  31. }
  32. salt, err := generateRandomBytes(p.saltLength)
  33. if err != nil {
  34. return "", err
  35. }
  36. hash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
  37. b64Salt := base64.RawStdEncoding.EncodeToString(salt)
  38. b64Hash := base64.RawStdEncoding.EncodeToString(hash)
  39. encodedHash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, b64Salt, b64Hash)
  40. return encodedHash, nil
  41. }
  42. func generateRandomBytes(n uint32) ([]byte, error) {
  43. b := make([]byte, n)
  44. _, err := rand.Read(b)
  45. if err != nil {
  46. return nil, err
  47. }
  48. return b, nil
  49. }
  50. func ComparePasswordAndHash(password, encodedHash string) (match bool, err error) {
  51. p, salt, hash, err := decodeHash(encodedHash)
  52. if err != nil {
  53. return false, err
  54. }
  55. otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
  56. if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
  57. return true, nil
  58. }
  59. return false, nil
  60. }
  61. func decodeHash(encodedHash string) (p *params, salt, hash []byte, err error) {
  62. vals := strings.Split(encodedHash, "$")
  63. if len(vals) != 6 {
  64. return nil, nil, nil, ErrInvalidHash
  65. }
  66. var version int
  67. _, err = fmt.Sscanf(vals[2], "v=%d", &version)
  68. if err != nil {
  69. return nil, nil, nil, err
  70. }
  71. if version != argon2.Version {
  72. return nil, nil, nil, ErrIncompatibleVersion
  73. }
  74. p = &params{}
  75. _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism)
  76. if err != nil {
  77. return nil, nil, nil, err
  78. }
  79. salt, err = base64.RawStdEncoding.DecodeString(vals[4])
  80. if err != nil {
  81. return nil, nil, nil, err
  82. }
  83. p.saltLength = uint32(len(salt))
  84. hash, err = base64.RawStdEncoding.DecodeString(vals[5])
  85. if err != nil {
  86. return nil, nil, nil, err
  87. }
  88. p.keyLength = uint32(len(hash))
  89. return p, salt, hash, nil
  90. }