/
encryption.go
154 lines (132 loc) · 3.69 KB
/
encryption.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
package persistent
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/hkdf"
)
type encryption struct {
base BlockStorage
key []byte
}
// WithEncryption wraps a BlockStorage implementation and makes sure that all
// values are encrypted with AES-GCM before being processed further.
//
// The encryption key is derived with Argon2 from `password`.
func WithEncryption(base BlockStorage, password string) BlockStorage {
// NOTE: The fixed salt to Argon2 is intentional. Its purpose is domain
// separation, not to frustrate a password cracker.
key := argon2.IDKey([]byte(password), []byte("7fedd6d671beec56"), 1, 64*1024, 4, 32)
return &encryption{base, key}
}
func (e *encryption) encrypt(ptr uint64, data []byte) ([]byte, error) {
tag := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(tag, ptr)
tag = tag[:n]
// Compute the encryption key for this block.
key := make([]byte, 32)
_, err := io.ReadFull(hkdf.Expand(sha256.New, e.key, tag), key)
if err != nil {
return nil, err
}
// Initialize the AEAD.
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Generate a fresh nonce and encrypt the given data.
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
ct := aead.Seal(nil, nonce, data, tag)
return append(nonce, ct...), nil
}
func (e *encryption) decrypt(ptr uint64, raw []byte) ([]byte, error) {
tag := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(tag, ptr)
tag = tag[:n]
// Compute the encryption key for this block.
key := make([]byte, 32)
_, err := io.ReadFull(hkdf.Expand(sha256.New, e.key, tag), key)
if err != nil {
return nil, err
}
// Initialize the AEAD.
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Decrypt the given data.
ns := aead.NonceSize()
if len(raw) < ns {
return nil, fmt.Errorf("ciphertext is too small")
}
val, err := aead.Open(nil, raw[:ns], raw[ns:], tag)
if err != nil {
return nil, err
}
return val, nil
}
func (e *encryption) Start(ctx context.Context, prefetch []uint64) (map[uint64][]byte, error) {
data, err := e.base.Start(ctx, prefetch)
if err != nil {
return nil, err
}
out := make(map[uint64][]byte)
for ptr, raw := range data {
val, err := e.decrypt(ptr, raw)
if err != nil {
return nil, fmt.Errorf("encryption: failed to decrypt block %x: %v", ptr, err)
}
out[ptr] = val
}
return out, nil
}
func (e *encryption) Get(ctx context.Context, ptr uint64) ([]byte, error) {
data, err := e.GetMany(ctx, []uint64{ptr})
if err != nil {
return nil, err
} else if data[ptr] == nil {
return nil, ErrObjectNotFound
}
return data[ptr], nil
}
func (e *encryption) GetMany(ctx context.Context, ptrs []uint64) (map[uint64][]byte, error) {
data, err := e.base.GetMany(ctx, ptrs)
if err != nil {
return nil, err
}
out := make(map[uint64][]byte)
for ptr, raw := range data {
val, err := e.decrypt(ptr, raw)
if err != nil {
return nil, fmt.Errorf("encryption: failed to decrypt block %x: %v", ptr, err)
}
out[ptr] = val
}
return out, nil
}
func (e *encryption) Set(ctx context.Context, ptr uint64, data []byte, dt DataType) error {
ct, err := e.encrypt(ptr, data)
if err != nil {
return fmt.Errorf("encryption: failed to encrypt: %v", err)
}
return e.base.Set(ctx, ptr, ct, dt)
}
func (e *encryption) Commit(ctx context.Context) error { return e.base.Commit(ctx) }
func (e *encryption) Rollback(ctx context.Context) { e.base.Rollback(ctx) }