minor key.encrypt api change/cleanup

we already have .decrypt(id, data, ...).
i changed .encrypt(chunk) to .encrypt(id, data).

the old borg crypto won't really need or use the id,
but the new AEAD crypto will authenticate the id in future.
This commit is contained in:
Thomas Waldmann 2022-03-21 12:33:11 +01:00
parent 41b8a04d82
commit d3b78a6cf5
7 changed files with 46 additions and 36 deletions

View File

@ -1789,7 +1789,7 @@ class ArchiveChecker:
def add_callback(chunk):
id_ = self.key.id_hash(chunk)
cdata = self.key.encrypt(chunk)
cdata = self.key.encrypt(id_, chunk)
add_reference(id_, len(chunk), len(cdata), cdata)
return id_
@ -1811,7 +1811,7 @@ class ArchiveChecker:
def replacement_chunk(size):
chunk = Chunk(None, allocation=CH_ALLOC, size=size)
chunk_id, data = cached_hash(chunk, self.key.id_hash)
cdata = self.key.encrypt(data)
cdata = self.key.encrypt(chunk_id, data)
csize = len(cdata)
return chunk_id, size, csize, cdata
@ -1998,7 +1998,7 @@ class ArchiveChecker:
archive.items = items_buffer.chunks
data = msgpack.packb(archive.as_dict())
new_archive_id = self.key.id_hash(data)
cdata = self.key.encrypt(data)
cdata = self.key.encrypt(new_archive_id, data)
add_reference(new_archive_id, len(data), len(cdata), cdata)
self.manifest.archives[info.name] = (new_archive_id, info.ts)
pi.finish()

View File

@ -942,7 +942,7 @@ class LocalCache(CacheStatsMixin):
refcount = self.seen_chunk(id, size)
if refcount and not overwrite:
return self.chunk_incref(id, stats)
data = self.key.encrypt(chunk)
data = self.key.encrypt(id, chunk)
csize = len(data)
self.repository.put(id, data, wait=wait)
self.chunks.add(id, 1, size, csize)
@ -1107,7 +1107,7 @@ Chunk index: {0.total_unique_chunks:20d} unknown"""
refcount = self.seen_chunk(id, size)
if refcount:
return self.chunk_incref(id, stats, size=size)
data = self.key.encrypt(chunk)
data = self.key.encrypt(id, chunk)
csize = len(data)
self.repository.put(id, data, wait=wait)
self.chunks.add(id, 1, size, csize)

View File

@ -158,7 +158,7 @@ class KeyBase:
"""
raise NotImplementedError
def encrypt(self, chunk):
def encrypt(self, id, data):
pass
def decrypt(self, id, data, decompress=True):
@ -264,8 +264,8 @@ class PlaintextKey(KeyBase):
def id_hash(self, data):
return sha256(data).digest()
def encrypt(self, chunk):
data = self.compressor.compress(chunk)
def encrypt(self, id, data):
data = self.compressor.compress(data)
return b''.join([self.TYPE_STR, data])
def decrypt(self, id, data, decompress=True):
@ -340,8 +340,8 @@ class AESKeyBase(KeyBase):
logically_encrypted = True
def encrypt(self, chunk):
data = self.compressor.compress(chunk)
def encrypt(self, id, data):
data = self.compressor.compress(data)
next_iv = self.nonce_manager.ensure_reservation(self.cipher.next_iv(),
self.cipher.block_count(len(data)))
return self.cipher.encrypt(data, header=self.TYPE_STR, iv=next_iv)
@ -678,8 +678,8 @@ class AuthenticatedKeyBase(AESKeyBase, FlexiKey):
if manifest_data is not None:
self.assert_type(manifest_data[0])
def encrypt(self, chunk):
data = self.compressor.compress(chunk)
def encrypt(self, id, data):
data = self.compressor.compress(data)
return b''.join([self.TYPE_STR, data])
def decrypt(self, id, data, decompress=True):
@ -732,9 +732,9 @@ class AEADKeyBase(KeyBase):
logically_encrypted = True
def encrypt(self, chunk):
def encrypt(self, id, data):
# to encrypt new data in this session we use always self.cipher and self.sessionid
data = self.compressor.compress(chunk)
data = self.compressor.compress(data)
reserved = b'\0'
iv = self.cipher.next_iv()
iv_48bit = iv.to_bytes(6, 'big')

View File

@ -261,4 +261,4 @@ class Manifest:
self.tam_verified = True
data = self.key.pack_and_authenticate_metadata(manifest.as_dict())
self.id = self.key.id_hash(data)
self.repository.put(self.MANIFEST_ID, self.key.encrypt(data))
self.repository.put(self.MANIFEST_ID, self.key.encrypt(self.MANIFEST_ID, data))

View File

@ -3806,7 +3806,7 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase):
'version': 1,
})
archive_id = key.id_hash(archive)
repository.put(archive_id, key.encrypt(archive))
repository.put(archive_id, key.encrypt(archive_id, archive))
repository.commit(compact=False)
self.cmd('check', self.repository_location, exit_code=1)
self.cmd('check', '--repair', self.repository_location, exit_code=0)
@ -3894,7 +3894,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase):
def spoof_manifest(self, repository):
with repository:
_, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
repository.put(Manifest.MANIFEST_ID, key.encrypt(msgpack.packb({
repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb({
'version': 1,
'archives': {},
'config': {},
@ -3907,7 +3907,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase):
repository = Repository(self.repository_path, exclusive=True)
with repository:
manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
repository.put(Manifest.MANIFEST_ID, key.encrypt(msgpack.packb({
repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb({
'version': 1,
'archives': {},
'timestamp': (datetime.utcnow() + timedelta(days=1)).strftime(ISO_FORMAT),
@ -3929,7 +3929,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase):
manifest = msgpack.unpackb(key.decrypt(None, repository.get(Manifest.MANIFEST_ID)))
del manifest[b'tam']
repository.put(Manifest.MANIFEST_ID, key.encrypt(msgpack.packb(manifest)))
repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb(manifest)))
repository.commit(compact=False)
output = self.cmd('list', '--debug', self.repository_location)
assert 'archive1234' in output

View File

@ -114,18 +114,21 @@ class TestKey:
def test_plaintext(self):
key = PlaintextKey.create(None, None)
chunk = b'foo'
assert hexlify(key.id_hash(chunk)) == b'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
assert chunk == key.decrypt(key.id_hash(chunk), key.encrypt(chunk))
id = key.id_hash(chunk)
assert hexlify(id) == b'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
assert chunk == key.decrypt(id, key.encrypt(id, chunk))
def test_keyfile(self, monkeypatch, keys_dir):
monkeypatch.setenv('BORG_PASSPHRASE', 'test')
key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
assert key.cipher.next_iv() == 0
manifest = key.encrypt(b'ABC')
chunk = b'ABC'
id = key.id_hash(chunk)
manifest = key.encrypt(id, chunk)
assert key.cipher.extract_iv(manifest) == 0
manifest2 = key.encrypt(b'ABC')
manifest2 = key.encrypt(id, chunk)
assert manifest != manifest2
assert key.decrypt(None, manifest) == key.decrypt(None, manifest2)
assert key.decrypt(id, manifest) == key.decrypt(id, manifest2)
assert key.cipher.extract_iv(manifest2) == 1
iv = key.cipher.extract_iv(manifest)
key2 = KeyfileKey.detect(self.MockRepository(), manifest)
@ -134,7 +137,8 @@ class TestKey:
assert len({key2.id_key, key2.enc_key, key2.enc_hmac_key}) == 3
assert key2.chunk_seed != 0
chunk = b'foo'
assert chunk == key2.decrypt(key.id_hash(chunk), key.encrypt(chunk))
id = key.id_hash(chunk)
assert chunk == key2.decrypt(id, key.encrypt(id, chunk))
def test_keyfile_nonce_rollback_protection(self, monkeypatch, keys_dir):
monkeypatch.setenv('BORG_PASSPHRASE', 'test')
@ -142,9 +146,11 @@ class TestKey:
with open(os.path.join(get_security_dir(repository.id_str), 'nonce'), "w") as fd:
fd.write("0000000000002000")
key = KeyfileKey.create(repository, self.MockArgs())
data = key.encrypt(b'ABC')
chunk = b'ABC'
id = key.id_hash(chunk)
data = key.encrypt(id, chunk)
assert key.cipher.extract_iv(data) == 0x2000
assert key.decrypt(None, data) == b'ABC'
assert key.decrypt(id, data) == chunk
def test_keyfile_kfenv(self, tmpdir, monkeypatch):
keyfile = tmpdir.join('keyfile')
@ -155,7 +161,7 @@ class TestKey:
assert keyfile.exists()
chunk = b'ABC'
chunk_id = key.id_hash(chunk)
chunk_cdata = key.encrypt(chunk)
chunk_cdata = key.encrypt(chunk_id, chunk)
key = KeyfileKey.detect(self.MockRepository(), chunk_cdata)
assert chunk == key.decrypt(chunk_id, chunk_cdata)
keyfile.remove()
@ -212,18 +218,20 @@ class TestKey:
def test_roundtrip(self, key):
repository = key.repository
plaintext = b'foo'
encrypted = key.encrypt(plaintext)
id = key.id_hash(plaintext)
encrypted = key.encrypt(id, plaintext)
identified_key_class = identify_key(encrypted)
assert identified_key_class == key.__class__
loaded_key = identified_key_class.detect(repository, encrypted)
decrypted = loaded_key.decrypt(None, encrypted)
decrypted = loaded_key.decrypt(id, encrypted)
assert decrypted == plaintext
def test_decrypt_decompress(self, key):
plaintext = b'123456789'
encrypted = key.encrypt(plaintext)
assert key.decrypt(None, encrypted, decompress=False) != plaintext
assert key.decrypt(None, encrypted) == plaintext
id = key.id_hash(plaintext)
encrypted = key.encrypt(id, plaintext)
assert key.decrypt(id, encrypted, decompress=False) != plaintext
assert key.decrypt(id, encrypted) == plaintext
def test_assert_id(self, key):
plaintext = b'123456789'
@ -243,7 +251,8 @@ class TestKey:
assert AuthenticatedKey.id_hash is ID_HMAC_SHA_256.id_hash
assert len(key.id_key) == 32
plaintext = b'123456789'
authenticated = key.encrypt(plaintext)
id = key.id_hash(plaintext)
authenticated = key.encrypt(id, plaintext)
# 0x07 is the key TYPE, \x0000 identifies no compression.
assert authenticated == b'\x07\x00\x00' + plaintext
@ -253,7 +262,8 @@ class TestKey:
assert Blake2AuthenticatedKey.id_hash is ID_BLAKE2b_256.id_hash
assert len(key.id_key) == 128
plaintext = b'123456789'
authenticated = key.encrypt(plaintext)
id = key.id_hash(plaintext)
authenticated = key.encrypt(id, plaintext)
# 0x06 is the key TYPE, 0x0000 identifies no compression.
assert authenticated == b'\x06\x00\x00' + plaintext

View File

@ -165,7 +165,7 @@ class TestRepositoryCache:
def _put_encrypted_object(self, key, repository, data):
id_ = key.id_hash(data)
repository.put(id_, key.encrypt(data))
repository.put(id_, key.encrypt(id_, data))
return id_
@pytest.fixture