1
2
3
4
5
6
7
8
9
10 package encrypted
11
12 import (
13 "crypto/rand"
14 "encoding/json"
15 "errors"
16 "fmt"
17 "io"
18
19 "golang.org/x/crypto/nacl/secretbox"
20 "golang.org/x/crypto/scrypt"
21 )
22
23 const saltSize = 32
24
25 const (
26 boxKeySize = 32
27 boxNonceSize = 24
28 )
29
30
31
32 type KDFParameterStrength uint8
33
34 const (
35
36 Legacy KDFParameterStrength = iota + 1
37
38 Standard
39
40 OWASP
41 )
42
43 var (
44
45
46 legacyParams = scryptParams{
47 N: 32768,
48 R: 8,
49 P: 1,
50 }
51
52
53
54 standardParams = scryptParams{
55 N: 65536,
56 R: 8,
57 P: 1,
58 }
59
60
61 owaspParams = scryptParams{
62 N: 131072,
63 R: 8,
64 P: 1,
65 }
66
67
68
69 defaultParams = standardParams
70 )
71
72 const (
73 nameScrypt = "scrypt"
74 nameSecretBox = "nacl/secretbox"
75 )
76
77 type data struct {
78 KDF scryptKDF `json:"kdf"`
79 Cipher secretBoxCipher `json:"cipher"`
80 Ciphertext []byte `json:"ciphertext"`
81 }
82
83 type scryptParams struct {
84 N int `json:"N"`
85 R int `json:"r"`
86 P int `json:"p"`
87 }
88
89 func (sp *scryptParams) Equal(in *scryptParams) bool {
90 return in != nil && sp.N == in.N && sp.P == in.P && sp.R == in.R
91 }
92
93 func newScryptKDF(level KDFParameterStrength) (scryptKDF, error) {
94 salt := make([]byte, saltSize)
95 if err := fillRandom(salt); err != nil {
96 return scryptKDF{}, fmt.Errorf("unable to generate a random salt: %w", err)
97 }
98
99 var params scryptParams
100 switch level {
101 case Legacy:
102 params = legacyParams
103 case Standard:
104 params = standardParams
105 case OWASP:
106 params = owaspParams
107 default:
108
109 params = defaultParams
110 }
111
112 return scryptKDF{
113 Name: nameScrypt,
114 Params: params,
115 Salt: salt,
116 }, nil
117 }
118
119 type scryptKDF struct {
120 Name string `json:"name"`
121 Params scryptParams `json:"params"`
122 Salt []byte `json:"salt"`
123 }
124
125 func (s *scryptKDF) Key(passphrase []byte) ([]byte, error) {
126 return scrypt.Key(passphrase, s.Salt, s.Params.N, s.Params.R, s.Params.P, boxKeySize)
127 }
128
129
130
131
132 func (s *scryptKDF) CheckParams() error {
133 switch {
134 case legacyParams.Equal(&s.Params):
135 case standardParams.Equal(&s.Params):
136 case owaspParams.Equal(&s.Params):
137 default:
138 return errors.New("unsupported scrypt parameters")
139 }
140
141 return nil
142 }
143
144 func newSecretBoxCipher() (secretBoxCipher, error) {
145 nonce := make([]byte, boxNonceSize)
146 if err := fillRandom(nonce); err != nil {
147 return secretBoxCipher{}, err
148 }
149 return secretBoxCipher{
150 Name: nameSecretBox,
151 Nonce: nonce,
152 }, nil
153 }
154
155 type secretBoxCipher struct {
156 Name string `json:"name"`
157 Nonce []byte `json:"nonce"`
158
159 encrypted bool
160 }
161
162 func (s *secretBoxCipher) Encrypt(plaintext, key []byte) []byte {
163 var keyBytes [boxKeySize]byte
164 var nonceBytes [boxNonceSize]byte
165
166 if len(key) != len(keyBytes) {
167 panic("incorrect key size")
168 }
169 if len(s.Nonce) != len(nonceBytes) {
170 panic("incorrect nonce size")
171 }
172
173 copy(keyBytes[:], key)
174 copy(nonceBytes[:], s.Nonce)
175
176
177 if s.encrypted {
178 panic("Encrypt must only be called once for each cipher instance")
179 }
180 s.encrypted = true
181
182 return secretbox.Seal(nil, plaintext, &nonceBytes, &keyBytes)
183 }
184
185 func (s *secretBoxCipher) Decrypt(ciphertext, key []byte) ([]byte, error) {
186 var keyBytes [boxKeySize]byte
187 var nonceBytes [boxNonceSize]byte
188
189 if len(key) != len(keyBytes) {
190 panic("incorrect key size")
191 }
192 if len(s.Nonce) != len(nonceBytes) {
193
194 return nil, errors.New("encrypted: incorrect nonce size")
195 }
196
197 copy(keyBytes[:], key)
198 copy(nonceBytes[:], s.Nonce)
199
200 res, ok := secretbox.Open(nil, ciphertext, &nonceBytes, &keyBytes)
201 if !ok {
202 return nil, errors.New("encrypted: decryption failed")
203 }
204 return res, nil
205 }
206
207
208
209 func Encrypt(plaintext, passphrase []byte) ([]byte, error) {
210 return EncryptWithCustomKDFParameters(plaintext, passphrase, Standard)
211 }
212
213
214
215
216 func EncryptWithCustomKDFParameters(plaintext, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) {
217 k, err := newScryptKDF(kdfLevel)
218 if err != nil {
219 return nil, err
220 }
221 key, err := k.Key(passphrase)
222 if err != nil {
223 return nil, err
224 }
225
226 c, err := newSecretBoxCipher()
227 if err != nil {
228 return nil, err
229 }
230
231 data := &data{
232 KDF: k,
233 Cipher: c,
234 }
235 data.Ciphertext = c.Encrypt(plaintext, key)
236
237 return json.Marshal(data)
238 }
239
240
241 func Marshal(v interface{}, passphrase []byte) ([]byte, error) {
242 return MarshalWithCustomKDFParameters(v, passphrase, Standard)
243 }
244
245
246 func MarshalWithCustomKDFParameters(v interface{}, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) {
247 data, err := json.MarshalIndent(v, "", "\t")
248 if err != nil {
249 return nil, err
250 }
251 return EncryptWithCustomKDFParameters(data, passphrase, kdfLevel)
252 }
253
254
255
256
257 func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
258 data := &data{}
259 if err := json.Unmarshal(ciphertext, data); err != nil {
260 return nil, err
261 }
262
263 if data.KDF.Name != nameScrypt {
264 return nil, fmt.Errorf("encrypted: unknown kdf name %q", data.KDF.Name)
265 }
266 if data.Cipher.Name != nameSecretBox {
267 return nil, fmt.Errorf("encrypted: unknown cipher name %q", data.Cipher.Name)
268 }
269 if err := data.KDF.CheckParams(); err != nil {
270 return nil, err
271 }
272
273 key, err := data.KDF.Key(passphrase)
274 if err != nil {
275 return nil, err
276 }
277
278 return data.Cipher.Decrypt(data.Ciphertext, key)
279 }
280
281
282
283 func Unmarshal(data []byte, v interface{}, passphrase []byte) error {
284 decrypted, err := Decrypt(data, passphrase)
285 if err != nil {
286 return err
287 }
288 return json.Unmarshal(decrypted, v)
289 }
290
291 func fillRandom(b []byte) error {
292 _, err := io.ReadFull(rand.Reader, b)
293 return err
294 }
295
View as plain text