diff --git a/src/borg/archiver/transfer.py b/src/borg/archiver/transfer.py index 62f8f0ac7..2f4ed137c 100644 --- a/src/borg/archiver/transfer.py +++ b/src/borg/archiver/transfer.py @@ -3,6 +3,7 @@ import argparse from .common import with_repository, with_other_repository from ..archive import Archive from ..constants import * # NOQA +from ..crypto.key import uses_same_id_hash, uses_same_chunker_secret from ..helpers import EXIT_SUCCESS, EXIT_ERROR from ..helpers import location_validator, Location from ..helpers import format_file_size @@ -20,6 +21,18 @@ class TransferMixIn: self, args, *, repository, manifest, key, cache, other_repository=None, other_manifest=None, other_key=None ): """archives transfer from other repository, optionally upgrade data format""" + if not uses_same_id_hash(other_key, key): + self.print_error( + "You must keep the same ID hash ([HMAC-]SHA256 or BLAKE2b) or deduplication will break. " + "Use a related repository!" + ) + return EXIT_ERROR + if not uses_same_chunker_secret(other_key, key): + self.print_error( + "You must use the same chunker secret or deduplication will break. " "Use a related repository!" + ) + return EXIT_ERROR + dry_run = args.dry_run args.consider_checkpoints = True archive_names = tuple(x.name for x in other_manifest.archives.list_considering(args)) @@ -106,7 +119,7 @@ class TransferMixIn: # initialize DST_REPO reusing key material from SRC_REPO, so that # chunking and chunk id generation will work in the same way as before. - borg --repo=DST_REPO init --other-repo=SRC_REPO --encryption=DST_ENC + borg --repo=DST_REPO rcreate --other-repo=SRC_REPO --encryption=DST_ENC # transfer archives from SRC_REPO to DST_REPO borg --repo=DST_REPO transfer --other-repo=SRC_REPO --dry-run # check what it would do diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 2def782f0..9b64e71ce 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -121,18 +121,35 @@ def tam_required(repository): return os.path.isfile(file) +def uses_same_chunker_secret(other_key, key): + """is the chunker secret the same?""" + # avoid breaking the deduplication by a different chunker secret + same_chunker_secret = other_key.chunk_seed == key.chunk_seed + return same_chunker_secret + + def uses_same_id_hash(other_key, key): """other_key -> key upgrade: is the id hash the same?""" # avoid breaking the deduplication by changing the id hash - old_hmac_sha256_ids = (RepoKey, KeyfileKey) - new_hmac_sha256_ids = (AESOCBRepoKey, AESOCBKeyfileKey, CHPORepoKey, CHPOKeyfileKey) - old_blake2_ids = (Blake2RepoKey, Blake2KeyfileKey) - new_blake2_ids = (Blake2AESOCBRepoKey, Blake2AESOCBKeyfileKey, Blake2CHPORepoKey, Blake2CHPOKeyfileKey) + old_sha256_ids = (PlaintextKey,) + new_sha256_ids = (PlaintextKey,) + old_hmac_sha256_ids = (RepoKey, KeyfileKey, AuthenticatedKey) + new_hmac_sha256_ids = (AESOCBRepoKey, AESOCBKeyfileKey, CHPORepoKey, CHPOKeyfileKey, AuthenticatedKey) + old_blake2_ids = (Blake2RepoKey, Blake2KeyfileKey, Blake2AuthenticatedKey) + new_blake2_ids = ( + Blake2AESOCBRepoKey, + Blake2AESOCBKeyfileKey, + Blake2CHPORepoKey, + Blake2CHPOKeyfileKey, + Blake2AuthenticatedKey, + ) same_ids = ( isinstance(other_key, old_hmac_sha256_ids + new_hmac_sha256_ids) and isinstance(key, new_hmac_sha256_ids) or isinstance(other_key, old_blake2_ids + new_blake2_ids) and isinstance(key, new_blake2_ids) + or isinstance(other_key, old_sha256_ids + new_sha256_ids) + and isinstance(key, new_sha256_ids) ) return same_ids