forked from sahib/brig
-
Notifications
You must be signed in to change notification settings - Fork 0
/
format.go
259 lines (218 loc) · 6.89 KB
/
format.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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Package encrypt implements the encryption layer of brig.
// The file format used looks something like this:
//
// [HEADER][[BLOCKHEADER][PAYLOAD]...]
//
// HEADER is 20+16 bytes big and contains the following fields:
// - 8 Byte: Magic number (to identify non-brig files quickly)
// - 2 Byte: Format version
// - 2 Byte: Used cipher type (ChaCha20 or AES-GCM currently)
// - 4 Byte: Key length in bytes.
// - 4 Byte: Maximum size of each block (last may be less)
// - 16 Byte: MAC protecting the header from forgery
//
// BLOCKHEADER contains the following fields:
// - 8 Byte: Nonce: Derived from the current block number.
// The block number is checked to be correct on decryption.
//
// PAYLOAD contains the actual encrypted data, which includes a MAC at the end.
// The size of the MAC depends on the algorithm, for poly1305 it's 16 bytes.
//
// All header metadata is encoded in little endian.
//
// Reader/Writer are capable or reading/writing this format. Additionally,
// Reader supports efficient seeking into the encrypted data, provided the
// underlying datastream supports seeking. SEEK_END is only supported when the
// number of encrypted blocks is present in the header.
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"encoding/binary"
"fmt"
"io"
chacha "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/sha3"
)
// Possible ciphers in Counter mode:
const (
aeadCipherChaCha = iota
aeadCipherAES
)
// Other constants:
const (
// Size of the header mac:
macSize = 16
// current file format version, increment on incompatible changes.
version = 1
// Size of the initial header:
headerSize = 20 + macSize
// Chacha20 appears to be twice as fast as AES-GCM on my machine
defaultCipherType = aeadCipherAES
// Default maxBlockSize if not set
defaultMaxBlockSize = 64 * 1024
defaultDecBufferSize = defaultMaxBlockSize
defaultEncBufferSize = defaultMaxBlockSize + 40
)
var (
// MagicNumber contains the first 8 byte of every brig header.
// For various reasons, it is the ascii string "moosecat".
MagicNumber = []byte{
0x6d, 0x6f, 0x6f, 0x73,
0x65, 0x63, 0x61, 0x74,
}
)
// KeySize of the used cipher's key in bytes.
var KeySize = chacha.KeySize
////////////////////
// Header Parsing //
////////////////////
// GenerateHeader creates a valid header for the format file
func GenerateHeader(key []byte, maxBlockSize int64, cipher uint16) []byte {
// This is in big endian:
header := []byte{
// Brigs magic number (8 Byte):
0, 0, 0, 0, 0, 0, 0, 0,
// File format version (2 Byte):
0, 0,
// Cipher type (2 Byte):
0, 0,
// Key length (4 Byte):
0, 0, 0, 0,
// Block length (4 Byte):
0, 0, 0, 0,
// MAC Header (16 Byte):
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
}
// Magic number:
copy(header[:len(MagicNumber)], MagicNumber)
binary.LittleEndian.PutUint16(header[8:10], version)
binary.LittleEndian.PutUint16(header[10:12], cipher)
// Encode key size:
binary.LittleEndian.PutUint32(header[12:16], uint32(KeySize))
// Encode max block size:
binary.LittleEndian.PutUint32(header[16:20], uint32(maxBlockSize))
// Calculate a MAC of the header; this needs to be done last:
headerMac := hmac.New(sha3.New224, key)
if _, err := headerMac.Write(header[:headerSize-macSize]); err != nil {
return nil
}
// Copy the MAC to the output:
shortHeaderMac := headerMac.Sum(nil)[:macSize]
copy(header[headerSize-macSize:headerSize], shortHeaderMac)
return header
}
// HeaderInfo represents a parsed header.
type HeaderInfo struct {
// Version of the file format. Currently always 1.
Version uint16
// Cipher type used in the file.
Cipher uint16
// Keylen is the number of bytes in the encryption key.
Keylen uint32
// Blocklen is the max. number of bytes in a block.
// The last block might be smaller.
Blocklen uint32
}
// ParseHeader parses the header of the format file.
// Returns the format version, cipher type, keylength and block length. If
// parsing fails, an error is returned.
func ParseHeader(header, key []byte) (*HeaderInfo, error) {
if bytes.Compare(header[:len(MagicNumber)], MagicNumber) != 0 {
return nil, fmt.Errorf("magic number in header differs")
}
version := binary.LittleEndian.Uint16(header[8:10])
cipher := binary.LittleEndian.Uint16(header[10:12])
switch cipher {
case aeadCipherAES:
case aeadCipherChaCha:
// we support this!
default:
return nil, fmt.Errorf("unknown cipher type: %d", cipher)
}
keylen := binary.LittleEndian.Uint32(header[12:16])
blocklen := binary.LittleEndian.Uint32(header[16:20])
// Check the header mac:
headerMac := hmac.New(sha3.New224, key)
if _, err := headerMac.Write(header[:headerSize-macSize]); err != nil {
return nil, err
}
storedMac := header[headerSize-macSize : headerSize]
shortHeaderMac := headerMac.Sum(nil)[:macSize]
if !hmac.Equal(shortHeaderMac, storedMac) {
return nil, fmt.Errorf("header MAC differs from expected")
}
return &HeaderInfo{
Version: version,
Cipher: cipher,
Keylen: keylen,
Blocklen: blocklen,
}, nil
}
//////////////////////
// Common Utilities //
//////////////////////
func createAEADWorker(cipherType uint16, key []byte) (cipher.AEAD, error) {
switch cipherType {
case aeadCipherAES:
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(block)
case aeadCipherChaCha:
return chacha.New(key)
}
return nil, fmt.Errorf("no such cipher type: %d", cipherType)
}
type aeadCommon struct {
// Nonce that form the first aead.NonceSize() bytes
// of the output
nonce []byte
// Key used for encryption/decryption
key []byte
// For more information, see:
// https://en.wikipedia.org/wiki/Authenticated_encryption
aead cipher.AEAD
// Buffer for encrypted data (maxBlockSize + overhead)
encBuf []byte
}
func (c *aeadCommon) initAeadCommon(key []byte, cipherType uint16, maxBlockSize int64) error {
aead, err := createAEADWorker(cipherType, key)
if err != nil {
return err
}
c.nonce = make([]byte, aead.NonceSize())
c.aead = aead
c.key = key
c.encBuf = make([]byte, 0, maxBlockSize+int64(aead.Overhead()))
return nil
}
// Encrypt is a utility function which encrypts the data from source with key
// and writes the resulting encrypted data to dest.
func Encrypt(key []byte, source io.Reader, dest io.Writer) (int64, error) {
layer, err := NewWriter(dest, key)
if err != nil {
return 0, err
}
n, err := io.CopyBuffer(layer, source, make([]byte, defaultEncBufferSize))
if err != nil {
return n, err
}
if closeErr := layer.Close(); closeErr != nil {
return n, closeErr
}
return n, nil
}
// Decrypt is a utility function which decrypts the data from source with key
// and writes the resulting encrypted data to dest.
func Decrypt(key []byte, source io.Reader, dest io.Writer) (int64, error) {
layer, err := NewReader(source, key)
if err != nil {
return 0, err
}
return io.CopyBuffer(dest, layer, make([]byte, defaultDecBufferSize))
}