-
Notifications
You must be signed in to change notification settings - Fork 0
/
passlock.go
139 lines (117 loc) · 3.67 KB
/
passlock.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Package passlock stores your passwords a tiny bit more safely than bcrypt alone.
package passlock
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"io"
"golang.org/x/crypto/bcrypt"
)
// DefaultCost is the minimum work factor for bcrypt.
const DefaultCost = 14
// GenerateFromPassword hashes and salts a password from the given plaintext
// password and HMAC key.
func GenerateFromPassword(password []byte, cost int, key *[32]byte) ([]byte, error) {
if cost < DefaultCost {
cost = DefaultCost
}
encodedPassword, err := hashAndEncodePassword(password)
if err != nil {
return nil, err
}
// Now bcrypt it
hashedPassword, err := bcrypt.GenerateFromPassword(encodedPassword, cost)
if err != nil {
return nil, err
}
// Now encrypt it
return encrypt(hashedPassword, key)
}
// CompareHashAndPassword compares a hashed password to a plaintext password. It
// will return nil if the passwords match, and an error otherwise.
//
// This package wraps all the errors exported by the bcrypt package, so you
// won't need to import that package to compare errors.
func CompareHashAndPassword(encryptedPassword, password []byte, key *[32]byte) error {
// decrypt hashedpassword TODO rename it encryptedPassword
hashedPassword, err := decrypt(encryptedPassword, key)
if err != nil {
return err
}
encodedPassword, err := hashAndEncodePassword(password)
if err != nil {
return err
}
return bcrypt.CompareHashAndPassword(hashedPassword, encodedPassword)
}
// NewEncryptionKey generates a random 256-bit key for Encrypt() and
// Decrypt(). It panics if the source of randomness fails.
func NewEncryptionKey() *[32]byte {
key := [32]byte{}
_, err := io.ReadFull(rand.Reader, key[:])
if err != nil {
panic(err)
}
return &key
}
// RotateKey decrypts the given hash using the old key, and encrypts it with the
// new one.
func RotateKey(oldKey, newKey *[32]byte, encryptedPassword []byte) ([]byte, error) {
decryptedPassword, err := decrypt(encryptedPassword, oldKey)
if err != nil {
return nil, err
}
return encrypt(decryptedPassword, newKey)
}
// hashAndEncodePassword hashes a plaintext password using SHA384, and base64 encodes it.
func hashAndEncodePassword(password []byte) ([]byte, error) {
hash := sha512.New512_256()
_, err := hash.Write(password)
if err != nil {
return nil, err
}
hashedPassword := hash.Sum(nil)
// Now base64 encode it
encodedPassword := make([]byte, len(hashedPassword)*2)
base64.StdEncoding.Encode(encodedPassword, hashedPassword)
return encodedPassword, nil
}
// encrypt encrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Output takes the
// form nonce|ciphertext|tag where '|' indicates concatenation.
func encrypt(plaintext []byte, key *[32]byte) (ciphertext []byte, err error) {
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
// decrypt decrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Expects input
// form nonce|ciphertext|tag where '|' indicates concatenation.
func decrypt(ciphertext []byte, key *[32]byte) (plaintext []byte, err error) {
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return gcm.Open(nil,
ciphertext[:gcm.NonceSize()],
ciphertext[gcm.NonceSize():],
nil,
)
}