diff --git a/crypto/crypto.go b/crypto/crypto.go index e00da9435..0e21233cc 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -33,22 +33,22 @@ var ( ErrBufferTooSmall = errors.New("destination buffer too small") ) -// Key holds signing and encryption keys for a repository. It is stored -// encrypted and signed as a JSON data structure in the Data field of the Key +// Key holds encryption and message authentication keys for a repository. It is stored +// encrypted and authenticated as a JSON data structure in the Data field of the Key // structure. For the master key, the secret random polynomial used for content // defined chunking is included. type Key struct { - Sign SigningKey `json:"sign"` + MAC MACKey `json:"mac"` Encrypt EncryptionKey `json:"encrypt"` ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"` } type EncryptionKey [32]byte -type SigningKey struct { +type MACKey struct { K [16]byte // for AES-128 R [16]byte // for Poly1305 - masked bool // remember if the signing key has already been masked + masked bool // remember if the MAC key has already been masked } // mask for key, (cf. http://cr.yp.to/mac/poly1305-20050329.pdf) @@ -71,7 +71,7 @@ var poly1305KeyMask = [16]byte{ 0x0f, // 15: top four bits zero } -func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte { +func poly1305MAC(msg []byte, nonce []byte, key *MACKey) []byte { k := poly1305PrepareKey(nonce, key) var out [16]byte @@ -81,7 +81,7 @@ func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte { } // mask poly1305 key -func maskKey(k *SigningKey) { +func maskKey(k *MACKey) { if k == nil || k.masked { return } @@ -94,14 +94,14 @@ func maskKey(k *SigningKey) { } // construct mac key from slice (k||r), with masking -func macKeyFromSlice(mk *SigningKey, data []byte) { +func macKeyFromSlice(mk *MACKey, data []byte) { copy(mk.K[:], data[:16]) copy(mk.R[:], data[16:32]) maskKey(mk) } // prepare key for low-level poly1305.Sum(): r||n -func poly1305PrepareKey(nonce []byte, key *SigningKey) [32]byte { +func poly1305PrepareKey(nonce []byte, key *MACKey) [32]byte { var k [32]byte maskKey(key) @@ -117,7 +117,7 @@ func poly1305PrepareKey(nonce []byte, key *SigningKey) [32]byte { return k } -func poly1305Verify(msg []byte, nonce []byte, key *SigningKey, mac []byte) bool { +func poly1305Verify(msg []byte, nonce []byte, key *MACKey, mac []byte) bool { k := poly1305PrepareKey(nonce, key) var m [16]byte @@ -126,7 +126,7 @@ func poly1305Verify(msg []byte, nonce []byte, key *SigningKey, mac []byte) bool return poly1305.Verify(&m, msg, &k) } -// NewRandomKey returns new encryption and signing keys. +// NewRandomKey returns new encryption and message authentication keys. func NewRandomKey() *Key { k := &Key{} @@ -135,17 +135,17 @@ func NewRandomKey() *Key { panic("unable to read enough random bytes for encryption key") } - n, err = rand.Read(k.Sign.K[:]) + n, err = rand.Read(k.MAC.K[:]) if n != macKeySizeK || err != nil { - panic("unable to read enough random bytes for mac encryption key") + panic("unable to read enough random bytes for MAC encryption key") } - n, err = rand.Read(k.Sign.R[:]) + n, err = rand.Read(k.MAC.R[:]) if n != macKeySizeR || err != nil { - panic("unable to read enough random bytes for mac signing key") + panic("unable to read enough random bytes for MAC key") } - maskKey(&k.Sign) + maskKey(&k.MAC) return k } @@ -163,11 +163,11 @@ type jsonMACKey struct { R []byte `json:"r"` } -func (m *SigningKey) MarshalJSON() ([]byte, error) { +func (m *MACKey) MarshalJSON() ([]byte, error) { return json.Marshal(jsonMACKey{K: m.K[:], R: m.R[:]}) } -func (m *SigningKey) UnmarshalJSON(data []byte) error { +func (m *MACKey) UnmarshalJSON(data []byte) error { j := jsonMACKey{} err := json.Unmarshal(data, &j) if err != nil { @@ -198,7 +198,7 @@ func (k *EncryptionKey) UnmarshalJSON(data []byte) error { // holds the plaintext. var ErrInvalidCiphertext = errors.New("invalid ciphertext, same slice used for plaintext") -// Encrypt encrypts and signs data. Stored in ciphertext is IV || Ciphertext || +// Encrypt encrypts and authenticates data. Stored in ciphertext is IV || Ciphertext || // MAC. Encrypt returns the new ciphertext slice, which is extended when // necessary. ciphertext and plaintext may not point to (exactly) the same // slice or non-intersecting slices. @@ -231,7 +231,7 @@ func Encrypt(ks *Key, ciphertext []byte, plaintext []byte) ([]byte, error) { // truncate to only cover iv and actual ciphertext ciphertext = ciphertext[:ivSize+len(plaintext)] - mac := poly1305Sign(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign) + mac := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &ks.MAC) ciphertext = append(ciphertext, mac...) return ciphertext, nil @@ -256,7 +256,7 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:] // verify mac - if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.Sign, mac) { + if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.MAC, mac) { return nil, ErrUnauthenticated } @@ -277,8 +277,8 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error return plaintext, nil } -// KDF derives encryption and signing keys from the password using the supplied -// parameters N, R and P and the Salt. +// KDF derives encryption and message authentication keys from the password +// using the supplied parameters N, R and P and the Salt. func KDF(N, R, P int, salt []byte, password string) (*Key, error) { if len(salt) == 0 { return nil, fmt.Errorf("scrypt() called with empty salt") @@ -300,7 +300,7 @@ func KDF(N, R, P int, salt []byte, password string) (*Key, error) { copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize]) // next 32 byte of scrypt output is the mac key, in the form k||r - macKeyFromSlice(&derKeys.Sign, scryptKeys[aesKeySize:]) + macKeyFromSlice(&derKeys.MAC, scryptKeys[aesKeySize:]) return derKeys, nil } diff --git a/crypto/crypto_int_test.go b/crypto/crypto_int_test.go index dfb467e74..5fed6b54c 100644 --- a/crypto/crypto_int_test.go +++ b/crypto/crypto_int_test.go @@ -45,10 +45,10 @@ var poly1305_tests = []struct { func TestPoly1305(t *testing.T) { for _, test := range poly1305_tests { - key := &SigningKey{} + key := &MACKey{} copy(key.K[:], test.k) copy(key.R[:], test.r) - mac := poly1305Sign(test.msg, test.nonce, key) + mac := poly1305MAC(test.msg, test.nonce, key) if !bytes.Equal(mac, test.mac) { t.Fatalf("wrong mac calculated, want: %02x, got: %02x", test.mac, mac) @@ -62,7 +62,7 @@ func TestPoly1305(t *testing.T) { var testValues = []struct { ekey EncryptionKey - skey SigningKey + skey MACKey ciphertext []byte plaintext []byte }{ @@ -71,7 +71,7 @@ var testValues = []struct { 0x30, 0x3e, 0x86, 0x87, 0xb1, 0xd7, 0xdb, 0x18, 0x42, 0x1b, 0xdc, 0x6b, 0xb8, 0x58, 0x8c, 0xca, 0xda, 0xc4, 0xd5, 0x9e, 0xe8, 0x7b, 0x8f, 0xf7, 0x0c, 0x44, 0xe6, 0x35, 0x79, 0x0c, 0xaf, 0xef, }), - skey: SigningKey{ + skey: MACKey{ K: [...]byte{0xef, 0x4d, 0x88, 0x24, 0xcb, 0x80, 0xb2, 0xbc, 0xc5, 0xfb, 0xff, 0x8a, 0x9b, 0x12, 0xa4, 0x2c}, R: [...]byte{0xcc, 0x8d, 0x4b, 0x94, 0x8e, 0xe0, 0xeb, 0xfe, 0x1d, 0x41, 0x5d, 0xe9, 0x21, 0xd1, 0x03, 0x53}, }, @@ -91,7 +91,7 @@ func TestCrypto(t *testing.T) { // test encryption k := &Key{ Encrypt: tv.ekey, - Sign: tv.skey, + MAC: tv.skey, } msg, err := Encrypt(k, msg, tv.plaintext) diff --git a/crypto/writer.go b/crypto/writer.go index c5ec394be..3de2df69c 100644 --- a/crypto/writer.go +++ b/crypto/writer.go @@ -27,7 +27,7 @@ func (e *encryptWriter) Close() error { e.s.XORKeyStream(c, c) // compute mac - mac := poly1305Sign(c, iv, &e.key.Sign) + mac := poly1305MAC(c, iv, &e.key.MAC) e.data = append(e.data, mac...) // write everything diff --git a/doc/Design.md b/doc/Design.md index 083cba818..b815efad9 100644 --- a/doc/Design.md +++ b/doc/Design.md @@ -53,11 +53,12 @@ complete filename. Apart from the files `version`, `id` and the files stored below the `keys` directory, all files are encrypted with AES-256 in counter mode (CTR). The -integrity of the encrypted data is secured by a Poly1305-AES signature. +integrity of the encrypted data is secured by a Poly1305-AES message +authentication code (sometimes also referred to as a "signature"). In the first 16 bytes of each encrypted file the initialisation vector (IV) is -stored. It is followed by the encrypted data and completed by the 16 byte MAC -signature. The format is: `IV || CIPHERTEXT || MAC`. The complete encryption +stored. It is followed by the encrypted data and completed by the 16 byte +MAC. The format is: `IV || CIPHERTEXT || MAC`. The complete encryption overhead is 32 byte. For each file, a new random IV is selected. The basic layout of a sample restic repository is shown below: @@ -100,20 +101,20 @@ The Pack's structure is as follows: EncryptedBlob1 || ... || EncryptedBlobN || EncryptedHeader || Header_Length -At the end of the Pack is a header, which describes the content and is -encrypted and signed. `Header_Length` is the length of the encrypted header +At the end of the Pack is a header, which describes the content. The header is +encrypted and authenticated. `Header_Length` is the length of the encrypted header encoded as a four byte integer in little-endian encoding. Placing the header at the end of a file allows writing the blobs in a continuous stream as soon as they are read during the backup phase. This reduces code complexity and avoids having to re-write a file once the pack is complete and the content and length of the header is known. -All the blobs (`EncryptedBlob1`, `EncryptedBlobN` etc.) are signed and +All the blobs (`EncryptedBlob1`, `EncryptedBlobN` etc.) are authenticated and encrypted independently. This enables repository reorganisation without having to touch the encrypted Blobs. In addition it also allows efficient indexing, for only the header needs to be read in order to find out which Blobs are -contained in the Pack. Since the header is signed, authenticity of the header -can be checked without having to read the complete Pack. +contained in the Pack. Since the header is authenticated, authenticity of the +header can be checked without having to read the complete Pack. After decryption, a Pack's header consists of the following elements: @@ -144,9 +145,9 @@ Indexing Index files contain information about Data and Tree Blobs and the Packs they are contained in and store this information in the repository. When the local cached index is not accessible any more, the index files can be downloaded and -used to reconstruct the index. The files are encrypted and signed like Data and -Tree Blobs, so the outer structure is `IV || Ciphertext || MAC` again. The -plaintext consists of a JSON document like the following: +used to reconstruct the index. The files are encrypted and authenticated like +Data and Tree Blobs, so the outer structure is `IV || Ciphertext || MAC` again. +The plaintext consists of a JSON document like the following: [ { "id": "73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c", @@ -183,21 +184,22 @@ Keys, Encryption and MAC ------------------------ All data stored by restic in the repository is encrypted with AES-256 in -counter mode and signed with Poly1305-AES. For encrypting new data first 16 -bytes are read from a cryptographically secure pseudorandom number generator as -a random nonce. This is used both as the IV for counter mode and the nonce for -Poly1305. This operation needs three keys: A 32 byte for AES-256 for +counter mode and authenticated using Poly1305-AES. For encrypting new data first +16 bytes are read from a cryptographically secure pseudorandom number generator +as a random nonce. This is used both as the IV for counter mode and the nonce +for Poly1305. This operation needs three keys: A 32 byte for AES-256 for encryption, a 16 byte AES key and a 16 byte key for Poly1305. For details see the original paper [The Poly1305-AES message-authentication code](http://cr.yp.to/mac/poly1305-20050329.pdf) by Dan Bernstein. -The data is then encrypted with AES-256 and afterwards the MAC is computed over -the ciphertext, everything is then stored as IV || CIPHERTEXT || MAC. +The data is then encrypted with AES-256 and afterwards a message authentication +code (MAC) is computed over the ciphertext, everything is then stored as +IV || CIPHERTEXT || MAC. The directory `keys` contains key files. These are simple JSON documents which -contain all data that is needed to derive the repository's master signing and -encryption keys from a user's password. The JSON document from the repository -can be pretty-printed for example by using the Python module `json` (shortened -to increase readability): +contain all data that is needed to derive the repository's master encryption and +message authentication keys from a user's password. The JSON document from the +repository can be pretty-printed for example by using the Python module `json` +(shortened to increase readability): $ python -mjson.tool /tmp/restic-repo/keys/b02de82* { @@ -216,24 +218,25 @@ When the repository is opened by restic, the user is prompted for the repository password. This is then used with `scrypt`, a key derivation function (KDF), and the supplied parameters (`N`, `r`, `p` and `salt`) to derive 64 key bytes. The first 32 bytes are used as the encryption key (for AES-256) and the -last 32 bytes are used as the signing key (for Poly1305-AES). These last 32 -bytes are divided into a 16 byte AES key `k` followed by 16 bytes of secret key -`r`. They key `r` is then masked for use with Poly1305 (see the paper for -details). +last 32 bytes are used as the message authentication key (for Poly1305-AES). +These last 32 bytes are divided into a 16 byte AES key `k` followed by 16 bytes +of secret key `r`. They key `r` is then masked for use with Poly1305 (see the +paper for details). -This signing key is used to compute a MAC over the bytes contained in the -JSON field `data` (after removing the Base64 encoding and not including the -last 32 byte). If the password is incorrect or the key file has been tampered -with, the computed MAC will not match the last 16 bytes of the data, and -restic exits with an error. Otherwise, the data is decrypted with the +This message authentication key is used to compute a MAC over the bytes contained +in the JSON field `data` (after removing the Base64 encoding and not including +the last 32 byte). If the password is incorrect or the key file has been +tampered with, the computed MAC will not match the last 16 bytes of the data, +and restic exits with an error. Otherwise, the data is decrypted with the encryption key derived from `scrypt`. This yields a JSON document which -contains the master signing and encryption keys for this repository (encoded in -Base64) and the polynomial that is used for CDC. The command `restic cat -masterkey` can be used as follows to decrypt and pretty-print the master key: +contains the master encryption and message authentication keys for this +repository (encoded in Base64) and the polynomial that is used for CDC. The +command `restic cat masterkey` can be used as follows to decrypt and +pretty-print the master key: $ restic -r /tmp/restic-repo cat masterkey { - "sign": { + "mac": { "k": "evFWd9wWlndL9jc501268g==", "r": "E9eEDnSJZgqwTOkDtOp+Dw==" }, @@ -241,8 +244,9 @@ masterkey` can be used as follows to decrypt and pretty-print the master key: "chunker_polynomial": "2f0797d9c2363f" } -All data in the repository is encrypted and signed with these master keys with -AES-256 in Counter mode and signed with Poly1305-AES as described above. +All data in the repository is encrypted and authenticated with these master keys. +For encryption, the AES-256 algorithm in Counter mode is used. For message +authentication, Poly1305-AES is used as described above. A repository can have several different passwords, with a key file for each. This way, the password can be changed without having to re-encrypt all data. @@ -396,7 +400,7 @@ The restic backup program guarantees the following: * Accessing the unencrypted content of stored files and meta data should not be possible without a password for the repository. Everything except the `version` and `id` files and the meta data included for informational - purposes in the key files is encrypted and then signed. + purposes in the key files is encrypted and authenticated. * Modifications (intentional or unintentional) can be detected automatically on several layers: @@ -406,8 +410,8 @@ The restic backup program guarantees the following: file's name). This way, modifications (bad RAM, broken harddisk) can be detected easily. - 2. Before decrypting any data, the MAC signature on the encrypted data is - checked. If there has been a modification, the signature check will + 2. Before decrypting any data, the MAC on the encrypted data is + checked. If there has been a modification, the MAC check will fail. This step happens even before the data is decrypted, so data that has been tampered with is not decrypted at all. diff --git a/server/key.go b/server/key.go index e763ac96a..f552c1ff2 100644 --- a/server/key.go +++ b/server/key.go @@ -229,16 +229,15 @@ func AddKey(s *Server, password string, template *Key) (*Key, error) { return newkey, nil } -// Encrypt encrypts and signs data with the master key. Stored in ciphertext is -// IV || Ciphertext || MAC. Returns the ciphertext, which is extended if -// necessary. +// Encrypt encrypts and authenticates data with the master key. Stored in +// ciphertext is IV || Ciphertext || MAC. Returns the ciphertext, which is +// extended if necessary. func (k *Key) Encrypt(ciphertext, plaintext []byte) ([]byte, error) { return crypto.Encrypt(k.master, ciphertext, plaintext) } -// EncryptTo encrypts and signs data with the master key. The returned -// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is -// used. +// EncryptTo encrypts and authenticates data with the master key. The returned +// io.Writer writes IV || Ciphertext || MAC. func (k *Key) EncryptTo(wr io.Writer) io.WriteCloser { return crypto.EncryptTo(k.master, wr) } @@ -253,7 +252,7 @@ func (k *Key) Decrypt(plaintext, ciphertext []byte) ([]byte, error) { // available on the returned Reader. Ciphertext must be in the form IV || // Ciphertext || MAC. In order to correctly verify the ciphertext, rd is // drained, locally buffered and made available on the returned Reader -// afterwards. If an MAC verification failure is observed, it is returned +// afterwards. If a MAC verification failure is observed, it is returned // immediately. func (k *Key) DecryptFrom(rd io.Reader) (io.ReadCloser, error) { return crypto.DecryptFrom(k.master, rd)