From f960831f107edfe66c7fa80f5202a325d65f91d9 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 19 Jun 2017 21:12:53 +0200 Subject: [PATCH] crypto: Make Encrypt/Decrypt a method of *Key --- src/restic/archiver/archiver_test.go | 4 +-- src/restic/checker/checker.go | 3 +- src/restic/crypto/crypto.go | 41 +++++++++++++++++----------- src/restic/crypto/crypto_int_test.go | 14 +++++----- src/restic/crypto/crypto_test.go | 28 +++++++++---------- src/restic/crypto/kdf.go | 4 +-- src/restic/pack/pack.go | 4 +-- src/restic/repository/key.go | 4 +-- src/restic/repository/repack.go | 3 +- src/restic/repository/repository.go | 4 +-- 10 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/restic/archiver/archiver_test.go b/src/restic/archiver/archiver_test.go index f4928089b..236027d6b 100644 --- a/src/restic/archiver/archiver_test.go +++ b/src/restic/archiver/archiver_test.go @@ -43,7 +43,7 @@ func benchmarkChunkEncrypt(b testing.TB, buf, buf2 []byte, rd Rdr, key *crypto.K Assert(b, uint(len(chunk.Data)) == chunk.Length, "invalid length: got %d, expected %d", len(chunk.Data), chunk.Length) - _, err = crypto.Encrypt(key, buf2, chunk.Data) + _, err = key.Encrypt(buf2, chunk.Data) OK(b, err) } } @@ -76,7 +76,7 @@ func benchmarkChunkEncryptP(b *testing.PB, buf []byte, rd Rdr, key *crypto.Key) } // reduce length of chunkBuf - crypto.Encrypt(key, chunk.Data, chunk.Data) + key.Encrypt(chunk.Data, chunk.Data) } } diff --git a/src/restic/checker/checker.go b/src/restic/checker/checker.go index b423da7b6..70320467a 100644 --- a/src/restic/checker/checker.go +++ b/src/restic/checker/checker.go @@ -13,7 +13,6 @@ import ( "restic/hashing" "restic" - "restic/crypto" "restic/debug" "restic/pack" "restic/repository" @@ -725,7 +724,7 @@ func checkPack(ctx context.Context, r restic.Repository, id restic.ID) error { continue } - n, err := crypto.Decrypt(r.Key(), buf, buf) + n, err := r.Key().Decrypt(buf, buf) if err != nil { debug.Log(" error decrypting blob %v: %v", blob.ID.Str(), err) errs = append(errs, errors.Errorf("blob %v: %v", i, err)) diff --git a/src/restic/crypto/crypto.go b/src/restic/crypto/crypto.go index fe7d3e5f2..68e5c6b3a 100644 --- a/src/restic/crypto/crypto.go +++ b/src/restic/crypto/crypto.go @@ -19,7 +19,9 @@ const ( macKeySize = macKeySizeK + macKeySizeR // for Poly1305-AES128 ivSize = aes.BlockSize - macSize = poly1305.TagSize + macSize = poly1305.TagSize + + // Extension is the number of bytes a plaintext is enlarged by encrypting it. Extension = ivSize + macSize ) @@ -32,11 +34,14 @@ var ( // encrypted and authenticated as a JSON data structure in the Data field of the Key // structure. type Key struct { - MAC MACKey `json:"mac"` - Encrypt EncryptionKey `json:"encrypt"` + MACKey `json:"mac"` + EncryptionKey `json:"encrypt"` } +// EncryptionKey is key used for encryption type EncryptionKey [32]byte + +// MACKey is used to sign (authenticate) data. type MACKey struct { K [16]byte // for AES-128 R [16]byte // for Poly1305 @@ -123,22 +128,22 @@ func poly1305Verify(msg []byte, nonce []byte, key *MACKey, mac []byte) bool { func NewRandomKey() *Key { k := &Key{} - n, err := rand.Read(k.Encrypt[:]) + n, err := rand.Read(k.EncryptionKey[:]) if n != aesKeySize || err != nil { panic("unable to read enough random bytes for encryption key") } - n, err = rand.Read(k.MAC.K[:]) + n, err = rand.Read(k.MACKey.K[:]) if n != macKeySizeK || err != nil { panic("unable to read enough random bytes for MAC encryption key") } - n, err = rand.Read(k.MAC.R[:]) + n, err = rand.Read(k.MACKey.R[:]) if n != macKeySizeR || err != nil { panic("unable to read enough random bytes for MAC key") } - maskKey(&k.MAC) + maskKey(&k.MACKey) return k } @@ -156,10 +161,12 @@ type jsonMACKey struct { R []byte `json:"r"` } +// MarshalJSON converts the MACKey to JSON. func (m *MACKey) MarshalJSON() ([]byte, error) { return json.Marshal(jsonMACKey{K: m.K[:], R: m.R[:]}) } +// UnmarshalJSON fills the key m with data from the JSON representation. func (m *MACKey) UnmarshalJSON(data []byte) error { j := jsonMACKey{} err := json.Unmarshal(data, &j) @@ -194,10 +201,12 @@ func (m *MACKey) Valid() bool { return false } +// MarshalJSON converts the EncryptionKey to JSON. func (k *EncryptionKey) MarshalJSON() ([]byte, error) { return json.Marshal(k[:]) } +// UnmarshalJSON fills the key k with data from the JSON representation. func (k *EncryptionKey) UnmarshalJSON(data []byte) error { d := make([]byte, aesKeySize) err := json.Unmarshal(data, &d) @@ -228,8 +237,8 @@ var ErrInvalidCiphertext = errors.New("invalid ciphertext, same slice used for p // 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 []byte, plaintext []byte) ([]byte, error) { - if !ks.Valid() { +func (k *Key) Encrypt(ciphertext []byte, plaintext []byte) ([]byte, error) { + if !k.Valid() { return nil, errors.New("invalid key") } @@ -248,7 +257,7 @@ func Encrypt(ks *Key, ciphertext []byte, plaintext []byte) ([]byte, error) { } iv := newIV() - c, err := aes.NewCipher(ks.Encrypt[:]) + c, err := aes.NewCipher(k.EncryptionKey[:]) if err != nil { panic(fmt.Sprintf("unable to create cipher: %v", err)) } @@ -261,7 +270,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 := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &ks.MAC) + mac := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &k.MACKey) ciphertext = append(ciphertext, mac...) return ciphertext, nil @@ -270,8 +279,8 @@ func Encrypt(ks *Key, ciphertext []byte, 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 []byte, ciphertextWithMac []byte) (int, error) { - if !ks.Valid() { +func (k *Key) Decrypt(plaintext []byte, ciphertextWithMac []byte) (int, error) { + if !k.Valid() { return 0, errors.New("invalid key") } @@ -291,7 +300,7 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) (int, error) { ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:] // verify mac - if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.MAC, mac) { + if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &k.MACKey, mac) { return 0, ErrUnauthenticated } @@ -303,7 +312,7 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) (int, error) { } // decrypt data - c, err := aes.NewCipher(ks.Encrypt[:]) + c, err := aes.NewCipher(k.EncryptionKey[:]) if err != nil { panic(fmt.Sprintf("unable to create cipher: %v", err)) } @@ -318,5 +327,5 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) (int, error) { // Valid tests if the key is valid. func (k *Key) Valid() bool { - return k.Encrypt.Valid() && k.MAC.Valid() + return k.EncryptionKey.Valid() && k.MACKey.Valid() } diff --git a/src/restic/crypto/crypto_int_test.go b/src/restic/crypto/crypto_int_test.go index 88c7b1fa7..db4b791b2 100644 --- a/src/restic/crypto/crypto_int_test.go +++ b/src/restic/crypto/crypto_int_test.go @@ -90,18 +90,18 @@ func TestCrypto(t *testing.T) { for _, tv := range testValues { // test encryption k := &Key{ - Encrypt: tv.ekey, - MAC: tv.skey, + EncryptionKey: tv.ekey, + MACKey: tv.skey, } - msg, err := Encrypt(k, msg, tv.plaintext) + msg, err := k.Encrypt(msg, tv.plaintext) if err != nil { t.Fatal(err) } // decrypt message buf := make([]byte, len(tv.plaintext)) - n, err := Decrypt(k, buf, msg) + n, err := k.Decrypt(buf, msg) if err != nil { t.Fatal(err) } @@ -110,7 +110,7 @@ func TestCrypto(t *testing.T) { // change mac, this must fail msg[len(msg)-8] ^= 0x23 - if _, err = Decrypt(k, buf, msg); err != ErrUnauthenticated { + if _, err = k.Decrypt(buf, msg); err != ErrUnauthenticated { t.Fatal("wrong MAC value not detected") } @@ -120,13 +120,13 @@ func TestCrypto(t *testing.T) { // tamper with message, this must fail msg[16+5] ^= 0x85 - if _, err = Decrypt(k, buf, msg); err != ErrUnauthenticated { + if _, err = k.Decrypt(buf, msg); err != ErrUnauthenticated { t.Fatal("tampered message not detected") } // test decryption p := make([]byte, len(tv.ciphertext)) - n, err = Decrypt(k, p, tv.ciphertext) + n, err = k.Decrypt(p, tv.ciphertext) if err != nil { t.Fatal(err) } diff --git a/src/restic/crypto/crypto_test.go b/src/restic/crypto/crypto_test.go index 39c3cc169..4ebc7a0d8 100644 --- a/src/restic/crypto/crypto_test.go +++ b/src/restic/crypto/crypto_test.go @@ -26,14 +26,14 @@ func TestEncryptDecrypt(t *testing.T) { data := Random(42, size) buf := make([]byte, size+crypto.Extension) - ciphertext, err := crypto.Encrypt(k, buf, data) + ciphertext, err := k.Encrypt(buf, data) OK(t, err) Assert(t, len(ciphertext) == len(data)+crypto.Extension, "ciphertext length does not match: want %d, got %d", len(data)+crypto.Extension, len(ciphertext)) plaintext := make([]byte, len(ciphertext)) - n, err := crypto.Decrypt(k, plaintext, ciphertext) + n, err := k.Decrypt(plaintext, ciphertext) OK(t, err) plaintext = plaintext[:n] Assert(t, len(plaintext) == len(data), @@ -53,7 +53,7 @@ func TestSmallBuffer(t *testing.T) { OK(t, err) ciphertext := make([]byte, size/2) - ciphertext, err = crypto.Encrypt(k, ciphertext, data) + ciphertext, err = k.Encrypt(ciphertext, data) // this must extend the slice Assert(t, cap(ciphertext) > size/2, "expected extended slice, but capacity is only %d bytes", @@ -61,7 +61,7 @@ func TestSmallBuffer(t *testing.T) { // check for the correct plaintext plaintext := make([]byte, len(ciphertext)) - n, err := crypto.Decrypt(k, plaintext, ciphertext) + n, err := k.Decrypt(plaintext, ciphertext) OK(t, err) plaintext = plaintext[:n] Assert(t, bytes.Equal(plaintext, data), @@ -78,11 +78,11 @@ func TestSameBuffer(t *testing.T) { ciphertext := make([]byte, 0, size+crypto.Extension) - ciphertext, err = crypto.Encrypt(k, ciphertext, data) + ciphertext, err = k.Encrypt(ciphertext, data) OK(t, err) // use the same buffer for decryption - n, err := crypto.Decrypt(k, ciphertext, ciphertext) + n, err := k.Decrypt(ciphertext, ciphertext) OK(t, err) ciphertext = ciphertext[:n] Assert(t, bytes.Equal(ciphertext, data), @@ -94,7 +94,7 @@ func TestCornerCases(t *testing.T) { // nil plaintext should encrypt to the empty string // nil ciphertext should allocate a new slice for the ciphertext - c, err := crypto.Encrypt(k, nil, nil) + c, err := k.Encrypt(nil, nil) OK(t, err) Assert(t, len(c) == crypto.Extension, @@ -102,12 +102,12 @@ func TestCornerCases(t *testing.T) { len(c)) // this should decrypt to nil - n, err := crypto.Decrypt(k, nil, c) + n, err := k.Decrypt(nil, c) OK(t, err) Equals(t, 0, n) // test encryption for same slice, this should return an error - _, err = crypto.Encrypt(k, c, c) + _, err = k.Encrypt(c, c) Equals(t, crypto.ErrInvalidCiphertext, err) } @@ -123,10 +123,10 @@ func TestLargeEncrypt(t *testing.T) { _, err := io.ReadFull(rand.Reader, data) OK(t, err) - ciphertext, err := crypto.Encrypt(k, make([]byte, size+crypto.Extension), data) + ciphertext, err := k.Encrypt(make([]byte, size+crypto.Extension), data) OK(t, err) - plaintext, err := crypto.Decrypt(k, []byte{}, ciphertext) + plaintext, err := k.Decrypt([]byte{}, ciphertext) OK(t, err) Equals(t, plaintext, data) @@ -144,7 +144,7 @@ func BenchmarkEncrypt(b *testing.B) { b.SetBytes(int64(size)) for i := 0; i < b.N; i++ { - _, err := crypto.Encrypt(k, buf, data) + _, err := k.Encrypt(buf, data) OK(b, err) } } @@ -158,14 +158,14 @@ func BenchmarkDecrypt(b *testing.B) { plaintext := make([]byte, size) ciphertext := make([]byte, size+crypto.Extension) - ciphertext, err := crypto.Encrypt(k, ciphertext, data) + ciphertext, err := k.Encrypt(ciphertext, data) OK(b, err) b.ResetTimer() b.SetBytes(int64(size)) for i := 0; i < b.N; i++ { - _, err = crypto.Decrypt(k, plaintext, ciphertext) + _, err = k.Decrypt(plaintext, ciphertext) OK(b, err) } } diff --git a/src/restic/crypto/kdf.go b/src/restic/crypto/kdf.go index 158f462f1..4b863037b 100644 --- a/src/restic/crypto/kdf.go +++ b/src/restic/crypto/kdf.go @@ -81,10 +81,10 @@ func KDF(p KDFParams, salt []byte, password string) (*Key, error) { } // first 32 byte of scrypt output is the encryption key - copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize]) + copy(derKeys.EncryptionKey[:], scryptKeys[:aesKeySize]) // next 32 byte of scrypt output is the mac key, in the form k||r - macKeyFromSlice(&derKeys.MAC, scryptKeys[aesKeySize:]) + macKeyFromSlice(&derKeys.MACKey, scryptKeys[aesKeySize:]) return derKeys, nil } diff --git a/src/restic/pack/pack.go b/src/restic/pack/pack.go index a7965f97a..6d497548d 100644 --- a/src/restic/pack/pack.go +++ b/src/restic/pack/pack.go @@ -75,7 +75,7 @@ func (p *Packer) Finalize() (uint, error) { return 0, err } - encryptedHeader, err := crypto.Encrypt(p.k, nil, hdrBuf.Bytes()) + encryptedHeader, err := p.k.Encrypt(nil, hdrBuf.Bytes()) if err != nil { return 0, err } @@ -266,7 +266,7 @@ func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries []restic.Blob, err return nil, err } - n, err := crypto.Decrypt(k, buf, buf) + n, err := k.Decrypt(buf, buf) if err != nil { return nil, err } diff --git a/src/restic/repository/key.go b/src/restic/repository/key.go index 68714db6c..827a1631e 100644 --- a/src/restic/repository/key.go +++ b/src/restic/repository/key.go @@ -88,7 +88,7 @@ func OpenKey(ctx context.Context, s *Repository, name string, password string) ( // decrypt master keys buf := make([]byte, len(k.Data)) - n, err := crypto.Decrypt(k.user, buf, k.Data) + n, err := k.user.Decrypt(buf, k.Data) if err != nil { return nil, err } @@ -218,7 +218,7 @@ func AddKey(ctx context.Context, s *Repository, password string, template *crypt return nil, errors.Wrap(err, "Marshal") } - newkey.Data, err = crypto.Encrypt(newkey.user, nil, buf) + newkey.Data, err = newkey.user.Encrypt(nil, buf) // dump as json buf, err = json.Marshal(newkey) diff --git a/src/restic/repository/repack.go b/src/restic/repository/repack.go index e613d8da2..b010f2c7f 100644 --- a/src/restic/repository/repack.go +++ b/src/restic/repository/repack.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "io" "restic" - "restic/crypto" "restic/debug" "restic/fs" "restic/hashing" @@ -88,7 +87,7 @@ func Repack(ctx context.Context, repo restic.Repository, packs restic.IDSet, kee h, tempfile.Name(), len(buf), n) } - n, err = crypto.Decrypt(repo.Key(), buf, buf) + n, err = repo.Key().Decrypt(buf, buf) if err != nil { return nil, err } diff --git a/src/restic/repository/repository.go b/src/restic/repository/repository.go index 1c158be88..8f037c917 100644 --- a/src/restic/repository/repository.go +++ b/src/restic/repository/repository.go @@ -420,7 +420,7 @@ func (r *Repository) decryptTo(plaintext, ciphertext []byte) (int, error) { return 0, errors.New("key for repository not set") } - return crypto.Decrypt(r.key, plaintext, ciphertext) + return r.key.Decrypt(plaintext, ciphertext) } // Encrypt encrypts and authenticates the plaintext and saves the result in @@ -430,7 +430,7 @@ func (r *Repository) Encrypt(ciphertext, plaintext []byte) ([]byte, error) { return nil, errors.New("key for repository not set") } - return crypto.Encrypt(r.key, ciphertext, plaintext) + return r.key.Encrypt(ciphertext, plaintext) } // Key returns the current master key.