diff --git a/crypto/crypto.go b/crypto/crypto.go index 6bca758ac..25f317545 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -205,29 +205,33 @@ func (k *EncryptionKey) UnmarshalJSON(data []byte) error { } // Encrypt encrypts and signs data. Stored in ciphertext is IV || Ciphertext || -// MAC. Encrypt returns the ciphertext's length. -func Encrypt(ks *Key, ciphertext, plaintext []byte) (int, error) { - if cap(ciphertext) < len(plaintext)+ivSize+macSize { - return 0, ErrBufferTooSmall +// MAC. Encrypt returns the new ciphertext slice, which is extended when +// necessary. ciphertext and plaintext may point to the same slice. +func Encrypt(ks *Key, ciphertext, plaintext []byte) ([]byte, error) { + // extend ciphertext slice if necessary + if cap(ciphertext) < len(plaintext)+Extension { + ext := len(plaintext) + Extension - cap(ciphertext) + n := len(ciphertext) + ciphertext = append(ciphertext, make([]byte, ext)...) + ciphertext = ciphertext[:n] } iv := newIV() - copy(ciphertext, iv[:]) - c, err := aes.NewCipher(ks.Encrypt[:]) if err != nil { panic(fmt.Sprintf("unable to create cipher: %v", err)) } - e := cipher.NewCTR(c, ciphertext[:ivSize]) + e := cipher.NewCTR(c, iv[:]) e.XORKeyStream(ciphertext[ivSize:cap(ciphertext)], plaintext) + copy(ciphertext, iv[:]) ciphertext = ciphertext[:ivSize+len(plaintext)] mac := poly1305_sign(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign) ciphertext = append(ciphertext, mac...) - return len(ciphertext), nil + return ciphertext, nil } // Decrypt verifies and decrypts the ciphertext. Ciphertext must be in the form diff --git a/crypto/crypto_int_test.go b/crypto/crypto_int_test.go index 8998085b4..c7b06e418 100644 --- a/crypto/crypto_int_test.go +++ b/crypto/crypto_int_test.go @@ -98,6 +98,7 @@ func should_panic(f func()) (did_panic bool) { } func TestCrypto(t *testing.T) { + msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now for _, tv := range test_values { // test encryption k := &Key{ @@ -105,12 +106,10 @@ func TestCrypto(t *testing.T) { Sign: tv.skey, } - msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now - n, err := Encrypt(k, msg, tv.plaintext) + msg, err := Encrypt(k, msg, tv.plaintext) if err != nil { t.Fatal(err) } - msg = msg[:n] // decrypt message _, err = Decrypt(k, []byte{}, msg) diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index a44133454..9cadcd5c0 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -29,11 +29,10 @@ func TestEncryptDecrypt(t *testing.T) { _, err := io.ReadFull(RandomReader(42, size), data) OK(t, err) - ciphertext := restic.GetChunkBuf("TestEncryptDecrypt") - n, err := crypto.Encrypt(k, ciphertext, data) + ciphertext, err := crypto.Encrypt(k, restic.GetChunkBuf("TestEncryptDecrypt"), data) OK(t, err) - plaintext, err := crypto.Decrypt(k, nil, ciphertext[:n]) + plaintext, err := crypto.Decrypt(k, nil, ciphertext) OK(t, err) restic.FreeChunkBuf("TestEncryptDecrypt", ciphertext) @@ -54,10 +53,40 @@ func TestSmallBuffer(t *testing.T) { OK(t, err) ciphertext := make([]byte, size/2) - _, err = crypto.Encrypt(k, ciphertext, data) + ciphertext, err = crypto.Encrypt(k, ciphertext, data) // this must throw an error, since the target slice is too small - Assert(t, err != nil && err == crypto.ErrBufferTooSmall, - "expected restic.ErrBufferTooSmall, got %#v", err) + Assert(t, cap(ciphertext) > size/2, + "expected extended slice, but capacity is only %d bytes", + cap(ciphertext)) + + // check for the correct plaintext + plaintext, err := crypto.Decrypt(k, nil, ciphertext) + OK(t, err) + Assert(t, bytes.Equal(plaintext, data), + "wrong plaintext returned") +} + +func TestSameBuffer(t *testing.T) { + k := crypto.NewKey() + + size := 600 + data := make([]byte, size) + f, err := os.Open("/dev/urandom") + OK(t, err) + + _, err = io.ReadFull(f, data) + OK(t, err) + + ciphertext := make([]byte, size) + copy(ciphertext, data) + + ciphertext, err = crypto.Encrypt(k, ciphertext, ciphertext) + OK(t, err) + + ciphertext, err = crypto.Decrypt(k, ciphertext, ciphertext) + OK(t, err) + Assert(t, bytes.Equal(ciphertext, data), + "wrong plaintext returned") } func TestLargeEncrypt(t *testing.T) { @@ -75,11 +104,10 @@ func TestLargeEncrypt(t *testing.T) { _, err = io.ReadFull(f, data) OK(t, err) - ciphertext := make([]byte, size+crypto.Extension) - n, err := crypto.Encrypt(k, ciphertext, data) + ciphertext, err := crypto.Encrypt(k, make([]byte, size+crypto.Extension), data) OK(t, err) - plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext[:n]) + plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext) OK(t, err) Equals(t, plaintext, data) @@ -183,14 +211,14 @@ func BenchmarkDecrypt(b *testing.B) { plaintext := restic.GetChunkBuf("BenchmarkDecrypt") defer restic.FreeChunkBuf("BenchmarkDecrypt", plaintext) - n, err := crypto.Encrypt(k, ciphertext, data) + ciphertext, err := crypto.Encrypt(k, ciphertext, data) OK(b, err) b.ResetTimer() b.SetBytes(int64(size)) for i := 0; i < b.N; i++ { - plaintext, err = crypto.Decrypt(k, plaintext, ciphertext[:n]) + plaintext, err = crypto.Decrypt(k, plaintext, ciphertext) OK(b, err) } } @@ -245,11 +273,11 @@ func TestDecryptStreamReader(t *testing.T) { ciphertext := make([]byte, size+crypto.Extension) // encrypt with default function - n, err := crypto.Encrypt(k, ciphertext, data) + ciphertext, err = crypto.Encrypt(k, ciphertext, data) OK(t, err) - Assert(t, n == len(data)+crypto.Extension, + Assert(t, len(ciphertext) == len(data)+crypto.Extension, "wrong number of bytes returned after encryption: expected %d, got %d", - len(data)+crypto.Extension, n) + len(data)+crypto.Extension, len(ciphertext)) rd, err := crypto.DecryptFrom(k, bytes.NewReader(ciphertext)) OK(t, err) diff --git a/key.go b/key.go index 095cd2223..70e16aa40 100644 --- a/key.go +++ b/key.go @@ -196,9 +196,7 @@ func AddKey(s Server, password string, template *Key) (*Key, error) { return nil, err } - newkey.Data = GetChunkBuf("key") - n, err = crypto.Encrypt(newkey.user, newkey.Data, buf) - newkey.Data = newkey.Data[:n] + newkey.Data, err = crypto.Encrypt(newkey.user, GetChunkBuf("key"), buf) // dump as json buf, err = json.Marshal(newkey) @@ -234,8 +232,9 @@ func AddKey(s Server, password string, template *Key) (*Key, error) { } // Encrypt encrypts and signs data with the master key. Stored in ciphertext is -// IV || Ciphertext || MAC. Returns the ciphertext length. -func (k *Key) Encrypt(ciphertext, plaintext []byte) (int, error) { +// 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) } diff --git a/server.go b/server.go index 7b16873ed..7c7dce850 100644 --- a/server.go +++ b/server.go @@ -172,13 +172,11 @@ func (s Server) Save(t backend.Type, data []byte, id backend.ID) (Blob, error) { } // encrypt blob - n, err := s.Encrypt(ciphertext, data) + ciphertext, err := s.Encrypt(ciphertext, data) if err != nil { return Blob{}, err } - ciphertext = ciphertext[:n] - // compute ciphertext hash sid := backend.Hash(ciphertext) @@ -309,9 +307,9 @@ func (s Server) Decrypt(ciphertext []byte) ([]byte, error) { return s.key.Decrypt([]byte{}, ciphertext) } -func (s Server) Encrypt(ciphertext, plaintext []byte) (int, error) { +func (s Server) Encrypt(ciphertext, plaintext []byte) ([]byte, error) { if s.key == nil { - return 0, errors.New("key for server not set") + return nil, errors.New("key for server not set") } return s.key.Encrypt(ciphertext, plaintext)