Repository: borgbackup/borg

Issue:
recover data from a repo in 'authenticated' mode without the key/passphrase?
<!--
Thank you for reporting an issue.

*IMPORTANT* -  *before* creating a new issue please look around:
 - Borgbackup documentation: http://borgbackup.readthedocs.io/en/stable/index.html
 - FAQ: https://borgbackup.readthedocs.io/en/stable/faq.html
 and
 - open issues in GitHub tracker: https://github.com/borgbackup/borg/issues
  
If you cannot find a similar problem, then create a new issue.

Please fill in as much of the template as possible.
-->

## Have you checked borgbackup docs, FAQ, and open GitHub issues?

Yes

## Is this a BUG / ISSUE report or a QUESTION?

Question

## Describe the problem you're observing.
So I have this borg archive that was created with --encryption=authenticated (or authenticated-blake2, I can't remember) and I have accessed it before, using the passphrase. But since then I've forgotten it. Now I know the data isn't actually encrypted, so how can I get to it? There's only a post on stackexchange asking this question although it did not have any answers. 



Discussion:
Well, `borg init` tells everybody to have a backup of passphrase **and** key, so it is bad that you did not follow that.

Theoretically, the data could be recovered (as it is not encrypted), but it can not be authenticated any more (and borg would want to do that, as the repo and the chunks in it are of that type) - so guess we have no code yet to actually do that.
How important is the data in the repo?

Resolving patch:
diff --git a/src/borg/archiver/rcreate_cmd.py b/src/borg/archiver/rcreate_cmd.py
--- a/src/borg/archiver/rcreate_cmd.py
+++ b/src/borg/archiver/rcreate_cmd.py
@@ -152,6 +152,8 @@ def build_parser_rcreate(self, subparsers, common_parser, mid_common_parser):
 
         If you do **not** want to encrypt the contents of your backups, but still want to detect
         malicious tampering use an `authenticated` mode. It's like `repokey` minus encryption.
+        To normally work with ``authenticated`` repos, you will need the passphrase, but
+        there is an emergency workaround, see ``BORG_WORKAROUNDS=authenticated_no_key`` docs.
 
         Creating a related repository
         +++++++++++++++++++++++++++++
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
@@ -20,6 +20,7 @@
 from ..helpers import bin_to_hex
 from ..helpers.passphrase import Passphrase, PasswordRetriesExceeded, PassphraseWrong
 from ..helpers import msgpack
+from ..helpers import workarounds
 from ..item import Key, EncryptedKey, want_bytes
 from ..manifest import Manifest
 from ..platform import SaveFile
@@ -30,6 +31,9 @@
 from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b, AES256_OCB, CHACHA20_POLY1305
 from . import low_level
 
+# workaround for lost passphrase or key in "authenticated" or "authenticated-blake2" mode
+AUTHENTICATED_NO_KEY = "authenticated_no_key" in workarounds
+
 
 class UnsupportedPayloadError(Error):
     """Unsupported payload type {}. A newer version is required to access this repository."""
@@ -242,6 +246,8 @@ def unpack_and_verify_manifest(self, data, force_tam_not_required=False):
         unpacker = get_limited_unpacker("manifest")
         unpacker.feed(data)
         unpacked = unpacker.unpack()
+        if AUTHENTICATED_NO_KEY:
+            return unpacked, True  # True is a lie.
         if "tam" not in unpacked:
             if tam_required:
                 raise TAMRequiredError(self.repository._location.canonical_path())
@@ -800,6 +806,19 @@ class AuthenticatedKeyBase(AESKeyBase, FlexiKey):
     # It's only authenticated, not encrypted.
     logically_encrypted = False
 
+    def _load(self, key_data, passphrase):
+        if AUTHENTICATED_NO_KEY:
+            # fake _load if we have no key or passphrase
+            NOPE = bytes(32)  # 256 bit all-zero
+            self.repository_id = NOPE
+            self.enc_key = NOPE
+            self.enc_hmac_key = NOPE
+            self.id_key = NOPE
+            self.chunk_seed = 0
+            self.tam_required = False
+            return True
+        return super()._load(key_data, passphrase)
+
     def load(self, target, passphrase):
         success = super().load(target, passphrase)
         self.logically_encrypted = False
diff --git a/src/borg/repoobj.py b/src/borg/repoobj.py
--- a/src/borg/repoobj.py
+++ b/src/borg/repoobj.py
@@ -1,8 +1,11 @@
 from struct import Struct
 
-from .helpers import msgpack
+from .helpers import msgpack, workarounds
 from .compress import Compressor, LZ4_COMPRESSOR, get_compressor
 
+# workaround for lost passphrase or key in "authenticated" or "authenticated-blake2" mode
+AUTHENTICATED_NO_KEY = "authenticated_no_key" in workarounds
+
 
 class RepoObj:
     meta_len_hdr = Struct("<H")  # 16bit unsigned int
@@ -110,7 +113,8 @@ def parse(
             compressor_cls, compression_level = Compressor.detect(compr_hdr)
             compressor = compressor_cls(level=compression_level)
             meta, data = compressor.decompress(dict(meta_compressed), data_compressed[:psize])
-            self.key.assert_id(id, data)
+            if not AUTHENTICATED_NO_KEY:
+                self.key.assert_id(id, data)
         else:
             meta, data = None, None
         return meta_compressed if want_compressed else meta, data_compressed if want_compressed else data
@@ -170,7 +174,8 @@ def parse(
         meta_compressed["csize"] = len(data_compressed)
         if decompress:
             meta, data = compressor.decompress(None, data_compressed)
-            self.key.assert_id(id, data)
+            if not AUTHENTICATED_NO_KEY:
+                self.key.assert_id(id, data)
         else:
             meta, data = None, None
         return meta_compressed if want_compressed else meta, data_compressed if want_compressed else data


Test patch: