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