Repository: borgbackup/borg

Issue:
Getting "msgpack.exceptions.OutOfData" on all encrypted repo operations
## Have you checked borgbackup docs, FAQ, and open GitHub issues?

Yes

## Is this a BUG / ISSUE report or a QUESTION?

BUG / ISSUE

## System information. For client/server mode post info for both machines.
Server:
```
Platform: Linux arch-bees 6.2.11-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 13 Apr 2023 16:59:24 +0000 x86_64
Linux: Unknown Linux  
Borg: 1.2.4  Python: CPython 3.11.3 msgpack: 1.0.5 fuse: None [pyfuse3,llfuse]
```
Client:
```
Platform: Linux recluse 5.15.104-2-MANJARO #1 SMP PREEMPT Thu Mar 23 01:40:42 UTC 2023 x86_64
Linux: Unknown Linux  
Borg: 1.2.4  Python: CPython 3.10.10 msgpack: 1.0.4 fuse: llfuse 1.4.2 [pyfuse3,llfuse]
```

#### Hardware / network configuration, and filesystems used.
All communication taking place on LAN, ext4 filesystem on server and client. BORG connections being made over ssh using authorized keys.

#### How much data is handled by borg?
~150GB

#### Full borg commandline that lead to the problem (leave away excludes and passwords)
```
borg list borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440
```

## Describe the problem you're observing.
Cannot list any contents for encrypted repositories (encrypted with RSA KEY and passphrase), but CAN list contents for unencrypted repos.  I suspect this could be a problem with msgpack version.  Stacktrace below.

BORG environment variables are set correctly, and were verified multiple times:
```
BORG_PASSCOMMAND=cat /home/myuser/borg_pass.tmp
BORG_RSH=ssh -i /home/myuser/.ssh/borg-at-arch-bees
BORG_KEY_FILE=/home/myuser/.config/borg/keys/arch_bees__srv_nfs_backups_borg_laptop-e5440
```

#### Can you reproduce the problem? If so, describe how. If not, describe troubleshooting steps you took before opening the issue.
Yes, can reproduce every time, using any command (not just list command) against an encrypted repo.  I also tried temporarily moving the integrity file out of the repo area, but got the same stacktrace.

#### Include any warning/errors/backtraces from the system logs
```
Local Exception
Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/borg/archiver.py", line 5213, in main
    exit_code = archiver.run(args)
  File "/usr/lib/python3.10/site-packages/borg/archiver.py", line 5144, in run
    return set_ec(func(args))
  File "/usr/lib/python3.10/site-packages/borg/archiver.py", line 170, in wrapper
    kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
  File "/usr/lib/python3.10/site-packages/borg/helpers/manifest.py", line 187, in load
    key = key_factory(repository, cdata)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 134, in key_factory
    return identify_key(manifest_data).detect(repository, manifest_data)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 590, in detect
    if not key.load(target, passphrase):
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 767, in load
    success = self._load(key_data, passphrase)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 612, in _load
    data = self.decrypt_key_file(cdata, passphrase)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 630, in decrypt_key_file
    data = unpacker.unpack()
  File "/usr/lib/python3.10/site-packages/borg/helpers/msgpack.py", line 109, in unpack
    return super().unpack()
  File "msgpack/_unpacker.pyx", line 504, in msgpack._cmsgpack.Unpacker.unpack
  File "msgpack/_unpacker.pyx", line 479, in msgpack._cmsgpack.Unpacker._unpack
msgpack.exceptions.OutOfData: No more data to unpack.

Platform: Linux recluse 5.15.104-2-MANJARO #1 SMP PREEMPT Thu Mar 23 01:40:42 UTC 2023 x86_64
Linux: Unknown Linux  
Borg: 1.2.4  Python: CPython 3.10.10 msgpack: 1.0.4 fuse: llfuse 1.4.2 [pyfuse3,llfuse]
PID: 2933573  CWD: /home/myuser
sys.argv: ['/usr/bin/borg', 'list', 'borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440']
SSH_ORIGINAL_COMMAND: None
```



Discussion:
Borg does not use RSA keys.

Also, one does not usually need to use `BORG_KEY_FILE`, but if one does, one must make very sure it points to the correct key file depending on the repo.

Maybe try `BORG_PASSPHRASE` rather than `BORG_PASSCOMMAND` and make sure it contains the correct passphrase.

Did you use `export` to actually have these variables **in the borg process environment**?

Did you check the contents of the `arch_bees__srv_nfs_backups_borg_laptop-e5440` file and whether it is readable?

I am not aware of any msgpack problems in recent times.

@ThomasWaldmann thanks for the reply.  I used the wrong terminology for the Borg key.  It's not RSA as you say.  Must have mixed that concept up with the BORG_RSH env var which I use to provide ssh keys to the server.

I have verified that BORG_KEY_FILE points to the correct file and that it is readable and that its contents are accurate.  Have also verified that the env vars are present in the process environment (same terminal as where I am executing borg commands).

I tried unsetting the BORG_PASSCOMMAND and providing the password directly, to the same effect.
E.g.
```
[myuser@recluse ~]$ env | grep BORG
BORG_RSH=ssh -i /home/myuser/.ssh/borg-at-arch-bees
BORG_PASSPHRASE=<VERIFIED_PASSWORD>
BORG_KEY_FILE=/home/myuser/.config/borg/keys/arch_bees__srv_nfs_backups_borg_laptop-e5440
```

What's interesting is that if I unset BORG_PASSPHRASE and also have no BORG_PASSCOMMAND set, I am not prompted for a password.  If I then unset the BORG_KEY_FILE variable, I still get the same error, e.g.

```
[myuser@recluse ~]$ env | grep BORG
BORG_RSH=ssh -i /home/myuser/.ssh/borg-at-arch-bees

[myuser@recluse ~]$ borg list borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440
Local Exception
Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/borg/archiver.py", line 5213, in main
    exit_code = archiver.run(args)
  File "/usr/lib/python3.10/site-packages/borg/archiver.py", line 5144, in run
    return set_ec(func(args))
  File "/usr/lib/python3.10/site-packages/borg/archiver.py", line 170, in wrapper
    kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
  File "/usr/lib/python3.10/site-packages/borg/helpers/manifest.py", line 187, in load
    key = key_factory(repository, cdata)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 134, in key_factory
    return identify_key(manifest_data).detect(repository, manifest_data)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 590, in detect
    if not key.load(target, passphrase):
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 767, in load
    success = self._load(key_data, passphrase)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 612, in _load
    data = self.decrypt_key_file(cdata, passphrase)
  File "/usr/lib/python3.10/site-packages/borg/crypto/key.py", line 630, in decrypt_key_file
    data = unpacker.unpack()
  File "/usr/lib/python3.10/site-packages/borg/helpers/msgpack.py", line 109, in unpack
    return super().unpack()
  File "msgpack/_unpacker.pyx", line 504, in msgpack._cmsgpack.Unpacker.unpack
  File "msgpack/_unpacker.pyx", line 479, in msgpack._cmsgpack.Unpacker._unpack
msgpack.exceptions.OutOfData: No more data to unpack.

Platform: Linux recluse 5.15.104-2-MANJARO #1 SMP PREEMPT Thu Mar 23 01:40:42 UTC 2023 x86_64
Linux: Unknown Linux  
Borg: 1.2.4  Python: CPython 3.10.10 msgpack: 1.0.4 fuse: llfuse 1.4.2 [pyfuse3,llfuse]
PID: 3070260  CWD: /home/myuser
sys.argv: ['/usr/bin/borg', 'list', 'borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440']
SSH_ORIGINAL_COMMAND: None

```

Hmm, strange. I'll have a look at the source later, maybe I can find some more stuff to try.

Maybe try with a freshly initialised encrypted repo if your scripts / commands work.

**When doing so, make very very sure that BORG_KEY_FILE does not point to some of your production key files.**
I'd try to establish a test base from a segregated env like a container or a live USB, possibly once from both ends. Then we know, if it's a version mismatch, and if any combos work.

I see you use arch (= life on git master), try e.g. a Debian stable that we know works for sure. Even, if you understandably don't want docker on the desktop, you could install it, `docker run --rm -it debian bash`, then purge after. There are also specific python images like `python:3.11` to see if there's any difference. (Don't think so, though, or there'd be a million posts.)

Docker containers use the host kernel, but I wouldn't expect the kernel to make a difference.

But a version issue seems odd as the python packages should be identical, and msgpack only has a minor diff. Crypto is usually very backwards compatible so any system lib mismatch there also seems unlikely to cause trouble even if you're on the very latest.

Getting a working base and then finding differences seems most useful either way, unless anyone has a direct clue. Quick enough with borg's init/create/list.
Had a look into borg 1.2.4 source code:

Considering it ran into `borg.crypto.key:590` (in **both** of your tracebacks), it means that it did **not** get a passphrase from the environment (neither via BORG_PASSPHRASE, nor via BORG_PASSCOMMAND, nor via BORG_PASSPHRASE_FD).

It then finds the key like:
- check BORG_KEY_FILE in env and if it is there, use that path for the keyfile
- otherwise, try to find the key in the keys directory (trying all files there until one passes the sanity check)
- in both cases, it does a sanity check on the keyfile (whether it starts with BORG_KEY REPOID).

It then tries to load the found key:
- first it tries an empty passphrase (*)
- if that doesn't work, it asks 3 times for the passphrase and retries with the entered values

(*) As it fails there already with "no more data to unpack", it looks like the key file it found and read data from was empty (except the BORG_KEY REPOID) or corrupted.

@elandorr Did you find out anything about this?
Sorry nope, that's a little too deep for me, without sitting in front of it. Most I could guess is that he had an old keyfile lying around in an incompatible format (on windows putty is one of those), but that doesn't seem to apply to him.
Ehrm, guess I meant @mdbr000 . :-)
@ThomasWaldmann thanks for all of the great info.  I have some updates and observations:

I was able to create a brand new repo with key and passphrase, and everything worked as expected, i.e. `borg list` completed without error.  This was performed on the same machines (no virtual or isolated environment was created).

With the test repo that doesn't have issues, I was getting prompted for the repo password at the command line as expected.  With the problematic repo, I never get prompted for the password on the command line. If I fail to supply the env var `BORG_PASSPHRASE`, execution passes through
```
/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 590
```
If I supply the correct BORG_PASSPHRASE value as an env var, execution passes through 
```
/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 598
```

