mirror of https://github.com/restic/restic.git
Merge remote-tracking branch 'fw42/crypto_nitpicks'
This commit is contained in:
commit
dbc41bb805
118
crypto/crypto.go
118
crypto/crypto.go
|
@ -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
|
// prepare key for low-level poly1305.Sum(): r||n
|
||||||
func poly1305Verify(msg []byte, nonce []byte, key *SigningKey, mac []byte) bool {
|
func poly1305PrepareKey(nonce []byte, key *MACKey) [32]byte {
|
||||||
// prepare key for low-level poly1305.Sum(): r||n
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue