2
2
mirror of https://github.com/octoleo/restic.git synced 2024-12-23 19:38:57 +00:00

Merge remote-tracking branch 'fw42/crypto_nitpicks'

This commit is contained in:
Alexander Neumann 2015-05-01 17:15:58 +02:00
commit dbc41bb805
8 changed files with 128 additions and 156 deletions

View File

@ -14,13 +14,13 @@ import (
) )
const ( const (
aesKeySize = 32 // for AES256 aesKeySize = 32 // for AES-256
macKeySizeK = 16 // for AES-128 macKeySizeK = 16 // for AES-128
macKeySizeR = 16 // for Poly1305 macKeySizeR = 16 // for Poly1305
macKeySize = macKeySizeK + macKeySizeR // for Poly1305-AES128 macKeySize = macKeySizeK + macKeySizeR // for Poly1305-AES128
ivSize = aes.BlockSize ivSize = aes.BlockSize
macSize = poly1305.TagSize // Poly1305 size is 16 byte macSize = poly1305.TagSize
Extension = ivSize + macSize Extension = ivSize + macSize
) )
@ -33,22 +33,22 @@ var (
ErrBufferTooSmall = errors.New("destination buffer too small") ErrBufferTooSmall = errors.New("destination buffer too small")
) )
// Key holds signing and encryption keys for a repository. It is stored // Key holds encryption and message authentication keys for a repository. It is stored
// encrypted and signed as a JSON data structure in the Data field of the Key // 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 // structure. For the master key, the secret random polynomial used for content
// defined chunking is included. // defined chunking is included.
type Key struct { type Key struct {
Sign SigningKey `json:"sign"` MAC MACKey `json:"mac"`
Encrypt EncryptionKey `json:"encrypt"` Encrypt EncryptionKey `json:"encrypt"`
ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"` ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"`
} }
type EncryptionKey [32]byte type EncryptionKey [32]byte
type SigningKey struct { type MACKey struct {
K [16]byte `json:"k"` // for AES128 K [16]byte // for AES-128
R [16]byte `json:"r"` // for Poly1305 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) // 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 0x0f, // 15: top four bits zero
} }
// key is a [32]byte, in the form k||r func poly1305MAC(msg []byte, nonce []byte, key *MACKey) []byte {
func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte { k := poly1305PrepareKey(nonce, key)
// prepare key for low-level poly1305.Sum(): r||n
var k [32]byte
// 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 var out [16]byte
poly1305.Sum(&out, msg, &k) poly1305.Sum(&out, msg, &k)
@ -99,10 +81,11 @@ func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte {
} }
// mask poly1305 key // mask poly1305 key
func maskKey(k *SigningKey) { func maskKey(k *MACKey) {
if k == nil { if k == nil || k.masked {
return return
} }
for i := 0; i < poly1305.TagSize; i++ { for i := 0; i < poly1305.TagSize; i++ {
k.R[i] = k.R[i] & poly1305KeyMask[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 // 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.K[:], data[:16])
copy(mk.R[:], data[16:32]) copy(mk.R[:], data[16:32])
maskKey(mk) 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 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[:]) cipher, err := aes.NewCipher(key.K[:])
if err != nil { if err != nil {
panic(err) panic(err)
} }
cipher.Encrypt(k[16:], nonce[:]) cipher.Encrypt(k[16:], nonce[:])
// copy r
copy(k[:16], key.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 var m [16]byte
copy(m[:], mac) copy(m[:], mac)
return poly1305.Verify(&m, msg, &k) return poly1305.Verify(&m, msg, &k)
} }
// NewKey returns new encryption and signing keys. // NewRandomKey returns new encryption and message authentication keys.
func NewKey() (k *Key) { func NewRandomKey() *Key {
k = &Key{} k := &Key{}
n, err := rand.Read(k.Encrypt[:]) n, err := rand.Read(k.Encrypt[:])
if n != aesKeySize || err != nil { if n != aesKeySize || err != nil {
panic("unable to read enough random bytes for encryption 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 { 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 { 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 return k
} }
@ -181,11 +163,11 @@ type jsonMACKey struct {
R []byte `json:"r"` 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[:]}) 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{} j := jsonMACKey{}
err := json.Unmarshal(data, &j) err := json.Unmarshal(data, &j)
if err != nil { if err != nil {
@ -216,11 +198,11 @@ func (k *EncryptionKey) UnmarshalJSON(data []byte) error {
// 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 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 // MAC. Encrypt returns the new ciphertext slice, which is extended when
// necessary. ciphertext and plaintext may not point to (exactly) the same // necessary. ciphertext and plaintext may not point to (exactly) the same
// slice or non-intersecting slices. // 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)] ciphertext = ciphertext[:cap(ciphertext)]
// test for same slice, if possible // test for same slice, if possible
@ -245,11 +227,11 @@ func Encrypt(ks *Key, ciphertext, plaintext []byte) ([]byte, error) {
e.XORKeyStream(ciphertext[ivSize:], plaintext) e.XORKeyStream(ciphertext[ivSize:], plaintext)
copy(ciphertext, iv[:]) copy(ciphertext, iv[:])
// truncate to only conver iv and actual ciphertext
// truncate to only cover iv and actual ciphertext
ciphertext = ciphertext[:ivSize+len(plaintext)] ciphertext = ciphertext[:ivSize+len(plaintext)]
mac := poly1305Sign(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign) mac := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &ks.MAC)
// append the mac tag
ciphertext = append(ciphertext, mac...) ciphertext = append(ciphertext, mac...)
return ciphertext, nil 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 // Decrypt verifies and decrypts the ciphertext. Ciphertext must be in the form
// IV || Ciphertext || MAC. plaintext and ciphertext may point to (exactly) the // IV || Ciphertext || MAC. plaintext and ciphertext may point to (exactly) the
// same slice. // 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 // check for plausible length
if len(ciphertext) < ivSize+macSize { if len(ciphertextWithMac) < ivSize+macSize {
panic("trying to decrypt invalid data: ciphertext too small") panic("trying to decrypt invalid data: ciphertext too small")
} }
if cap(plaintext) < len(ciphertext) { if cap(plaintext) < len(ciphertextWithMac) {
// extend plaintext // extend plaintext
plaintext = append(plaintext, make([]byte, len(ciphertext)-cap(plaintext))...) plaintext = append(plaintext, make([]byte, len(ciphertextWithMac)-cap(plaintext))...)
} }
// extract mac // extract mac
l := len(ciphertext) - macSize l := len(ciphertextWithMac) - macSize
ciphertext, mac := ciphertext[:l], ciphertext[l:] ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:]
// verify mac // verify mac
if !poly1305Verify(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign, mac) { if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.MAC, mac) {
return nil, ErrUnauthenticated return nil, ErrUnauthenticated
} }
// extract iv // extract iv
iv, ciphertext := ciphertext[:ivSize], ciphertext[ivSize:] iv, ciphertext := ciphertextWithIV[:ivSize], ciphertextWithIV[ivSize:]
// decrypt data // decrypt data
c, err := aes.NewCipher(ks.Encrypt[:]) c, err := aes.NewCipher(ks.Encrypt[:])
@ -295,8 +277,8 @@ func Decrypt(ks *Key, plaintext, ciphertext []byte) ([]byte, error) {
return plaintext, nil return plaintext, nil
} }
// KDF derives encryption and signing keys from the password using the supplied // KDF derives encryption and message authentication keys from the password
// parameters N, R and P and the Salt. // using the supplied parameters N, R and P and the Salt.
func KDF(N, R, P int, salt []byte, password string) (*Key, error) { func KDF(N, R, P int, salt []byte, password string) (*Key, error) {
if len(salt) == 0 { if len(salt) == 0 {
return nil, fmt.Errorf("scrypt() called with empty salt") 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]) copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize])
// next 32 byte of scrypt output is the mac key, in the form k||r // 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 return derKeys, nil
} }

View File

@ -45,10 +45,10 @@ var poly1305_tests = []struct {
func TestPoly1305(t *testing.T) { func TestPoly1305(t *testing.T) {
for _, test := range poly1305_tests { for _, test := range poly1305_tests {
key := &SigningKey{} key := &MACKey{}
copy(key.K[:], test.k) copy(key.K[:], test.k)
copy(key.R[:], test.r) 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) { if !bytes.Equal(mac, test.mac) {
t.Fatalf("wrong mac calculated, want: %02x, got: %02x", test.mac, mac) t.Fatalf("wrong mac calculated, want: %02x, got: %02x", test.mac, mac)
@ -62,15 +62,16 @@ func TestPoly1305(t *testing.T) {
var testValues = []struct { var testValues = []struct {
ekey EncryptionKey ekey EncryptionKey
skey SigningKey skey MACKey
ciphertext []byte ciphertext []byte
plaintext []byte plaintext []byte
shouldPanic bool
}{ }{
{ {
ekey: EncryptionKey([...]byte{0x30, 0x3e, 0x86, 0x87, 0xb1, 0xd7, 0xdb, 0x18, 0x42, 0x1b, 0xdc, 0x6b, 0xb8, 0x58, 0x8c, 0xca, ekey: EncryptionKey([...]byte{
0xda, 0xc4, 0xd5, 0x9e, 0xe8, 0x7b, 0x8f, 0xf7, 0x0c, 0x44, 0xe6, 0x35, 0x79, 0x0c, 0xaf, 0xef}), 0x30, 0x3e, 0x86, 0x87, 0xb1, 0xd7, 0xdb, 0x18, 0x42, 0x1b, 0xdc, 0x6b, 0xb8, 0x58, 0x8c, 0xca,
skey: SigningKey{ 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}, 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}, 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 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) { func TestCrypto(t *testing.T) {
msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now msg := make([]byte, 0, 8*1024*1024) // use 8MiB for now
for _, tv := range testValues { for _, tv := range testValues {
// test encryption // test encryption
k := &Key{ k := &Key{
Encrypt: tv.ekey, Encrypt: tv.ekey,
Sign: tv.skey, MAC: tv.skey,
} }
msg, err := Encrypt(k, msg, tv.plaintext) msg, err := Encrypt(k, msg, tv.plaintext)

View File

@ -17,7 +17,7 @@ import (
var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads") var testLargeCrypto = flag.Bool("test.largecrypto", false, "also test crypto functions with large payloads")
func TestEncryptDecrypt(t *testing.T) { func TestEncryptDecrypt(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
tests := []int{5, 23, 2<<18 + 23, 1 << 20} tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto { if *testLargeCrypto {
@ -48,7 +48,7 @@ func TestEncryptDecrypt(t *testing.T) {
} }
func TestSmallBuffer(t *testing.T) { func TestSmallBuffer(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
size := 600 size := 600
data := make([]byte, size) data := make([]byte, size)
@ -73,7 +73,7 @@ func TestSmallBuffer(t *testing.T) {
} }
func TestSameBuffer(t *testing.T) { func TestSameBuffer(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
size := 600 size := 600
data := make([]byte, size) data := make([]byte, size)
@ -96,7 +96,7 @@ func TestSameBuffer(t *testing.T) {
} }
func TestCornerCases(t *testing.T) { func TestCornerCases(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
// nil plaintext should encrypt to the empty string // nil plaintext should encrypt to the empty string
// nil ciphertext should allocate a new slice for the ciphertext // nil ciphertext should allocate a new slice for the ciphertext
@ -122,7 +122,7 @@ func TestLargeEncrypt(t *testing.T) {
t.SkipNow() t.SkipNow()
} }
k := crypto.NewKey() k := crypto.NewRandomKey()
for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} { for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} {
data := make([]byte, size) data := make([]byte, size)
@ -146,7 +146,7 @@ func BenchmarkEncryptWriter(b *testing.B) {
size := 8 << 20 // 8MiB size := 8 << 20 // 8MiB
rd := RandomReader(23, size) rd := RandomReader(23, size)
k := crypto.NewKey() k := crypto.NewRandomKey()
b.ResetTimer() b.ResetTimer()
b.SetBytes(int64(size)) b.SetBytes(int64(size))
@ -166,7 +166,7 @@ func BenchmarkEncrypt(b *testing.B) {
size := 8 << 20 // 8MiB size := 8 << 20 // 8MiB
data := make([]byte, size) data := make([]byte, size)
k := crypto.NewKey() k := crypto.NewRandomKey()
buf := make([]byte, len(data)+crypto.Extension) buf := make([]byte, len(data)+crypto.Extension)
b.ResetTimer() b.ResetTimer()
@ -181,7 +181,7 @@ func BenchmarkEncrypt(b *testing.B) {
func BenchmarkDecryptReader(b *testing.B) { func BenchmarkDecryptReader(b *testing.B) {
size := 8 << 20 // 8MiB size := 8 << 20 // 8MiB
buf := Random(23, size) buf := Random(23, size)
k := crypto.NewKey() k := crypto.NewRandomKey()
ciphertext := make([]byte, len(buf)+crypto.Extension) ciphertext := make([]byte, len(buf)+crypto.Extension)
_, err := crypto.Encrypt(k, ciphertext, buf) _, err := crypto.Encrypt(k, ciphertext, buf)
@ -203,7 +203,7 @@ func BenchmarkDecryptReader(b *testing.B) {
} }
func BenchmarkEncryptDecryptReader(b *testing.B) { func BenchmarkEncryptDecryptReader(b *testing.B) {
k := crypto.NewKey() k := crypto.NewRandomKey()
size := 8 << 20 // 8MiB size := 8 << 20 // 8MiB
rd := RandomReader(23, size) rd := RandomReader(23, size)
@ -234,7 +234,7 @@ func BenchmarkDecrypt(b *testing.B) {
size := 8 << 20 // 8MiB size := 8 << 20 // 8MiB
data := make([]byte, size) data := make([]byte, size)
k := crypto.NewKey() k := crypto.NewRandomKey()
ciphertext := restic.GetChunkBuf("BenchmarkDecrypt") ciphertext := restic.GetChunkBuf("BenchmarkDecrypt")
defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext) defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext)
@ -254,7 +254,7 @@ func BenchmarkDecrypt(b *testing.B) {
} }
func TestEncryptStreamWriter(t *testing.T) { func TestEncryptStreamWriter(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
tests := []int{5, 23, 2<<18 + 23, 1 << 20} tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto { if *testLargeCrypto {
@ -288,7 +288,7 @@ func TestEncryptStreamWriter(t *testing.T) {
} }
func TestDecryptStreamReader(t *testing.T) { func TestDecryptStreamReader(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
tests := []int{5, 23, 2<<18 + 23, 1 << 20} tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto { if *testLargeCrypto {
@ -322,7 +322,7 @@ func TestDecryptStreamReader(t *testing.T) {
} }
func TestEncryptWriter(t *testing.T) { func TestEncryptWriter(t *testing.T) {
k := crypto.NewKey() k := crypto.NewRandomKey()
tests := []int{5, 23, 2<<18 + 23, 1 << 20} tests := []int{5, 23, 2<<18 + 23, 1 << 20}
if *testLargeCrypto { if *testLargeCrypto {

View File

@ -77,7 +77,6 @@ func DecryptFrom(ks *Key, rd io.Reader) (io.ReadCloser, error) {
ciphertext := buf.Bytes() ciphertext := buf.Bytes()
// decrypt
ciphertext, err = Decrypt(ks, ciphertext, ciphertext) ciphertext, err = Decrypt(ks, ciphertext, ciphertext)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -27,7 +27,7 @@ func (e *encryptWriter) Close() error {
e.s.XORKeyStream(c, c) e.s.XORKeyStream(c, c)
// compute mac // compute mac
mac := poly1305Sign(c, iv, &e.key.Sign) mac := poly1305MAC(c, iv, &e.key.MAC)
e.data = append(e.data, mac...) e.data = append(e.data, mac...)
// write everything // write everything

View File

@ -53,11 +53,12 @@ complete filename.
Apart from the files `version`, `id` and the files stored below the `keys` 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 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 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 stored. It is followed by the encrypted data and completed by the 16 byte
signature. The format is: `IV || CIPHERTEXT || MAC`. The complete encryption MAC. The format is: `IV || CIPHERTEXT || MAC`. The complete encryption
overhead is 32 byte. For each file, a new random IV is selected. overhead is 32 byte. For each file, a new random IV is selected.
The basic layout of a sample restic repository is shown below: 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 EncryptedBlob1 || ... || EncryptedBlobN || EncryptedHeader || Header_Length
At the end of the Pack is a header, which describes the content and is At the end of the Pack is a header, which describes the content. The header is
encrypted and signed. `Header_Length` is the length of the encrypted header 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 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 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 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 having to re-write a file once the pack is complete and the content and length
of the header is known. 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 encrypted independently. This enables repository reorganisation without having
to touch the encrypted Blobs. In addition it also allows efficient indexing, 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 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 contained in the Pack. Since the header is authenticated, authenticity of the
can be checked without having to read the complete Pack. header can be checked without having to read the complete Pack.
After decryption, a Pack's header consists of the following elements: 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 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 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 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 used to reconstruct the index. The files are encrypted and authenticated like
Tree Blobs, so the outer structure is `IV || Ciphertext || MAC` again. The Data and Tree Blobs, so the outer structure is `IV || Ciphertext || MAC` again.
plaintext consists of a JSON document like the following: The plaintext consists of a JSON document like the following:
[ { [ {
"id": "73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c", "id": "73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c",
@ -183,21 +184,22 @@ Keys, Encryption and MAC
------------------------ ------------------------
All data stored by restic in the repository is encrypted with AES-256 in 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 counter mode and authenticated using Poly1305-AES. For encrypting new data first
bytes are read from a cryptographically secure pseudorandom number generator as 16 bytes are read from a cryptographically secure pseudorandom number generator
a random nonce. This is used both as the IV for counter mode and the nonce for as a random nonce. This is used both as the IV for counter mode and the nonce
Poly1305. This operation needs three keys: A 32 byte for AES-256 for 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 encryption, a 16 byte AES key and a 16 byte key for Poly1305. For details see
the original paper [The Poly1305-AES message-authentication the original paper [The Poly1305-AES message-authentication
code](http://cr.yp.to/mac/poly1305-20050329.pdf) by Dan Bernstein. 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 data is then encrypted with AES-256 and afterwards a message authentication
the ciphertext, everything is then stored as IV || CIPHERTEXT || MAC. 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 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 contain all data that is needed to derive the repository's master encryption and
encryption keys from a user's password. The JSON document from the repository message authentication keys from a user's password. The JSON document from the
can be pretty-printed for example by using the Python module `json` (shortened repository can be pretty-printed for example by using the Python module `json`
to increase readability): (shortened to increase readability):
$ python -mjson.tool /tmp/restic-repo/keys/b02de82* $ 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 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 (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 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 last 32 bytes are used as the message authentication key (for Poly1305-AES).
bytes are divided into a 16 byte AES key `k` followed by 16 bytes of secret key These last 32 bytes are divided into a 16 byte AES key `k` followed by 16 bytes
`r`. They key `r` is then masked for use with Poly1305 (see the paper for of secret key `r`. They key `r` is then masked for use with Poly1305 (see the
details). paper for details).
This signing key is used to compute a MAC over the bytes contained in the This message authentication key is used to compute a MAC over the bytes contained
JSON field `data` (after removing the Base64 encoding and not including the in the JSON field `data` (after removing the Base64 encoding and not including
last 32 byte). If the password is incorrect or the key file has been tampered the last 32 byte). If the password is incorrect or the key file has been
with, the computed MAC will not match the last 16 bytes of the data, and tampered with, the computed MAC will not match the last 16 bytes of the data,
restic exits with an error. Otherwise, the data is decrypted with the and restic exits with an error. Otherwise, the data is decrypted with the
encryption key derived from `scrypt`. This yields a JSON document which encryption key derived from `scrypt`. This yields a JSON document which
contains the master signing and encryption keys for this repository (encoded in contains the master encryption and message authentication keys for this
Base64) and the polynomial that is used for CDC. The command `restic cat repository (encoded in Base64) and the polynomial that is used for CDC. The
masterkey` can be used as follows to decrypt and pretty-print the master key: command `restic cat masterkey` can be used as follows to decrypt and
pretty-print the master key:
$ restic -r /tmp/restic-repo cat masterkey $ restic -r /tmp/restic-repo cat masterkey
{ {
"sign": { "mac": {
"k": "evFWd9wWlndL9jc501268g==", "k": "evFWd9wWlndL9jc501268g==",
"r": "E9eEDnSJZgqwTOkDtOp+Dw==" "r": "E9eEDnSJZgqwTOkDtOp+Dw=="
}, },
@ -241,8 +244,9 @@ masterkey` can be used as follows to decrypt and pretty-print the master key:
"chunker_polynomial": "2f0797d9c2363f" "chunker_polynomial": "2f0797d9c2363f"
} }
All data in the repository is encrypted and signed with these master keys with All data in the repository is encrypted and authenticated with these master keys.
AES-256 in Counter mode and signed with Poly1305-AES as described above. 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. 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. 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 * Accessing the unencrypted content of stored files and meta data should not
be possible without a password for the repository. Everything except the be possible without a password for the repository. Everything except the
`version` and `id` files and the meta data included for informational `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 * Modifications (intentional or unintentional) can be detected automatically
on several layers: 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 file's name). This way, modifications (bad RAM, broken harddisk) can be
detected easily. detected easily.
2. Before decrypting any data, the MAC signature on the encrypted data is 2. Before decrypting any data, the MAC on the encrypted data is
checked. If there has been a modification, the signature check will checked. If there has been a modification, the MAC check will
fail. This step happens even before the data is decrypted, so data that fail. This step happens even before the data is decrypted, so data that
has been tampered with is not decrypted at all. has been tampered with is not decrypted at all.

View File

@ -37,7 +37,7 @@ func TestCreatePack(t *testing.T) {
file := bytes.NewBuffer(nil) file := bytes.NewBuffer(nil)
// create random keys // create random keys
k := crypto.NewKey() k := crypto.NewRandomKey()
// pack blobs // pack blobs
p := pack.NewPacker(k, file) p := pack.NewPacker(k, file)

View File

@ -176,7 +176,7 @@ func AddKey(s *Server, password string, template *Key) (*Key, error) {
if template == nil { if template == nil {
// generate new random master keys // generate new random master keys
newkey.master = crypto.NewKey() newkey.master = crypto.NewRandomKey()
// generate random polynomial for cdc // generate random polynomial for cdc
p, err := chunker.RandomPolynomial() p, err := chunker.RandomPolynomial()
if err != nil { if err != nil {
@ -229,16 +229,15 @@ func AddKey(s *Server, password string, template *Key) (*Key, error) {
return newkey, nil return newkey, nil
} }
// Encrypt encrypts and signs data with the master key. Stored in ciphertext is // Encrypt encrypts and authenticates data with the master key. Stored in
// IV || Ciphertext || MAC. Returns the ciphertext, which is extended if // ciphertext is IV || Ciphertext || MAC. Returns the ciphertext, which is
// necessary. // extended if necessary.
func (k *Key) Encrypt(ciphertext, plaintext []byte) ([]byte, error) { func (k *Key) Encrypt(ciphertext, plaintext []byte) ([]byte, error) {
return crypto.Encrypt(k.master, ciphertext, plaintext) return crypto.Encrypt(k.master, ciphertext, plaintext)
} }
// EncryptTo encrypts and signs data with the master key. The returned // EncryptTo encrypts and authenticates data with the master key. The returned
// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is // io.Writer writes IV || Ciphertext || MAC.
// used.
func (k *Key) EncryptTo(wr io.Writer) io.WriteCloser { func (k *Key) EncryptTo(wr io.Writer) io.WriteCloser {
return crypto.EncryptTo(k.master, wr) 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 || // available on the returned Reader. Ciphertext must be in the form IV ||
// Ciphertext || MAC. In order to correctly verify the ciphertext, rd is // Ciphertext || MAC. In order to correctly verify the ciphertext, rd is
// drained, locally buffered and made available on the returned Reader // 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. // immediately.
func (k *Key) DecryptFrom(rd io.Reader) (io.ReadCloser, error) { func (k *Key) DecryptFrom(rd io.Reader) (io.ReadCloser, error) {
return crypto.DecryptFrom(k.master, rd) return crypto.DecryptFrom(k.master, rd)