What may be the most interesting thing is running
```
borg key export borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440
```
against the problematic repo.  That results in a successful response:
```
BORG_KEY c486baae3axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
If I then attempt to use this key with `borg list`, I get the same msgpack error.

The data in these repos is pretty important to me, and it's actually the only spot I can access it thanks to the hard drive that was housing it reaching end-of-life. That hard drive is now completely dead. Are there any alternative methods of getting the data decrypted given that I have both the correct key and passphrase?
I ran the `borg list` command with the `--debug` flag, in case it sheds any more light on the issue...
```
[myuser@recluse borg_backups]$ borg list --debug borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440
using builtin fallback logging configuration
33 self tests completed in 0.09 seconds
SSH command line: ['ssh', '-i', '/home/myuser/.ssh/borg-at-arch-bees', 'borg@arch-bees', 'borg', 'serve', '--debug']
Remote: using builtin fallback logging configuration
Remote: 33 self tests completed in 0.15 seconds
Remote: using builtin fallback logging configuration
Remote: Initialized logging system for JSON-based protocol
Remote: Resolving repository path b'/srv/nfs/backups/borg/laptop-e5440'
Remote: Resolved repository path to '/srv/nfs/backups/borg/laptop-e5440'
Remote: Verified integrity of /srv/nfs/backups/borg/laptop-e5440/index.810
RemoteRepository: 230 B bytes sent, 4.60 kB bytes received, 4 messages sent
Local Exception
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 5213, in main
    exit_code = archiver.run(args)
                ^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 5144, in run
    return set_ec(func(args))
                  ^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 170, in wrapper
    kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/helpers/manifest.py", line 187, in load
    key = key_factory(repository, cdata)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 134, in key_factory
    return identify_key(manifest_data).detect(repository, manifest_data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 590, in detect
    if not key.load(target, passphrase):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 767, in load
    success = self._load(key_data, passphrase)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 612, in _load
    data = self.decrypt_key_file(cdata, passphrase)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 630, in decrypt_key_file
    data = unpacker.unpack()
           ^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/helpers/msgpack.py", line 109, in unpack
    return super().unpack()
           ^^^^^^^^^^^^^^^^
  File "msgpack/_unpacker.pyx", line 504, in msgpack._cmsgpack.Unpacker.unpack
  File "msgpack/_unpacker.pyx", line 479, in msgpack._cmsgpack.Unpacker._unpack
msgpack.exceptions.OutOfData: No more data to unpack.

Platform: Linux recluse 5.15.114-2-MANJARO #1 SMP PREEMPT Sun Jun 4 10:32:43 UTC 2023 x86_64
Linux: Unknown Linux  
Borg: 1.2.4  Python: CPython 3.11.3 msgpack: 1.0.5 fuse: llfuse 1.4.2 [pyfuse3,llfuse]
PID: 3001234  CWD: /home/myuser/borg_backups
sys.argv: ['/usr/bin/borg', 'list', '--debug', 'borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440']
SSH_ORIGINAL_COMMAND: None
```
It is still going via `key:590` (not via 598). That means it didn't get anything from the env.
Sorry, I didn't have `BORG_PASSPHRASE` set in that snippet above.  If I do set `BORG_PASSPHRASE` we pass through line 598...
```
[myuser@recluse borg_backups]$ export BORG_PASSPHRASE=supersecret_xxxxxxxxxxxxxxxxx
[myuser@recluse borg_backups]$ borg list --debug borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440
using builtin fallback logging configuration
33 self tests completed in 0.09 seconds
SSH command line: ['ssh', '-i', '/home/myuser/.ssh/borg-at-arch-bees', 'borg@arch-bees', 'borg', 'serve', '--debug']
Remote: using builtin fallback logging configuration
Remote: 33 self tests completed in 0.15 seconds
Remote: using builtin fallback logging configuration
Remote: Initialized logging system for JSON-based protocol
Remote: Resolving repository path b'/srv/nfs/backups/borg/laptop-e5440'
Remote: Resolved repository path to '/srv/nfs/backups/borg/laptop-e5440'
Remote: Verified integrity of /srv/nfs/backups/borg/laptop-e5440/index.810
RemoteRepository: 230 B bytes sent, 4.60 kB bytes received, 4 messages sent
Local Exception
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 5213, in main
    exit_code = archiver.run(args)
                ^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 5144, in run
    return set_ec(func(args))
                  ^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/archiver.py", line 170, in wrapper
    kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/helpers/manifest.py", line 187, in load
    key = key_factory(repository, cdata)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 134, in key_factory
    return identify_key(manifest_data).detect(repository, manifest_data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 598, in detect
    if not key.load(target, passphrase):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 767, in load
    success = self._load(key_data, passphrase)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 612, in _load
    data = self.decrypt_key_file(cdata, passphrase)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/crypto/key.py", line 630, in decrypt_key_file
    data = unpacker.unpack()
           ^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/borg/helpers/msgpack.py", line 109, in unpack
    return super().unpack()
           ^^^^^^^^^^^^^^^^
  File "msgpack/_unpacker.pyx", line 504, in msgpack._cmsgpack.Unpacker.unpack
  File "msgpack/_unpacker.pyx", line 479, in msgpack._cmsgpack.Unpacker._unpack
msgpack.exceptions.OutOfData: No more data to unpack.

Platform: Linux recluse 5.15.114-2-MANJARO #1 SMP PREEMPT Sun Jun 4 10:32:43 UTC 2023 x86_64
Linux: Unknown Linux  
Borg: 1.2.4  Python: CPython 3.11.3 msgpack: 1.0.5 fuse: llfuse 1.4.2 [pyfuse3,llfuse]
PID: 3043302  CWD: /home/myuser/borg_backups
sys.argv: ['/usr/bin/borg', 'list', '--debug', 'borg@arch-bees:/srv/nfs/backups/borg/laptop-e5440']
SSH_ORIGINAL_COMMAND: None
```
It looks like the key file it "found" does not contain an encrypted key.
It might have a line 1 like `BORG_KEY <REPOID>`, but some base64 encoded encrypted key should be in line 2+.

Maybe put a `print(target)` before line 598, so you see what filename it is using.

This is how a key file looks like:
```
BORG_KEY 74df3e2adbf247615b922a498186830a9b8fe4087db9fc9596f5dfd410f39048
hqlhbGdvcml0aG2mc2hhMjU2pGRhdGHaAN53Doa4jhCaFsK1Zbo0qIIbzSiXKnXr646IDJ
HAAkXhDuF8Krxwlicp5XcaudLJ3NK6XRxCTawtpEd8az+vZkEuOfM/ymO+FlEyanb1wbhP
CFc2k4ZWhAtdD/RXNVqwxvrSRW9/SPgXEAcIvmiX5K6+NG2Q9/4hSZPNNd/GwM/Px+Ym8q
S2ciECcm++GSAu+MebL49Tpj+6Bnlx73mpJ+1H651GJt5GH4w5fWqP/cWu85I3NDtGUM0W
Z0yWj7eNNfKbV7SI2LFOGcOzTSHxvbPp4XBXGBGKX5ORGMd3K/CkaGFzaNoAIB2NF5WK6r
+FdiGWkdmLi/QYPKMLzXjWQPT5bB8nvNwRqml0ZXJhdGlvbnPOAAGGoKRzYWx02gAgd4rL
qSEa28qfCI2Vuw6K2YXJaWh6IzYdKnCJXK28FhWndmVyc2lvbgE=
```
- 74df... is the repo id
- hqlh...vbgE= is the base64 encoded encrypted key

To clarify, it appears that the key file has two lines in it?

If you see much less than the example I gave above, something is wrong there.
I have the equivalent of what you show above, but everything is on a single line... I think that might be the issue?
The first line **must only** have `BORG_KEY <REPOID>` (`<REPOID>` is 256bits == 64 hex digits).
The encrypted key **must be in line 2+**.

Maybe the encrypted key must not be split in several lines as in my example, that is just how borg 1.2.4 generated it on my machine / using my python. But IIRC the base64 decoder can tolerate white space differences.

# enhancement idea

`sanity_check` method only checks `BORG_KEY <REPOID>`, but not the `\n` after that nor that line 2+ contains something base64.
It was the absence of newlines in the key file! When I created the repo originally I copied the output of the keyfile into my password manager. That operation somehow managed to lump both lines together. I am not sure if that was due to how Borg outputs the key file data or if it was a mistake on my part (it was a while ago).

My notes in my pw manager state: `(all text below should be provided as a SINGLE LINE)` so at the time I clearly believed the keyfile was a one-liner which is not the case.

I am able to access my data now having split the keyfile between the repo id the key below using a newline char.

For those who might have this issue in the future, you can verify what your repo id is by going to the server and looking at the contents of the `config` file, e.g.:
```
cat /srv/nfs/backups/borg/laptop-e5440
...
[repository]
version = 1
segments_per_dir = 1000
max_segment_size = 524288000
append_only = 0
storage_quota = 0
additional_free_space = 0
id = cded13c4d4f9de35e3e66cddd731b64d679097a33a77c9696cfbfb031b5bb45c
```

@ThomasWaldmann @elandorr thank you so much for assisting me here. It was a silly mistake on my end, I hope this will help others in the future. I also hope to contribute somehow to this project as it has been really useful for me. I appreciate all of the hard work.
Resolved per previous comment (user error). Thanks again folks.
Reopening for improving the sanitity_check / error messages.

Resolving patch:
diff --git a/src/borg/cache.py b/src/borg/cache.py
--- a/src/borg/cache.py
+++ b/src/borg/cache.py
@@ -1049,6 +1049,9 @@ def memorize_file(self, hashed_path, path_hash, st, ids):
         elif "m" in cache_mode:
             cmtime_type = "mtime"
             cmtime_ns = safe_ns(st.st_mtime_ns)
+        else:  # neither 'c' nor 'm' in cache_mode, avoid UnboundLocalError
+            cmtime_type = "ctime"
+            cmtime_ns = safe_ns(st.st_ctime_ns)
         entry = FileCacheEntry(
             age=0, inode=st.st_ino, size=st.st_size, cmtime=int_to_timestamp(cmtime_ns), chunk_ids=ids
         )
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
@@ -1,3 +1,4 @@
+import binascii
 import hmac
 import os
 import textwrap
@@ -615,7 +616,31 @@ def sanity_check(self, filename, id):
                 raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
             if fd.read(len(repo_id)) != repo_id:
                 raise KeyfileMismatchError(self.repository._location.canonical_path(), filename)
-            return filename
+        # we get here if it really looks like a borg key for this repo,
+        # do some more checks that are close to how borg reads/parses the key.
+        with open(filename, "r") as fd:
+            lines = fd.readlines()
+            if len(lines) < 2:
+                logger.warning(f"borg key sanity check: expected 2+ lines total. [{filename}]")
+                raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
+            if len(lines[0].rstrip()) > len(file_id) + len(repo_id):
+                logger.warning(f"borg key sanity check: key line 1 seems too long. [{filename}]")
+                raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
+            key_b64 = "".join(lines[1:])
+            try:
+                key = a2b_base64(key_b64)
+            except binascii.Error:
+                logger.warning(f"borg key sanity check: key line 2+ does not look like base64. [{filename}]")
+                raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
+            if len(key) < 20:
+                # this is in no way a precise check, usually we have about 400b key data.
+                logger.warning(
+                    f"borg key sanity check: binary encrypted key data from key line 2+ suspiciously short."
+                    f" [{filename}]"
+                )
+                raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
+        # looks good!
+        return filename
 
     def find_key(self):
         if self.STORAGE == KeyBlobStorage.KEYFILE:


Test patch: