2
2
mirror of https://github.com/octoleo/restic.git synced 2024-12-23 11:28:54 +00:00

Merge pull request #1397 from restic/crypto-aead

crypto: Make crypto.Key implement cipher.AEAD
This commit is contained in:
Alexander Neumann 2017-11-01 13:21:10 +01:00
commit 7e2c93420f
9 changed files with 381 additions and 182 deletions

View File

@ -32,6 +32,7 @@ type Rdr interface {
func benchmarkChunkEncrypt(b testing.TB, buf, buf2 []byte, rd Rdr, key *crypto.Key) { func benchmarkChunkEncrypt(b testing.TB, buf, buf2 []byte, rd Rdr, key *crypto.Key) {
rd.Seek(0, 0) rd.Seek(0, 0)
ch := chunker.New(rd, testPol) ch := chunker.New(rd, testPol)
nonce := crypto.NewRandomNonce()
for { for {
chunk, err := ch.Next(buf) chunk, err := ch.Next(buf)
@ -42,12 +43,10 @@ func benchmarkChunkEncrypt(b testing.TB, buf, buf2 []byte, rd Rdr, key *crypto.K
rtest.OK(b, err) rtest.OK(b, err)
// reduce length of buf
rtest.Assert(b, uint(len(chunk.Data)) == chunk.Length, rtest.Assert(b, uint(len(chunk.Data)) == chunk.Length,
"invalid length: got %d, expected %d", len(chunk.Data), chunk.Length) "invalid length: got %d, expected %d", len(chunk.Data), chunk.Length)
_, err = key.Encrypt(buf2, chunk.Data) _ = key.Seal(buf2[:0], nonce, chunk.Data, nil)
rtest.OK(b, err)
} }
} }
@ -71,6 +70,7 @@ func BenchmarkChunkEncrypt(b *testing.B) {
func benchmarkChunkEncryptP(b *testing.PB, buf []byte, rd Rdr, key *crypto.Key) { func benchmarkChunkEncryptP(b *testing.PB, buf []byte, rd Rdr, key *crypto.Key) {
ch := chunker.New(rd, testPol) ch := chunker.New(rd, testPol)
nonce := crypto.NewRandomNonce()
for { for {
chunk, err := ch.Next(buf) chunk, err := ch.Next(buf)
@ -78,8 +78,7 @@ func benchmarkChunkEncryptP(b *testing.PB, buf []byte, rd Rdr, key *crypto.Key)
break break
} }
// reduce length of chunkBuf _ = key.Seal(chunk.Data[:0], nonce, chunk.Data, nil)
key.Encrypt(chunk.Data, chunk.Data)
} }
} }

View File

@ -724,15 +724,15 @@ func checkPack(ctx context.Context, r restic.Repository, id restic.ID) error {
continue continue
} }
n, err := r.Key().Decrypt(buf, buf) nonce, ciphertext := buf[:r.Key().NonceSize()], buf[r.Key().NonceSize():]
plaintext, err := r.Key().Open(ciphertext[:0], nonce, ciphertext, nil)
if err != nil { if err != nil {
debug.Log(" error decrypting blob %v: %v", blob.ID.Str(), err) debug.Log(" error decrypting blob %v: %v", blob.ID.Str(), err)
errs = append(errs, errors.Errorf("blob %v: %v", i, err)) errs = append(errs, errors.Errorf("blob %v: %v", i, err))
continue continue
} }
buf = buf[:n]
hash := restic.Hash(buf) hash := restic.Hash(plaintext)
if !hash.Equal(blob.ID) { if !hash.Equal(blob.ID) {
debug.Log(" Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str()) debug.Log(" Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str())
errs = append(errs, errors.Errorf("Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str())) errs = append(errs, errors.Errorf("Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str()))

View File

@ -147,7 +147,9 @@ func NewRandomKey() *Key {
return k return k
} }
func newIV() []byte { // NewRandomNonce returns a new random nonce. It panics on error so that the
// program is safely terminated.
func NewRandomNonce() []byte {
iv := make([]byte, ivSize) iv := make([]byte, ivSize)
n, err := rand.Read(iv) n, err := rand.Read(iv)
if n != ivSize || err != nil { if n != ivSize || err != nil {
@ -233,91 +235,134 @@ func (k *EncryptionKey) Valid() bool {
// holds the plaintext. // holds the plaintext.
var ErrInvalidCiphertext = errors.New("invalid ciphertext, same slice used for plaintext") var ErrInvalidCiphertext = errors.New("invalid ciphertext, same slice used for plaintext")
// Encrypt encrypts and authenticates data. Stored in ciphertext is IV || Ciphertext || // validNonce checks that nonce is not all zero.
// MAC. Encrypt returns the new ciphertext slice, which is extended when func validNonce(nonce []byte) bool {
// necessary. ciphertext and plaintext may not point to (exactly) the same var sum byte
// slice or non-intersecting slices. for _, b := range nonce {
func (k *Key) Encrypt(ciphertext []byte, plaintext []byte) ([]byte, error) { sum |= b
}
return sum > 0
}
// statically ensure that *Key implements crypto/cipher.AEAD
var _ cipher.AEAD = &Key{}
// NonceSize returns the size of the nonce that must be passed to Seal
// and Open.
func (k *Key) NonceSize() int {
return ivSize
}
// Overhead returns the maximum difference between the lengths of a
// plaintext and its ciphertext.
func (k *Key) Overhead() int {
return macSize
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
//
// taken from the stdlib, crypto/aes/aes_gcm.go
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
//
// The plaintext and dst may alias exactly or not at all. To reuse
// plaintext's storage for the encrypted output, use plaintext[:0] as dst.
func (k *Key) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if !k.Valid() {
panic("key is invalid")
}
if len(additionalData) > 0 {
panic("additional data is not supported")
}
if len(nonce) != ivSize {
panic("incorrect nonce length")
}
if !validNonce(nonce) {
panic("nonce is invalid")
}
ret, out := sliceForAppend(dst, len(plaintext)+k.Overhead())
c, err := aes.NewCipher(k.EncryptionKey[:])
if err != nil {
panic(fmt.Sprintf("unable to create cipher: %v", err))
}
e := cipher.NewCTR(c, nonce)
e.XORKeyStream(out, plaintext)
mac := poly1305MAC(out[:len(plaintext)], nonce, &k.MACKey)
copy(out[len(plaintext):], mac)
return ret
}
// Open decrypts and authenticates ciphertext, authenticates the
// additional data and, if successful, appends the resulting plaintext
// to dst, returning the updated slice. The nonce must be NonceSize()
// bytes long and both it and the additional data must match the
// value passed to Seal.
//
// The ciphertext and dst may alias exactly or not at all. To reuse
// ciphertext's storage for the decrypted output, use ciphertext[:0] as dst.
//
// Even if the function fails, the contents of dst, up to its capacity,
// may be overwritten.
func (k *Key) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if !k.Valid() { if !k.Valid() {
return nil, errors.New("invalid key") return nil, errors.New("invalid key")
} }
ciphertext = ciphertext[:cap(ciphertext)] // check parameters
if len(nonce) != ivSize {
// test for same slice, if possible panic("incorrect nonce length")
if len(plaintext) > 0 && len(ciphertext) > 0 && &plaintext[0] == &ciphertext[0] {
return nil, ErrInvalidCiphertext
} }
// extend ciphertext slice if necessary if !validNonce(nonce) {
if len(ciphertext) < len(plaintext)+Extension { return nil, errors.New("nonce is invalid")
ext := len(plaintext) + Extension - len(ciphertext)
ciphertext = append(ciphertext, make([]byte, ext)...)
}
iv := newIV()
copy(ciphertext, iv[:])
c, err := aes.NewCipher(k.EncryptionKey[:])
if err != nil {
panic(fmt.Sprintf("unable to create cipher: %v", err))
}
e := cipher.NewCTR(c, ciphertext[:ivSize])
e.XORKeyStream(ciphertext[ivSize:], plaintext)
// truncate to only cover iv and actual ciphertext
ciphertext = ciphertext[:ivSize+len(plaintext)]
mac := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &k.MACKey)
ciphertext = append(ciphertext, mac...)
return ciphertext, nil
}
// 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 (k *Key) Decrypt(plaintext []byte, ciphertextWithMac []byte) (int, error) {
if !k.Valid() {
return 0, errors.New("invalid key")
} }
// check for plausible length // check for plausible length
if len(ciphertextWithMac) < Extension { if len(ciphertext) < k.Overhead() {
return 0, errors.Errorf("trying to decrypt invalid data: ciphertext too small") return nil, errors.Errorf("trying to decrypt invalid data: ciphertext too small")
} }
// check buffer length for plaintext l := len(ciphertext) - macSize
plaintextLength := len(ciphertextWithMac) - Extension ct, mac := ciphertext[:l], ciphertext[l:]
if len(plaintext) < plaintextLength {
return 0, errors.Errorf("plaintext buffer too small, %d < %d", len(plaintext), plaintextLength)
}
// extract mac
l := len(ciphertextWithMac) - macSize
ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:]
// extract iv
iv, ciphertext := ciphertextWithIV[:ivSize], ciphertextWithIV[ivSize:]
// verify mac // verify mac
if !poly1305Verify(ciphertext, iv, &k.MACKey, mac) { if !poly1305Verify(ct, nonce, &k.MACKey, mac) {
return 0, ErrUnauthenticated return nil, ErrUnauthenticated
} }
if len(ciphertext) != plaintextLength { ret, out := sliceForAppend(dst, len(ct))
panic("plaintext and ciphertext lengths do not match")
}
// decrypt data
c, err := aes.NewCipher(k.EncryptionKey[:]) c, err := aes.NewCipher(k.EncryptionKey[:])
if err != nil { if err != nil {
panic(fmt.Sprintf("unable to create cipher: %v", err)) panic(fmt.Sprintf("unable to create cipher: %v", err))
} }
e := cipher.NewCTR(c, iv) e := cipher.NewCTR(c, nonce)
e.XORKeyStream(plaintext, ciphertext) e.XORKeyStream(out, ct)
return plaintextLength, nil return ret, nil
} }
// Valid tests if the key is valid. // Valid tests if the key is valid.

