diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index cd9a8b60e..804ed5c78 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -27,7 +27,7 @@ from ..item import Key, EncryptedKey from ..platform import SaveFile from .nonces import NonceManager -from .low_level import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_aes_blocks, hmac_sha256, blake2b_256, hkdf_hmac_sha512 +from .low_level import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256, hkdf_hmac_sha512 from .low_level import AES256_CTR_HMAC_SHA256 as CIPHERSUITE @@ -514,7 +514,7 @@ class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase): key.init(repository, passphrase) try: key.decrypt(None, manifest_data) - num_blocks = num_aes_blocks(len(manifest_data) - 41) + num_blocks = num_cipher_blocks(len(manifest_data) - 41) key.init_ciphers(key.extract_nonce(manifest_data) + num_blocks) key._passphrase = passphrase return key @@ -554,7 +554,7 @@ class KeyfileKeyBase(AESKeyBase): else: if not key.load(target, passphrase): raise PassphraseWrong - num_blocks = num_aes_blocks(len(manifest_data) - 41) + num_blocks = num_cipher_blocks(len(manifest_data) - 41) key.init_ciphers(key.extract_nonce(manifest_data) + num_blocks) key._passphrase = passphrase return key diff --git a/src/borg/crypto/low_level.pyx b/src/borg/crypto/low_level.pyx index f912401de..1fc67b832 100644 --- a/src/borg/crypto/low_level.pyx +++ b/src/borg/crypto/low_level.pyx @@ -168,11 +168,20 @@ def increment_iv(iv, amount=1): return iv -def num_aes_blocks(length): - """Return the number of AES blocks required to encrypt/decrypt *length* bytes of data. - Note: this is only correct for modes without padding, like AES-CTR. +def num_cipher_blocks(length, blocksize=16): + """Return the number of cipher blocks required to encrypt/decrypt bytes of data. + + For a precise computation, must be the used cipher's block size (AES: 16, CHACHA20: 64). + + For a safe-upper-boundary computation, must be the MINIMUM of the block sizes (in + bytes) of ALL supported ciphers. This can be used to adjust a counter if the used cipher is not + known (yet). + The default value of blocksize must be adjusted so it reflects this minimum, so a call of this + function without a blocksize is "safe-upper-boundary by default". + + Padding cipher modes are not supported. """ - return (length + 15) // 16 + return (length + blocksize - 1) // blocksize class CryptoError(Exception): @@ -363,8 +372,7 @@ cdef class AES256_CTR_HMAC_SHA256: PyBuffer_Release(&idata) def block_count(self, length): - # number of cipher blocks needed for data of length bytes - return (length + self.cipher_blk_len - 1) // self.cipher_blk_len + return num_cipher_blocks(length, self.cipher_blk_len) def set_iv(self, iv): # set_iv needs to be called before each encrypt() call @@ -528,8 +536,7 @@ cdef class _AEAD_BASE: PyBuffer_Release(&idata) def block_count(self, length): - # number of cipher blocks needed for data of length bytes - return (length + self.cipher_blk_len - 1) // self.cipher_blk_len + return num_cipher_blocks(length, self.cipher_blk_len) def set_iv(self, iv): # set_iv needs to be called before each encrypt() call, @@ -679,8 +686,7 @@ cdef class AES: PyBuffer_Release(&idata) def block_count(self, length): - # number of cipher blocks needed for data of length bytes - return (length + self.cipher_blk_len - 1) // self.cipher_blk_len + return num_cipher_blocks(length, self.cipher_blk_len) def set_iv(self, iv): # set_iv needs to be called before each encrypt() call, diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 58b52866d..3d2783e1f 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -36,7 +36,7 @@ from ..archive import Archive, ChunkBuffer, flags_noatime, flags_normal from ..archiver import Archiver, parse_storage_quota from ..cache import Cache, LocalCache from ..constants import * # NOQA -from ..crypto.low_level import bytes_to_long, num_aes_blocks +from ..crypto.low_level import bytes_to_long, num_cipher_blocks from ..crypto.key import KeyfileKeyBase, RepoKey, KeyfileKey, Passphrase, TAMRequiredError from ..crypto.keymanager import RepoIdMismatch, NotABorgKeyFile from ..crypto.file_integrity import FileIntegrityError @@ -2169,7 +2169,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): hash = sha256(data).digest() if hash not in seen: seen.add(hash) - num_blocks = num_aes_blocks(len(data) - 41) + num_blocks = num_cipher_blocks(len(data) - 41) nonce = bytes_to_long(data[33:41]) for counter in range(nonce, nonce + num_blocks): self.assert_not_in(counter, used)