consistently rename signature to mac

This commit is contained in:
Florian Weingarten 2015-04-30 12:09:08 -04:00
parent c500b94216
commit 90e6a8eb86
5 changed files with 79 additions and 76 deletions

View File

@ -33,22 +33,22 @@ var (
ErrBufferTooSmall = errors.New("destination buffer too small")
)
// Key holds signing and encryption keys for a repository. It is stored
// encrypted and signed as a JSON data structure in the Data field of the Key
// Key holds encryption and message authentication keys for a repository. It is stored
// 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
// defined chunking is included.
type Key struct {
Sign SigningKey `json:"sign"`
MAC MACKey `json:"mac"`
Encrypt EncryptionKey `json:"encrypt"`
ChunkerPolynomial chunker.Pol `json:"chunker_polynomial,omitempty"`
}
type EncryptionKey [32]byte
type SigningKey struct {
type MACKey struct {
K [16]byte // for AES-128
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)
@ -71,7 +71,7 @@ var poly1305KeyMask = [16]byte{
0x0f, // 15: top four bits zero
}
func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte {
func poly1305MAC(msg []byte, nonce []byte, key *MACKey) []byte {
k := poly1305PrepareKey(nonce, key)
var out [16]byte
@ -81,7 +81,7 @@ func poly1305Sign(msg []byte, nonce []byte, key *SigningKey) []byte {
}
// mask poly1305 key
func maskKey(k *SigningKey) {
func maskKey(k *MACKey) {
if k == nil || k.masked {
return
}
@ -94,14 +94,14 @@ func maskKey(k *SigningKey) {
}
// 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.R[:], data[16:32])
maskKey(mk)
}
// prepare key for low-level poly1305.Sum(): r||n
func poly1305PrepareKey(nonce []byte, key *SigningKey) [32]byte {
func poly1305PrepareKey(nonce []byte, key *MACKey) [32]byte {
var k [32]byte
maskKey(key)
@ -117,7 +117,7 @@ func poly1305PrepareKey(nonce []byte, key *SigningKey) [32]byte {
return k
}
func poly1305Verify(msg []byte, nonce []byte, key *SigningKey, mac []byte) bool {
func poly1305Verify(msg []byte, nonce []byte, key *MACKey, mac []byte) bool {
k := poly1305PrepareKey(nonce, key)
var m [16]byte
@ -126,7 +126,7 @@ func poly1305Verify(msg []byte, nonce []byte, key *SigningKey, mac []byte) bool
return poly1305.Verify(&m, msg, &k)
}
// NewRandomKey returns new encryption and signing keys.
// NewRandomKey returns new encryption and message authentication keys.
func NewRandomKey() *Key {
k := &Key{}
@ -135,17 +135,17 @@ func NewRandomKey() *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 {
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 {
panic("unable to read enough random bytes for mac signing key")
panic("unable to read enough random bytes for MAC key")
}
maskKey(&k.Sign)
maskKey(&k.MAC)
return k
}
@ -163,11 +163,11 @@ type jsonMACKey struct {
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[:]})
}
func (m *SigningKey) UnmarshalJSON(data []byte) error {
func (m *MACKey) UnmarshalJSON(data []byte) error {
j := jsonMACKey{}
err := json.Unmarshal(data, &j)
if err != nil {
@ -198,7 +198,7 @@ func (k *EncryptionKey) UnmarshalJSON(data []byte) error {
// holds the 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
// necessary. ciphertext and plaintext may not point to (exactly) the same
// slice or non-intersecting slices.
@ -231,7 +231,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 := poly1305Sign(ciphertext[ivSize:], ciphertext[:ivSize], &ks.Sign)
mac := poly1305MAC(ciphertext[ivSize:], ciphertext[:ivSize], &ks.MAC)
ciphertext = append(ciphertext, mac...)
return ciphertext, nil
@ -256,7 +256,7 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error
ciphertextWithIV, mac := ciphertextWithMac[:l], ciphertextWithMac[l:]
// verify mac
if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.Sign, mac) {
if !poly1305Verify(ciphertextWithIV[ivSize:], ciphertextWithIV[:ivSize], &ks.MAC, mac) {
return nil, ErrUnauthenticated
}
@ -277,8 +277,8 @@ func Decrypt(ks *Key, plaintext []byte, ciphertextWithMac []byte) ([]byte, error
return plaintext, nil
}
// KDF derives encryption and signing keys from the password using the supplied
// parameters N, R and P and the Salt.
// KDF derives encryption and message authentication keys from the password
// using the supplied parameters N, R and P and the Salt.
func KDF(N, R, P int, salt []byte, password string) (*Key, error) {
if len(salt) == 0 {
return nil, fmt.Errorf("scrypt() called with empty salt")
@ -300,7 +300,7 @@ func KDF(N, R, P int, salt []byte, password string) (*Key, error) {
copy(derKeys.Encrypt[:], scryptKeys[:aesKeySize])
// 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
}

View File

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

View File

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

View File

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

View File

@ -229,16 +229,15 @@ func AddKey(s *Server, password string, template *Key) (*Key, error) {
return newkey, nil
}
// Encrypt encrypts and signs data with the master key. Stored in ciphertext is
// IV || Ciphertext || MAC. Returns the ciphertext, which is extended if
// necessary.
// Encrypt encrypts and authenticates data with the master key. Stored in
// ciphertext is IV || Ciphertext || MAC. Returns the ciphertext, which is
// extended if necessary.
func (k *Key) Encrypt(ciphertext, plaintext []byte) ([]byte, error) {
return crypto.Encrypt(k.master, ciphertext, plaintext)
}
// EncryptTo encrypts and signs data with the master key. The returned
// io.Writer writes IV || Ciphertext || HMAC. For the hash function, SHA256 is
// used.
// EncryptTo encrypts and authenticates data with the master key. The returned
// io.Writer writes IV || Ciphertext || MAC.
func (k *Key) EncryptTo(wr io.Writer) io.WriteCloser {
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 ||
// Ciphertext || MAC. In order to correctly verify the ciphertext, rd is
// 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.
func (k *Key) DecryptFrom(rd io.Reader) (io.ReadCloser, error) {
return crypto.DecryptFrom(k.master, rd)