View File

@ -113,46 +113,80 @@ func TestCrypto(t *testing.T) {
MACKey: tv.skey, MACKey: tv.skey,
} }
msg, err := k.Encrypt(msg, tv.plaintext) nonce := NewRandomNonce()
if err != nil { ciphertext := k.Seal(msg[0:], nonce, tv.plaintext, nil)
t.Fatal(err)
}
// decrypt message // decrypt message
buf := make([]byte, len(tv.plaintext)) buf := make([]byte, 0, len(tv.plaintext))
n, err := k.Decrypt(buf, msg) buf, err := k.Open(buf, nonce, ciphertext, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
buf = buf[:n]
// change mac, this must fail if !bytes.Equal(buf, tv.plaintext) {
msg[len(msg)-8] ^= 0x23 t.Fatalf("wrong plaintext returned")
if _, err = k.Decrypt(buf, msg); err != ErrUnauthenticated {
t.Fatal("wrong MAC value not detected")
} }
// change mac, this must fail
ciphertext[len(ciphertext)-8] ^= 0x23
if _, err = k.Open(buf[:0], nonce, ciphertext, nil); err != ErrUnauthenticated {
t.Fatal("wrong MAC value not detected")
}
// reset mac // reset mac
msg[len(msg)-8] ^= 0x23 ciphertext[len(ciphertext)-8] ^= 0x23
// tamper with nonce, this must fail
nonce[2] ^= 0x88
if _, err = k.Open(buf[:0], nonce, ciphertext, nil); err != ErrUnauthenticated {
t.Fatal("tampered nonce not detected")
}
// reset nonce
nonce[2] ^= 0x88
// tamper with message, this must fail // tamper with message, this must fail
msg[16+5] ^= 0x85 ciphertext[16+5] ^= 0x85
if _, err = k.Open(buf[:0], nonce, ciphertext, nil); err != ErrUnauthenticated {
if _, err = k.Decrypt(buf, msg); err != ErrUnauthenticated {
t.Fatal("tampered message not detected") t.Fatal("tampered message not detected")
} }
// test decryption // test decryption
p := make([]byte, len(tv.ciphertext)) p := make([]byte, len(tv.ciphertext))
n, err = k.Decrypt(p, tv.ciphertext) nonce, ciphertext = tv.ciphertext[:16], tv.ciphertext[16:]
p, err = k.Open(p[:0], nonce, ciphertext, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p = p[:n]
if !bytes.Equal(p, tv.plaintext) { if !bytes.Equal(p, tv.plaintext) {
t.Fatalf("wrong plaintext: expected %q but got %q\n", tv.plaintext, p) t.Fatalf("wrong plaintext: expected %q but got %q\n", tv.plaintext, p)
} }
} }
} }
func TestNonceVadlid(t *testing.T) {
nonce := make([]byte, ivSize)
if validNonce(nonce) {
t.Error("null nonce detected as valid")
}
for i := 0; i < 100; i++ {
nonce = NewRandomNonce()
if !validNonce(nonce) {
t.Errorf("random nonce not detected as valid: %02x", nonce)
}
}
}
func BenchmarkNonceValid(b *testing.B) {
nonce := NewRandomNonce()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if !validNonce(nonce) {
b.Fatal("nonce is invalid")
}
}
}

View File

@ -24,18 +24,17 @@ func TestEncryptDecrypt(t *testing.T) {
for _, size := range tests { for _, size := range tests {
data := rtest.Random(42, size) data := rtest.Random(42, size)
buf := make([]byte, size+crypto.Extension) buf := make([]byte, 0, size+crypto.Extension)
ciphertext, err := k.Encrypt(buf, data) nonce := crypto.NewRandomNonce()
rtest.OK(t, err) ciphertext := k.Seal(buf[:0], nonce, data, nil)
rtest.Assert(t, len(ciphertext) == len(data)+crypto.Extension, rtest.Assert(t, len(ciphertext) == len(data)+k.Overhead(),
"ciphertext length does not match: want %d, got %d", "ciphertext length does not match: want %d, got %d",
len(data)+crypto.Extension, len(ciphertext)) len(data)+crypto.Extension, len(ciphertext))
plaintext := make([]byte, len(ciphertext)) plaintext := make([]byte, 0, len(ciphertext))
n, err := k.Decrypt(plaintext, ciphertext) plaintext, err := k.Open(plaintext[:0], nonce, ciphertext, nil)
rtest.OK(t, err) rtest.OK(t, err)
plaintext = plaintext[:n]
rtest.Assert(t, len(plaintext) == len(data), rtest.Assert(t, len(plaintext) == len(data),
"plaintext length does not match: want %d, got %d", "plaintext length does not match: want %d, got %d",
len(data), len(plaintext)) len(data), len(plaintext))
@ -52,8 +51,9 @@ func TestSmallBuffer(t *testing.T) {
_, err := io.ReadFull(rand.Reader, data) _, err := io.ReadFull(rand.Reader, data)
rtest.OK(t, err) rtest.OK(t, err)
ciphertext := make([]byte, size/2) ciphertext := make([]byte, 0, size/2)
ciphertext, err = k.Encrypt(ciphertext, data) nonce := crypto.NewRandomNonce()
ciphertext = k.Seal(ciphertext[:0], nonce, data, nil)
// this must extend the slice // this must extend the slice
rtest.Assert(t, cap(ciphertext) > size/2, rtest.Assert(t, cap(ciphertext) > size/2,
"expected extended slice, but capacity is only %d bytes", "expected extended slice, but capacity is only %d bytes",
@ -61,9 +61,8 @@ func TestSmallBuffer(t *testing.T) {
// check for the correct plaintext // check for the correct plaintext
plaintext := make([]byte, len(ciphertext)) plaintext := make([]byte, len(ciphertext))
n, err := k.Decrypt(plaintext, ciphertext) plaintext, err = k.Open(plaintext[:0], nonce, ciphertext, nil)
rtest.OK(t, err) rtest.OK(t, err)
plaintext = plaintext[:n]
rtest.Assert(t, bytes.Equal(plaintext, data), rtest.Assert(t, bytes.Equal(plaintext, data),
"wrong plaintext returned") "wrong plaintext returned")
} }
@ -78,37 +77,169 @@ func TestSameBuffer(t *testing.T) {
ciphertext := make([]byte, 0, size+crypto.Extension) ciphertext := make([]byte, 0, size+crypto.Extension)
ciphertext, err = k.Encrypt(ciphertext, data) nonce := crypto.NewRandomNonce()
rtest.OK(t, err) ciphertext = k.Seal(ciphertext, nonce, data, nil)
// use the same buffer for decryption // use the same buffer for decryption
n, err := k.Decrypt(ciphertext, ciphertext) ciphertext, err = k.Open(ciphertext[:0], nonce, ciphertext, nil)
rtest.OK(t, err) rtest.OK(t, err)
ciphertext = ciphertext[:n]
rtest.Assert(t, bytes.Equal(ciphertext, data), rtest.Assert(t, bytes.Equal(ciphertext, data),
"wrong plaintext returned") "wrong plaintext returned")
} }
func TestCornerCases(t *testing.T) { func encrypt(t testing.TB, k *crypto.Key, data, ciphertext, nonce []byte) []byte {
prefixlen := len(ciphertext)
ciphertext = k.Seal(ciphertext, nonce, data, nil)
if len(ciphertext) != len(data)+k.Overhead()+prefixlen {
t.Fatalf("destination slice has wrong length, want %d, got %d",
len(data)+k.Overhead(), len(ciphertext))
}
return ciphertext
}
func decryptNewSliceAndCompare(t testing.TB, k *crypto.Key, data, ciphertext, nonce []byte) {
plaintext := make([]byte, 0, len(ciphertext))
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
}
func decryptAndCompare(t testing.TB, k *crypto.Key, data, ciphertext, nonce, dst []byte) {
prefix := make([]byte, len(dst))
copy(prefix, dst)
plaintext, err := k.Open(dst, nonce, ciphertext, nil)
if err != nil {
t.Fatalf("unable to decrypt ciphertext: %v", err)
}
if len(data)+len(prefix) != len(plaintext) {
t.Fatalf("wrong plaintext returned, want %d bytes, got %d", len(data)+len(prefix), len(plaintext))
}
if !bytes.Equal(plaintext[:len(prefix)], prefix) {
t.Fatal("prefix is wrong")
}
if !bytes.Equal(plaintext[len(prefix):], data) {
t.Fatal("wrong plaintext returned")
}
}
func TestAppendOpen(t *testing.T) {
k := crypto.NewRandomKey()
nonce := crypto.NewRandomNonce()
data := make([]byte, 600)
_, err := io.ReadFull(rand.Reader, data)
rtest.OK(t, err)
ciphertext := encrypt(t, k, data, nil, nonce)
// we need to test several different cases:
// * destination slice is nil
// * destination slice is empty and has enough capacity
// * destination slice is empty and does not have enough capacity
// * destination slice contains data and has enough capacity
// * destination slice contains data and does not have enough capacity
// destination slice is nil
t.Run("nil", func(t *testing.T) {
var plaintext []byte
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
})
// destination slice is empty and has enough capacity
t.Run("empty-large", func(t *testing.T) {
plaintext := make([]byte, 0, len(data)+100)
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
})
// destination slice is empty and does not have enough capacity
t.Run("empty-small", func(t *testing.T) {
plaintext := make([]byte, 0, len(data)/2)
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
})
// destination slice contains data and has enough capacity
t.Run("prefix-large", func(t *testing.T) {
plaintext := make([]byte, 0, len(data)+100)
plaintext = append(plaintext, []byte("foobar")...)
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
})
// destination slice contains data and does not have enough capacity
t.Run("prefix-small", func(t *testing.T) {
plaintext := make([]byte, 0, len(data)/2)
plaintext = append(plaintext, []byte("foobar")...)
decryptAndCompare(t, k, data, ciphertext, nonce, plaintext)
})
}
func TestAppendSeal(t *testing.T) {
k := crypto.NewRandomKey() k := crypto.NewRandomKey()
// nil plaintext should encrypt to the empty string data := make([]byte, 600)
// nil ciphertext should allocate a new slice for the ciphertext _, err := io.ReadFull(rand.Reader, data)
c, err := k.Encrypt(nil, nil)
rtest.OK(t, err) rtest.OK(t, err)
rtest.Assert(t, len(c) == crypto.Extension, // we need to test several different cases:
"wrong length returned for ciphertext, expected 0, got %d", // * destination slice is nil
len(c)) // * destination slice is empty and has enough capacity
// * destination slice is empty and does not have enough capacity
// * destination slice contains data and has enough capacity
// * destination slice contains data and does not have enough capacity
// this should decrypt to nil // destination slice is nil
n, err := k.Decrypt(nil, c) t.Run("nil", func(t *testing.T) {
rtest.OK(t, err) nonce := crypto.NewRandomNonce()
rtest.Equals(t, 0, n) var ciphertext []byte
// test encryption for same slice, this should return an error ciphertext = encrypt(t, k, data, ciphertext, nonce)
_, err = k.Encrypt(c, c) decryptNewSliceAndCompare(t, k, data, ciphertext, nonce)
rtest.Equals(t, crypto.ErrInvalidCiphertext, err) })
// destination slice is empty and has enough capacity
t.Run("empty-large", func(t *testing.T) {
nonce := crypto.NewRandomNonce()
ciphertext := make([]byte, 0, len(data)+100)
ciphertext = encrypt(t, k, data, ciphertext, nonce)
decryptNewSliceAndCompare(t, k, data, ciphertext, nonce)
})
// destination slice is empty and does not have enough capacity
t.Run("empty-small", func(t *testing.T) {
nonce := crypto.NewRandomNonce()
ciphertext := make([]byte, 0, len(data)/2)
ciphertext = encrypt(t, k, data, ciphertext, nonce)
decryptNewSliceAndCompare(t, k, data, ciphertext, nonce)
})
// destination slice contains data and has enough capacity
t.Run("prefix-large", func(t *testing.T) {
nonce := crypto.NewRandomNonce()
ciphertext := make([]byte, 0, len(data)+100)
ciphertext = append(ciphertext, []byte("foobar")...)
ciphertext = encrypt(t, k, data, ciphertext, nonce)
if string(ciphertext[:6]) != "foobar" {
t.Errorf("prefix is missing")
}
decryptNewSliceAndCompare(t, k, data, ciphertext[6:], nonce)
})
// destination slice contains data and does not have enough capacity
t.Run("prefix-small", func(t *testing.T) {
nonce := crypto.NewRandomNonce()
ciphertext := make([]byte, 0, len(data)/2)
ciphertext = append(ciphertext, []byte("foobar")...)
ciphertext = encrypt(t, k, data, ciphertext, nonce)
if string(ciphertext[:6]) != "foobar" {
t.Errorf("prefix is missing")
}
decryptNewSliceAndCompare(t, k, data, ciphertext[6:], nonce)
})
} }
func TestLargeEncrypt(t *testing.T) { func TestLargeEncrypt(t *testing.T) {
@ -123,10 +254,9 @@ func TestLargeEncrypt(t *testing.T) {
_, err := io.ReadFull(rand.Reader, data) _, err := io.ReadFull(rand.Reader, data)
rtest.OK(t, err) rtest.OK(t, err)
ciphertext, err := k.Encrypt(make([]byte, size+crypto.Extension), data) nonce := crypto.NewRandomNonce()
rtest.OK(t, err) ciphertext := k.Seal(make([]byte, size+k.Overhead()), nonce, data, nil)
plaintext, err := k.Open([]byte{}, nonce, ciphertext, nil)
plaintext, err := k.Decrypt([]byte{}, ciphertext)
rtest.OK(t, err) rtest.OK(t, err)
rtest.Equals(t, plaintext, data) rtest.Equals(t, plaintext, data)
@ -139,13 +269,13 @@ func BenchmarkEncrypt(b *testing.B) {
k := crypto.NewRandomKey() k := crypto.NewRandomKey()
buf := make([]byte, len(data)+crypto.Extension) buf := make([]byte, len(data)+crypto.Extension)
nonce := crypto.NewRandomNonce()
b.ResetTimer() b.ResetTimer()
b.SetBytes(int64(size)) b.SetBytes(int64(size))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := k.Encrypt(buf, data) _ = k.Seal(buf, nonce, data, nil)
rtest.OK(b, err)
} }
} }
@ -155,17 +285,18 @@ func BenchmarkDecrypt(b *testing.B) {
k := crypto.NewRandomKey() k := crypto.NewRandomKey()
plaintext := make([]byte, size) plaintext := make([]byte, 0, size)
ciphertext := make([]byte, size+crypto.Extension) ciphertext := make([]byte, 0, size+crypto.Extension)
nonce := crypto.NewRandomNonce()
ciphertext = k.Seal(ciphertext, nonce, data, nil)
ciphertext, err := k.Encrypt(ciphertext, data) var err error
rtest.OK(b, err)
b.ResetTimer() b.ResetTimer()
b.SetBytes(int64(size)) b.SetBytes(int64(size))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err = k.Decrypt(plaintext, ciphertext) _, err = k.Open(plaintext, nonce, ciphertext, nil)
rtest.OK(b, err) rtest.OK(b, err)
} }
} }

View File

@ -75,10 +75,10 @@ func (p *Packer) Finalize() (uint, error) {
return 0, err return 0, err
} }
encryptedHeader, err := p.k.Encrypt(nil, hdrBuf.Bytes()) encryptedHeader := make([]byte, 0, hdrBuf.Len()+p.k.Overhead()+p.k.NonceSize())
if err != nil { nonce := crypto.NewRandomNonce()
return 0, err encryptedHeader = append(encryptedHeader, nonce...)
} encryptedHeader = p.k.Seal(encryptedHeader, nonce, hdrBuf.Bytes(), nil)
// append the header // append the header
n, err := p.wr.Write(encryptedHeader) n, err := p.wr.Write(encryptedHeader)
@ -268,15 +268,19 @@ func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries []restic.Blob, err
return nil, err return nil, err
} }
n, err := k.Decrypt(buf, buf) if len(buf) < k.NonceSize()+k.Overhead() {
return nil, errors.New("invalid header, too small")
}
nonce, buf := buf[:k.NonceSize()], buf[k.NonceSize():]
buf, err = k.Open(buf[:0], nonce, buf, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
buf = buf[:n]
hdrRd := bytes.NewReader(buf) hdrRd := bytes.NewReader(buf)
entries = make([]restic.Blob, 0, uint(n)/entrySize) entries = make([]restic.Blob, 0, uint(len(buf))/entrySize)
pos := uint(0) pos := uint(0)
for { for {

View File

@ -87,12 +87,11 @@ func OpenKey(ctx context.Context, s *Repository, name string, password string) (
} }
// decrypt master keys // decrypt master keys
buf := make([]byte, len(k.Data)) nonce, ciphertext := k.Data[:k.user.NonceSize()], k.Data[k.user.NonceSize():]
n, err := k.user.Decrypt(buf, k.Data) buf, err := k.user.Open(nil, nonce, ciphertext, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
buf = buf[:n]
// restore json // restore json
k.master = &crypto.Key{} k.master = &crypto.Key{}
@ -221,7 +220,11 @@ func AddKey(ctx context.Context, s *Repository, password string, template *crypt
return nil, errors.Wrap(err, "Marshal") return nil, errors.Wrap(err, "Marshal")
} }
newkey.Data, err = newkey.user.Encrypt(nil, buf) nonce := crypto.NewRandomNonce()
ciphertext := make([]byte, 0, len(buf)+newkey.user.Overhead()+newkey.user.NonceSize())
ciphertext = append(ciphertext, nonce...)
ciphertext = newkey.user.Seal(ciphertext, nonce, buf, nil)
newkey.Data = ciphertext
// dump as json // dump as json
buf, err = json.Marshal(newkey) buf, err = json.Marshal(newkey)

View File

@ -90,14 +90,13 @@ func Repack(ctx context.Context, repo restic.Repository, packs restic.IDSet, kee
h, tempfile.Name(), len(buf), n) h, tempfile.Name(), len(buf), n)
} }
n, err = repo.Key().Decrypt(buf, buf) nonce, ciphertext := buf[:repo.Key().NonceSize()], buf[repo.Key().NonceSize():]
plaintext, err := repo.Key().Open(ciphertext[:0], nonce, ciphertext, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
buf = buf[:n] id := restic.Hash(plaintext)
id := restic.Hash(buf)
if !id.Equal(entry.ID) { if !id.Equal(entry.ID) {
debug.Log("read blob %v/%v from %v: wrong data returned, hash is %v", debug.Log("read blob %v/%v from %v: wrong data returned, hash is %v",
h.Type, h.ID, tempfile.Name(), id) h.Type, h.ID, tempfile.Name(), id)
@ -105,7 +104,7 @@ func Repack(ctx context.Context, repo restic.Repository, packs restic.IDSet, kee
h, tempfile.Name(), id) h, tempfile.Name(), id)
} }
_, err = repo.SaveBlob(ctx, entry.Type, buf, entry.ID) _, err = repo.SaveBlob(ctx, entry.Type, plaintext, entry.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -79,13 +79,13 @@ func (r *Repository) LoadAndDecrypt(ctx context.Context, t restic.FileType, id r
return nil, errors.Errorf("load %v: invalid data returned", h) return nil, errors.Errorf("load %v: invalid data returned", h)
} }
// decrypt nonce, ciphertext := buf[:r.key.NonceSize()], buf[r.key.NonceSize():]
n, err := r.decryptTo(buf, buf) plaintext, err := r.key.Open(ciphertext[:0], nonce, ciphertext, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return buf[:n], nil return plaintext, nil
} }
// sortCachedPacks moves all cached pack files to the front of blobs. // sortCachedPacks moves all cached pack files to the front of blobs.
@ -156,20 +156,22 @@ func (r *Repository) loadBlob(ctx context.Context, id restic.ID, t restic.BlobTy
} }
// decrypt // decrypt
n, err = r.decryptTo(plaintextBuf, plaintextBuf) nonce, ciphertext := plaintextBuf[:r.key.NonceSize()], plaintextBuf[r.key.NonceSize():]
plaintext, err := r.key.Open(ciphertext[:0], nonce, ciphertext, nil)
if err != nil { if err != nil {
lastError = errors.Errorf("decrypting blob %v failed: %v", id, err) lastError = errors.Errorf("decrypting blob %v failed: %v", id, err)
continue continue
} }
plaintextBuf = plaintextBuf[:n]
// check hash // check hash
if !restic.Hash(plaintextBuf).Equal(id) { if !restic.Hash(plaintext).Equal(id) {
lastError = errors.Errorf("blob %v returned invalid hash", id) lastError = errors.Errorf("blob %v returned invalid hash", id)
continue continue
} }
return len(plaintextBuf), nil // move decrypted data to the start of the provided buffer
copy(plaintextBuf[0:], plaintext)
return len(plaintext), nil
} }
if lastError != nil { if lastError != nil {
@ -210,11 +212,12 @@ func (r *Repository) SaveAndEncrypt(ctx context.Context, t restic.BlobType, data
ciphertext := getBuf() ciphertext := getBuf()
defer freeBuf(ciphertext) defer freeBuf(ciphertext)
ciphertext = ciphertext[:0]
nonce := crypto.NewRandomNonce()
ciphertext = append(ciphertext, nonce...)
// encrypt blob // encrypt blob
ciphertext, err := r.Encrypt(ciphertext, data) ciphertext = r.key.Seal(ciphertext, nonce, data, nil)
if err != nil {
return restic.ID{}, err
}
// find suitable packer and add blob // find suitable packer and add blob
var pm *packerManager var pm *packerManager
@ -266,10 +269,11 @@ func (r *Repository) SaveJSONUnpacked(ctx context.Context, t restic.FileType, it
// storage hash. // storage hash.
func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, p []byte) (id restic.ID, err error) { func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, p []byte) (id restic.ID, err error) {
ciphertext := restic.NewBlobBuffer(len(p)) ciphertext := restic.NewBlobBuffer(len(p))
ciphertext, err = r.Encrypt(ciphertext, p) ciphertext = ciphertext[:0]
if err != nil { nonce := crypto.NewRandomNonce()
return restic.ID{}, err ciphertext = append(ciphertext, nonce...)
}
ciphertext = r.key.Seal(ciphertext, nonce, p, nil)
id = restic.Hash(ciphertext) id = restic.Hash(ciphertext)
h := restic.Handle{Type: t, Name: id.String()} h := restic.Handle{Type: t, Name: id.String()}
@ -522,26 +526,6 @@ func (r *Repository) init(ctx context.Context, password string, cfg restic.Confi
return err return err
} }
// decrypt authenticates and decrypts ciphertext and stores the result in
// plaintext.
func (r *Repository) decryptTo(plaintext, ciphertext []byte) (int, error) {
if r.key == nil {
return 0, errors.New("key for repository not set")
}
return r.key.Decrypt(plaintext, ciphertext)
}
// Encrypt encrypts and authenticates the plaintext and saves the result in
// ciphertext.
func (r *Repository) Encrypt(ciphertext, plaintext []byte) ([]byte, error) {
if r.key == nil {
return nil, errors.New("key for repository not set")
}
return r.key.Encrypt(ciphertext, plaintext)
}
// Key returns the current master key. // Key returns the current master key.
func (r *Repository) Key() *crypto.Key { func (r *Repository) Key() *crypto.Key {
return r.key return r.key