diff --git a/crypto/crypto.go b/crypto/crypto.go index fbf8866e7..0e21233cc 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -14,13 +14,13 @@ import ( ) const ( - aesKeySize = 32 // for AES256 + aesKeySize = 32 // for AES-256 macKeySizeK = 16 // for AES-128 macKeySizeR = 16 // for Poly1305 macKeySize = macKeySizeK + macKeySizeR // for Poly1305-AES128 ivSize = aes.BlockSize - macSize = poly1305.TagSize // Poly1305 size is 16 byte + macSize = poly1305.TagSize Extension = ivSize + macSize ) @@ -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 { - K [16]byte `json:"k"` // for AES128 - R [16]byte `json:"r"` // for Poly1305 +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,27 +71,9 @@ var poly1305KeyMask = [16]byte{ 0x0f, // 15: top four bits zero } -// key is a [32]byte, in the form k||r -func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte { - // prepare key for low-level poly1305.Sum(): r||n - var k [32]byte +func poly1305MAC(msg []byte, nonce []byte, key *MACKey) []byte { + k := poly1305PrepareKey(nonce, key) - // make sure key is masked - if !key.masked { - maskKey(key) - } - - // fill in nonce, encrypted with AES and key[:16] - cipher, err := aes.NewCipher(key.K[:]) - if err != nil { - panic(err) - } - cipher.Encrypt(k[16:], nonce[:]) - - // copy r - copy(k[:16], key.R[:]) - - // save mac in out var out [16]byte poly1305.Sum(&out, msg, &k) @@ -99,10 +81,11 @@ func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte { } // mask poly1305 key -func maskKey(k *SigningKey) { - if k == nil { +func maskKey(k *MACKey) { + if k == nil || k.masked { return } + for i := 0; i < poly1305.TagSize; i++ { k.R[i] = k.R[i] & poly1305KeyMask[i] } @@ -111,59 +94,58 @@ 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) } -// key: k||r -func poly1305Verify(msg []byte, nonce []byte, key *SigningKey, mac []byte) bool { - // prepare key for low-level poly1305.Sum(): r||n +// prepare key for low-level poly1305.Sum(): r||n +func poly1305PrepareKey(nonce []byte, key *MACKey) [32]byte { var k [32]byte - // make sure key is masked - if !key.masked { - maskKey(key) - } + maskKey(key) - // fill in nonce, encrypted with AES and key[:16] cipher, err := aes.NewCipher(key.K[:]) if err != nil { panic(err) } cipher.Encrypt(k[16:], nonce[:]) - // copy r copy(k[:16], key.R[:]) - // copy mac to array + return k +} + +func poly1305Verify(msg []byte, nonce []byte, key *MACKey, mac []byte) bool { + k := poly1305PrepareKey(nonce, key) + var m [16]byte copy(m[:], mac) return poly1305.Verify(&m, msg, &k) } -// NewKey returns new encryption and signing keys. -func NewKey() (k *Key) { - k = &Key{} +// NewRandomKey returns new encryption and message authentication keys. +func NewRandomKey() *Key { + k := &Key{} + n, err := rand.Read(k.Encrypt[:]) if n != aesKeySize || err != nil { 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") } - // mask r - maskKey(&k.Sign) + maskKey(&k.MAC) return k } @@ -181,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 { @@ -216,11 +198,11 @@ 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. -func Encrypt(ks *Key, ciphertext, plaintext []byte) ([]byte, error) { +func Encrypt(ks *Key, ciphertext []byte, plaintext []byte) ([]byte, error) { ciphertext = ciphertext[:cap(ciphertext)] // test for same slice, if possible @@ -245,11 +227,11 @@ func Encrypt(ks *Key, ciphertext, plaintext []byte) ([]byte, error) { e.XORKeyStream(ciphertext[ivSize:], plaintext) copy(ciphertext, iv[:]) - // truncate to only conver iv and actual ciphertext + + // truncate to only cover iv and actual ciphertext ciphertext = ciphertext[:ivSize+len(plaintext)] - mac := poly1305Sign(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign) - // append the mac tag + mac := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &ks.MAC) ciphertext = append(ciphertext, mac...) return ciphertext, nil @@ -258,28 +240,28 @@ func Encrypt(ks *Key, ciphertext, plaintext []byte) ([]byte, error) { // Decrypt verifies and decrypts the ciphertext. Ciphertext must be in the form // IV || Ciphertext || MAC. plaintext and ciphertext may point to (exactly) the // same slice. -func Decrypt(ks *Key, plaintext, ciphertext []byte) ([]byte, error) { +func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error) { // check for plausible length - if len(ciphertext) < ivSize+macSize { + if len(ciphertextWithMac) < ivSize+macSize { panic("trying to decrypt invalid data: ciphertext too small") } - if cap(plaintext) < len(ciphertext) { + if cap(plaintext) < len(ciphertextWithMac) { // extend plaintext - plaintext = append(plaintext, make([]byte, len(ciphertext)-cap(plaintext))...) + plaintext = append(plaintext, make([]byte, len(ciphertextWithMac)-cap(plaintext))...) } // extract mac - l := len(ciphertext) - macSize - ciphertext, mac := ciphertext[:l], ciphertext[l:] + l := len(ciphertextWithMac) - macSize + ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:] // verify mac - if !poly1305Verify(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign, mac) { + if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.MAC, mac) { return nil, ErrUnauthenticated } // extract iv - iv, ciphertext := ciphertext[:ivSize], ciphertext[ivSize:] + iv, ciphertext := ciphertextWithIV[:ivSize], ciphertextWithIV[ivSize:] // decrypt data c, err := aes.NewCipher(ks.Encrypt[:]) @@ -295,8 +277,8 @@ func Decrypt(ks *Key, plaintext, ciphertext []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") @@ -318,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 cced71fd0..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) @@ -61,16 +61,17 @@ func TestPoly1305(t *testing.T) { } var testValues = []struct { - ekey EncryptionKey - skey SigningKey - ciphertext []byte - plaintext []byte - shouldPanic bool + ekey EncryptionKey + skey MACKey + ciphertext []byte + plaintext []byte }{ { - ekey: EncryptionKey([...]byte{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{ + ekey: EncryptionKey([...]byte{ + 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: 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}, }, @@ -84,26 +85,13 @@ func decodeHex(s string) []byte { return d } -// returns true if function called panic -func shouldPanic(f func()) (didPanic bool) { - defer func() { - if r := recover(); r != nil { - didPanic = true - } - }() - - f() - - return false -} - func TestCrypto(t *testing.T) { msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now for _, tv := range testValues { // test encryption k := &Key{ Encrypt: tv.ekey, - Sign: tv.skey, + MAC: tv.skey, } msg, err := Encrypt(k, msg, tv.plaintext) diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 2a8375035..321461a46 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -17,7 +17,7 @@ import ( var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads") func TestEncryptDecrypt(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() tests := []int{5, 23, 2<<18 + 23, 1 << 20} if *testLargeCrypto { @@ -48,7 +48,7 @@ func TestEncryptDecrypt(t *testing.T) { } func TestSmallBuffer(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() size := 600 data := make([]byte, size) @@ -73,7 +73,7 @@ func TestSmallBuffer(t *testing.T) { } func TestSameBuffer(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() size := 600 data := make([]byte, size) @@ -96,7 +96,7 @@ func TestSameBuffer(t *testing.T) { } func TestCornerCases(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() // nil plaintext should encrypt to the empty string // nil ciphertext should allocate a new slice for the ciphertext @@ -122,7 +122,7 @@ func TestLargeEncrypt(t *testing.T) { t.SkipNow() } - k := crypto.NewKey() + k := crypto.NewRandomKey() for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} { data := make([]byte, size) @@ -146,7 +146,7 @@ func BenchmarkEncryptWriter(b *testing.B) { size := 8 << 20 // 8MiB rd := RandomReader(23, size) - k := crypto.NewKey() + k := crypto.NewRandomKey() b.ResetTimer() b.SetBytes(int64(size)) @@ -166,7 +166,7 @@ func BenchmarkEncrypt(b *testing.B) { size := 8 << 20 // 8MiB data := make([]byte, size) - k := crypto.NewKey() + k := crypto.NewRandomKey() buf := make([]byte, len(data)+crypto.Extension) b.ResetTimer() @@ -181,7 +181,7 @@ func BenchmarkEncrypt(b *testing.B) { func BenchmarkDecryptReader(b *testing.B) { size := 8 << 20 // 8MiB buf := Random(23, size) - k := crypto.NewKey() + k := crypto.NewRandomKey() ciphertext := make([]byte, len(buf)+crypto.Extension) _, err := crypto.Encrypt(k, ciphertext, buf) @@ -203,7 +203,7 @@ func BenchmarkDecryptReader(b *testing.B) { } func BenchmarkEncryptDecryptReader(b *testing.B) { - k := crypto.NewKey() + k := crypto.NewRandomKey() size := 8 << 20 // 8MiB rd := RandomReader(23, size) @@ -234,7 +234,7 @@ func BenchmarkDecrypt(b *testing.B) { size := 8 << 20 // 8MiB data := make([]byte, size) - k := crypto.NewKey() + k := crypto.NewRandomKey() ciphertext := restic.GetChunkBuf("BenchmarkDecrypt") defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext) @@ -254,7 +254,7 @@ func BenchmarkDecrypt(b *testing.B) { } func TestEncryptStreamWriter(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() tests := []int{5, 23, 2<<18 + 23, 1 << 20} if *testLargeCrypto { @@ -288,7 +288,7 @@ func TestEncryptStreamWriter(t *testing.T) { } func TestDecryptStreamReader(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() tests := []int{5, 23, 2<<18 + 23, 1 << 20} if *testLargeCrypto { @@ -322,7 +322,7 @@ func TestDecryptStreamReader(t *testing.T) { } func TestEncryptWriter(t *testing.T) { - k := crypto.NewKey() + k := crypto.NewRandomKey() tests := []int{5, 23, 2<<18 + 23, 1 << 20} if *testLargeCrypto { diff --git a/crypto/reader.go b/crypto/reader.go index a5109863b..fe27f5344 100644 --- a/crypto/reader.go +++ b/crypto/reader.go @@ -77,7 +77,6 @@ func DecryptFrom(ks *Key, rd io.Reader) (io.ReadCloser, error) { ciphertext := buf.Bytes() - // decrypt ciphertext, err = Decrypt(ks, ciphertext, ciphertext) if err != nil { return nil, err 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/pack/pack_test.go b/pack/pack_test.go index 001161090..fb44d9115 100644 --- a/pack/pack_test.go +++ b/pack/pack_test.go @@ -37,7 +37,7 @@ func TestCreatePack(t *testing.T) { file := bytes.NewBuffer(nil) // create random keys - k := crypto.NewKey() + k := crypto.NewRandomKey() // pack blobs p := pack.NewPacker(k, file) diff --git a/server/key.go b/server/key.go index 9d4db1b63..f552c1ff2 100644 --- a/server/key.go +++ b/server/key.go @@ -176,7 +176,7 @@ func AddKey(s *Server, password string, template *Key) (*Key, error) { if template == nil { // generate new random master keys - newkey.master = crypto.NewKey() + newkey.master = crypto.NewRandomKey() // generate random polynomial for cdc p, err := chunker.RandomPolynomial() if err != nil { @@ -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)