Repository: borgbackup/borg

Issue:
check same key material before doing transfer?
`borg transfer` currently transfers archives even between unrelated repos (== repos not created using the `--other-repo` option of `borg rcreate`).

This is not a problem concerning the AEAD encryption as data is decrypted (with source repo key / algorithm) and reencrypted (with dest repo key / algorithm) anyway.

But it is a problem with:
- chunk cutting being different (due to different chunker secret) - this leads to future backups not deduplicating with past backups (different chunk length, different chunks offsets)
- ID computation being different (due to ID MAC key being different) - this leads to future backups not deduplicating with past backups (different IDs even if it was the same content). also it means that `ID_MAC_NEW(chunk_content) <> ID`.

So, shall borg just reject transferring archives between unrelated repos if users forget to give `--other-repo` to `borg rcreate`?



Discussion:
See also #6710 - different AEAD key material is not a problem.

Just check ID MAC key and chunker secret to be identical.

Resolving patch:
diff --git a/src/borg/archiver/transfer.py b/src/borg/archiver/transfer.py
--- a/src/borg/archiver/transfer.py
+++ b/src/borg/archiver/transfer.py
@@ -3,6 +3,7 @@
 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 @@ def do_transfer(
         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 @@ def build_parser_transfer(self, subparsers, common_parser, mid_common_parser):
 
             # 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
--- 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
 


Test patch: