-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhasher.go
105 lines (89 loc) · 3.23 KB
/
hasher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package crypt
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"github.com/pkg/errors"
"golang.org/x/crypto/pbkdf2"
)
const (
// hashLength should be 32 bytes for sha256
hashLength = 32
// saltLength should be at least 8 bytes per RFC-2898 (https://www.ietf.org/rfc/rfc2898.txt)
saltLength = 8
// 2 ^ 16, gives ~64millis for pbkdf2Encode on local machine.
iterations = 65536
)
// Hasher provides API for hash data and than check if hashed data equal to new passed one.
// Commonly used for passwords storing.
type Hasher interface {
// Hash hashes plain data with salt
Hash(plain string, globalSalt []byte) (string, error)
// Verify checks if auditee is the same data as was previously hashed with salt
Verify(hashed, auditee string, globalSalt []byte) (bool, error)
}
type PBKDF2Hasher struct {
}
var DefaultHasher Hasher = &PBKDF2Hasher{}
// Hash hashes data with slow hash function pbkdf2 via base64.Encode(hashFn(globalSalt + randomSalt + plain))
// Random salt with 8 bytes length appended to resulted encoded hash.
func (PBKDF2Hasher) Hash(plain string, globalSalt []byte) (string, error) {
randomSalt, err := salt()
if err != nil {
return "", errors.Wrap(err, "failed to hash password")
}
hash := pbkdf2Hash(plain, globalSalt, randomSalt)
hashBase64 := base64Encode(hash)
return string(append(randomSalt, hashBase64...)), nil
}
// VerifyHash verifies that passed auditee is the same as was hashed.
func (PBKDF2Hasher) Verify(hashed, auditee string, globalSalt []byte) (bool, error) {
hashedBytes := []byte(hashed)
decodedHash, err := base64Decode(hashedBytes[saltLength:])
if err != nil {
return false, errors.Wrapf(err, "failed to base64 decode original salt from %s", hashed[:saltLength])
}
randomSalt := hashedBytes[:saltLength]
hashedAuditee := pbkdf2Hash(auditee, globalSalt, randomSalt)
return bytes.Equal(decodedHash, hashedAuditee), nil
}
// pbkdf2Hash encodes plain string
func pbkdf2Hash(plain string, globalSalt []byte, randomSalt []byte) []byte {
return pbkdf2.Key([]byte(plain), append(globalSalt, randomSalt...), iterations, hashLength, sha256.New)
}
// salt generates a cryptographically secure nonce salt in ASCII.
func salt() ([]byte, error) {
b, err := generateRandomBytes(saltLength)
if err != nil {
return nil, errors.Wrap(err, "failed to generate salt")
}
saltBase64 := base64Encode(b)
return saltBase64[:saltLength], nil
}
// generateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}
func base64Encode(src []byte) []byte {
buf := make([]byte, base64.URLEncoding.EncodedLen(len(src)))
base64.URLEncoding.Encode(buf, src)
return buf
}
func base64Decode(src []byte) ([]byte, error) {
buf := make([]byte, base64.URLEncoding.DecodedLen(len(src)))
n, err := base64.URLEncoding.Decode(buf, src)
if err != nil {
return nil, errors.Wrapf(err, "succeed only %d bytes, failed to decode base64 string %s", n, string(src))
}
return buf[:n], nil
}