Repository: borgbackup/borg

Issue:
init --other-repository: implement AES/MAC key change
when using `borg -r REPO rcreate --encryption=X --other-repository=OTHER_REPO`, borg will currently copy **all** key material from the other repo's key to the new repo's key.

this is good and useful for:
- same AES/MAC key: being able to transfer encrypted chunks "as is" from other repo to repo (note: borg transfer does **not** do this (yet?))
- same chunker seed: cut chunks in the same way / at the same cutting points
- same ID key: compute chunk ids in the same way, so deduplication works

this is fine as long as there is no problem with the existing key material.

if key and passphrase have been compromised / are in unauthorized hands, one will want to change the AES and MAC key (needs reencrypting / reauthing chunks on transfer) and keep only the ID key and the chunker seed (so that dedup works in the same way).

in this case, we should also ask for a new passphrase and not just copy the old one.

another reason to change AES/MAC key is if there is just no reason to keep it the same, e.g. if everything added to the new repo is freshly encrypted/signed anyway.



Discussion:
Of course keeping the ID key and chunker seed the same means that an attacker who has these could try to do fingerprinting attacks (needs filesystem or RPC access to the repo, the chunk ids are in the unencrypted repo index). The attacker then could confirm whether the repo has some data known to the attacker.

Guess for a lot of users this is only a minor issue, but it should be documented.

If one would want to avoid that, deduplication would break. Guess in that case one can also just start a new empty repo and delete the old one after a while.

Resolving patch:
diff --git a/src/borg/archiver/rcreate.py b/src/borg/archiver/rcreate.py
--- a/src/borg/archiver/rcreate.py
+++ b/src/borg/archiver/rcreate.py
@@ -21,6 +21,8 @@ def do_rcreate(self, args, repository, *, other_repository=None, other_key=None)
         """Create a new, empty repository"""
         path = args.location.canonical_path()
         logger.info('Initializing repository at "%s"' % path)
+        if other_key is not None:
+            other_key.copy_ae_key = args.copy_ae_key
         try:
             key = key_creator(repository, args, other_key=other_key)
         except (EOFError, KeyboardInterrupt):
@@ -206,3 +208,9 @@ def build_parser_rcreate(self, subparsers, common_parser, mid_common_parser):
             action="store_true",
             help="create the parent directories of the repository directory, if they are missing.",
         )
+        subparser.add_argument(
+            "--copy-ae-key",
+            dest="copy_ae_key",
+            action="store_true",
+            help="copy the authenticated encryption (AE) key from the key of the other repo (default: new random key).",
+        )
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
@@ -191,6 +191,7 @@ def __init__(self, repository):
         self.compressor = Compressor("lz4")
         self.decompress = self.compressor.decompress
         self.tam_required = True
+        self.copy_ae_key = False
 
     def id_hash(self, data):
         """Return HMAC hash using the "id" HMAC key"""
@@ -605,11 +606,17 @@ def create(cls, repository, args, *, other_key=None):
                 raise Error("Copying key material to an AES-CTR based mode is insecure and unsupported.")
             if not uses_same_id_hash(other_key, key):
                 raise Error("You must keep the same ID hash (HMAC-SHA256 or BLAKE2b) or deduplication will break.")
+            if other_key.copy_ae_key:
+                # give the user the option to use the same authenticated encryption (AE) key
+                enc_key = other_key.enc_key
+                enc_hmac_key = other_key.enc_hmac_key
+            else:
+                # borg transfer re-encrypts all data anyway, thus we can default to a new, random AE key
+                data = os.urandom(64)
+                enc_key = data[0:32]
+                enc_hmac_key = data[32:64]
             key.init_from_given_data(
-                enc_key=other_key.enc_key,
-                enc_hmac_key=other_key.enc_hmac_key,
-                id_key=other_key.id_key,
-                chunk_seed=other_key.chunk_seed,
+                enc_key=enc_key, enc_hmac_key=enc_hmac_key, id_key=other_key.id_key, chunk_seed=other_key.chunk_seed
             )
             passphrase = other_key._passphrase
         else:


Test patch: