diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 804ed5c78..2ac8dd32e 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -373,13 +373,6 @@ class AESKeyBase(KeyBase): self.assert_id(id, data) return data - def extract_nonce(self, payload): - if not (payload[0] == self.TYPE or - payload[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)): - raise IntegrityError('Manifest: Invalid encryption envelope') - nonce = bytes_to_long(payload[33:41]) - return nonce - def init_from_random_data(self, data=None): if data is None: data = os.urandom(100) @@ -391,10 +384,21 @@ class AESKeyBase(KeyBase): if self.chunk_seed & 0x80000000: self.chunk_seed = self.chunk_seed - 0xffffffff - 1 - def init_ciphers(self, manifest_nonce=0): - self.cipher = CIPHERSUITE(mac_key=self.enc_hmac_key, enc_key=self.enc_key, - iv=manifest_nonce.to_bytes(16, byteorder='big')) - self.nonce_manager = NonceManager(self.repository, self.cipher, manifest_nonce) + def init_ciphers(self, manifest_data=None): + self.cipher = CIPHERSUITE(mac_key=self.enc_hmac_key, enc_key=self.enc_key) + if manifest_data is None: + nonce = 0 + else: + if not (manifest_data[0] == self.TYPE or + manifest_data[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)): + raise IntegrityError('Invalid encryption envelope') + # manifest_blocks is a safe upper bound on the amount of cipher blocks needed + # to encrypt the manifest. depending on the ciphersuite and overhead, it might + # be a bit too high, but that does not matter. + manifest_blocks = num_cipher_blocks(len(manifest_data)) + nonce = self.cipher.extract_iv(manifest_data) + manifest_blocks + self.cipher.set_iv(nonce.to_bytes(16, byteorder='big')) + self.nonce_manager = NonceManager(self.repository, self.cipher, nonce) class Passphrase(str): @@ -514,8 +518,7 @@ class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase): key.init(repository, passphrase) try: key.decrypt(None, manifest_data) - num_blocks = num_cipher_blocks(len(manifest_data) - 41) - key.init_ciphers(key.extract_nonce(manifest_data) + num_blocks) + key.init_ciphers(manifest_data) key._passphrase = passphrase return key except IntegrityError: @@ -554,8 +557,7 @@ class KeyfileKeyBase(AESKeyBase): else: if not key.load(target, passphrase): raise PassphraseWrong - num_blocks = num_cipher_blocks(len(manifest_data) - 41) - key.init_ciphers(key.extract_nonce(manifest_data) + num_blocks) + key.init_ciphers(manifest_data) key._passphrase = passphrase return key diff --git a/src/borg/crypto/low_level.pyx b/src/borg/crypto/low_level.pyx index 1fc67b832..034e99024 100644 --- a/src/borg/crypto/low_level.pyx +++ b/src/borg/crypto/low_level.pyx @@ -233,6 +233,9 @@ class UNENCRYPTED: def next_iv(self): return self.iv + def extract_iv(self, envelope): + return 0 + cdef class AES256_CTR_HMAC_SHA256: # Layout: HEADER + HMAC 32 + IV 8 + CT (same as attic / borg < 1.2 IF HEADER = TYPE_BYTE, no AAD) @@ -395,6 +398,10 @@ cdef class AES256_CTR_HMAC_SHA256: for i in range(self.iv_len_short): iv_out[i] = iv[(self.iv_len-self.iv_len_short)+i] + def extract_iv(self, envelope): + offset = 1 + self.mac_len + return bytes_to_long(envelope[offset:offset+self.iv_len_short]) + ctypedef const EVP_CIPHER * (* CIPHER)() @@ -565,6 +572,10 @@ cdef class _AEAD_BASE: for i in range(self.iv_len): iv_out[i] = iv[i] + def extract_iv(self, envelope): + offset = 1 + self.mac_len # XXX 1 -> self.header_len + return bytes_to_long(envelope[offset:offset+self.iv_len]) + cdef class _AES_BASE(_AEAD_BASE): def __init__(self, *args, **kwargs): diff --git a/src/borg/testsuite/key.py b/src/borg/testsuite/key.py index 27b42fc80..670af0d2c 100644 --- a/src/borg/testsuite/key.py +++ b/src/borg/testsuite/key.py @@ -119,12 +119,12 @@ class TestKey: key = KeyfileKey.create(self.MockRepository(), self.MockArgs()) assert bytes_to_long(key.cipher.next_iv(), 8) == 0 manifest = key.encrypt(b'ABC') - assert key.extract_nonce(manifest) == 0 + assert key.cipher.extract_iv(manifest) == 0 manifest2 = key.encrypt(b'ABC') assert manifest != manifest2 assert key.decrypt(None, manifest) == key.decrypt(None, manifest2) - assert key.extract_nonce(manifest2) == 1 - iv = key.extract_nonce(manifest) + assert key.cipher.extract_iv(manifest2) == 1 + iv = key.cipher.extract_iv(manifest) key2 = KeyfileKey.detect(self.MockRepository(), manifest) assert bytes_to_long(key2.cipher.next_iv(), 8) >= iv + key2.cipher.block_count(len(manifest) - KeyfileKey.PAYLOAD_OVERHEAD) # Key data sanity check @@ -140,7 +140,7 @@ class TestKey: fd.write("0000000000002000") key = KeyfileKey.create(repository, self.MockArgs()) data = key.encrypt(b'ABC') - assert key.extract_nonce(data) == 0x2000 + assert key.cipher.extract_iv(data) == 0x2000 assert key.decrypt(None, data) == b'ABC' def test_keyfile_kfenv(self, tmpdir, monkeypatch): @@ -192,14 +192,14 @@ class TestKey: assert hexlify(key.enc_key) == b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a' assert key.chunk_seed == -775740477 manifest = key.encrypt(b'ABC') - assert key.extract_nonce(manifest) == 0 + assert key.cipher.extract_iv(manifest) == 0 manifest2 = key.encrypt(b'ABC') assert manifest != manifest2 assert key.decrypt(None, manifest) == key.decrypt(None, manifest2) - assert key.extract_nonce(manifest2) == 1 - iv = key.extract_nonce(manifest) + assert key.cipher.extract_iv(manifest2) == 1 + iv = key.cipher.extract_iv(manifest) key2 = PassphraseKey.detect(self.MockRepository(), manifest) - assert bytes_to_long(key2.cipher.next_iv(), 8) == iv + key2.cipher.block_count(len(manifest) - PassphraseKey.PAYLOAD_OVERHEAD) + assert bytes_to_long(key2.cipher.next_iv(), 8) == iv + key2.cipher.block_count(len(manifest)) assert key.id_key == key2.id_key assert key.enc_hmac_key == key2.enc_hmac_key assert key.enc_key == key2.enc_key