-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathcryptkeeper.go
225 lines (191 loc) · 5.51 KB
/
cryptkeeper.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package cryptkeeper
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"database/sql"
"database/sql/driver"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"reflect"
)
/*CryptString is a wrapper for encrypting and decrypting a string for database operations. */
type CryptString struct {
json.Marshaler
json.Unmarshaler
sql.Scanner
driver.Valuer
String string
}
/*CryptBytes is a wrapper for encrypting and decrypting a byte slice for database operations. */
type CryptBytes struct {
json.Marshaler
json.Unmarshaler
sql.Scanner
driver.Valuer
Bytes []byte
}
var cryptKeeperKey []byte
func init() {
SetCryptKey([]byte(os.Getenv("CRYPT_KEEPER_KEY")))
}
// MarshalJSON encrypts and marshals the underlying string
func (cs *CryptString) MarshalJSON() ([]byte, error) {
encrypted, err := Encrypt(cs.String)
if err != nil {
return nil, err
}
return json.Marshal(encrypted)
}
// MarshalJSON encrypts and marshals the underlying byte slice
func (cb *CryptBytes) MarshalJSON() ([]byte, error) {
encrypted, err := EncryptBytes(cb.Bytes)
if err != nil {
return nil, err
}
return json.Marshal(encrypted) // encoded as base64 per json.Marshal docs
}
// UnmarshalJSON unmarshals and decrypts the underlying string into the CryptString instance
func (cs *CryptString) UnmarshalJSON(b []byte) error {
var target string
if err := json.Unmarshal(b, &target); err != nil {
return err
}
decrypted, err := Decrypt(target)
if err != nil {
return err
}
cs.String = decrypted
return nil
}
// UnmarshalJSON unmarshals and decrypts the underlying byte slice into the CryptBytes instance
func (cb *CryptBytes) UnmarshalJSON(b []byte) error {
var target []byte
if err := json.Unmarshal(b, &target); err != nil {
return err
}
decrypted, err := DecryptBytes(target)
if err != nil {
return err
}
cb.Bytes = decrypted
return nil
}
// Scan implements sql.Scanner and decryptes incoming sql column data into an underlying string
func (cs *CryptString) Scan(value interface{}) error {
switch v := value.(type) {
case string:
rawString, err := Decrypt(v)
if err != nil {
return err
}
cs.String = rawString
case []byte:
rawString, err := Decrypt(string(v))
if err != nil {
return err
}
cs.String = rawString
default:
return fmt.Errorf("failed to scan type %+v for value", reflect.TypeOf(value))
}
return nil
}
// Scan implements sql.Scanner and decryptes incoming sql column data into an underlying byte slice
func (cb *CryptBytes) Scan(value interface{}) error {
switch v := value.(type) {
case string:
rawBytes, err := DecryptBytes([]byte(v))
if err != nil {
return err
}
cb.Bytes = rawBytes
case []byte:
rawBytes, err := DecryptBytes(v)
if err != nil {
return err
}
cb.Bytes = rawBytes
default:
return fmt.Errorf("failed to scan type %+v for value", reflect.TypeOf(value))
}
return nil
}
// Value implements driver.Valuer and encrypts outgoing bind values for sql
func (cs CryptString) Value() (value driver.Value, err error) {
return Encrypt(cs.String)
}
// Value implements driver.Valuer and encrypts outgoing bind values for sql
func (cb CryptBytes) Value() (value driver.Value, err error) {
return EncryptBytes(cb.Bytes)
}
// SetCryptKey will set the key to be used for encryption and decryption
func SetCryptKey(secretKey []byte) error {
keyLen := len(secretKey)
if keyLen != 16 && keyLen != 24 && keyLen != 32 {
return fmt.Errorf("Invalid KEY to set for CRYPT_KEEPER_KEY; must be 16, 24, or 32 bytes (got %d)", keyLen)
}
cryptKeeperKey = secretKey
return nil
}
// CryptKey will return the current key that will be used for encryption and decryption
func CryptKey() []byte {
return cryptKeeperKey
}
// Encrypt will AES-encrypt and base64 url-encode the given string
func Encrypt(text string) (string, error) {
ciphertext, err := EncryptBytes([]byte(text))
if err != nil {
return "", err
}
// convert to base64
return base64.URLEncoding.EncodeToString(ciphertext), nil
}
// EncryptBytes will AES-encrypt the given byte slice
func EncryptBytes(data []byte) ([]byte, error) {
block, err := aes.NewCipher(cryptKeeperKey)
if err != nil {
return nil, err
}
// The IV needs to be unique, but not secure, therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, aes.BlockSize+len(data))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cipher.NewCFBEncrypter(block, iv).XORKeyStream(ciphertext[aes.BlockSize:], data)
return ciphertext, nil
}
// Decrypt will base64 url-decode and then AES-decrypt the given string
func Decrypt(encrypted string) (string, error) {
decodedBytes, err := base64.URLEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
ciphertext, err := DecryptBytes(decodedBytes)
if err != nil {
return "", err
}
return string(ciphertext), nil
}
// DecryptBytes will AES-decrypt the given byte slice
func DecryptBytes(encrypted []byte) ([]byte, error) {
block, err := aes.NewCipher(cryptKeeperKey)
if err != nil {
return nil, err
}
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
if byteLen := len(encrypted); byteLen < aes.BlockSize {
return nil, fmt.Errorf("invalid cipher size %d, expected at least %d", byteLen, aes.BlockSize)
}
iv := encrypted[:aes.BlockSize]
encrypted = encrypted[aes.BlockSize:]
// XORKeyStream can work in-place if the two arguments are the same.
cipher.NewCFBDecrypter(block, iv).XORKeyStream(encrypted, encrypted)
return encrypted, nil
}