Просмотр исходного кода

Merge pull request #8104 from ThomasWaldmann/error-msg-bad-nonce-file2-master

refactor: use less binascii
TW 1 год назад
Родитель
Сommit
2d31b027c7

+ 2 - 8
src/borg/archiver/config_cmd.py

@@ -1,13 +1,12 @@
 import argparse
 import argparse
 import configparser
 import configparser
-from binascii import unhexlify
 
 
 from ._common import with_repository
 from ._common import with_repository
 from ..cache import Cache, assert_secure
 from ..cache import Cache, assert_secure
 from ..constants import *  # NOQA
 from ..constants import *  # NOQA
 from ..helpers import Error, CommandError
 from ..helpers import Error, CommandError
 from ..helpers import Location
 from ..helpers import Location
-from ..helpers import parse_file_size
+from ..helpers import parse_file_size, hex_to_bin
 from ..manifest import Manifest
 from ..manifest import Manifest
 
 
 from ..logger import create_logger
 from ..logger import create_logger
@@ -46,12 +45,7 @@ class ConfigMixIn:
                     raise ValueError("Invalid value")
                     raise ValueError("Invalid value")
             elif name in ["id"]:
             elif name in ["id"]:
                 if check_value:
                 if check_value:
-                    try:
-                        bin_id = unhexlify(value)
-                    except:  # noqa
-                        raise ValueError("Invalid value, must be 64 hex digits") from None
-                    if len(bin_id) != 32:
-                        raise ValueError("Invalid value, must be 64 hex digits")
+                    hex_to_bin(value, length=32)
             else:
             else:
                 raise ValueError("Invalid name")
                 raise ValueError("Invalid name")
 
 

+ 9 - 18
src/borg/archiver/debug_cmd.py

@@ -1,5 +1,4 @@
 import argparse
 import argparse
-from binascii import unhexlify, hexlify
 import functools
 import functools
 import json
 import json
 import textwrap
 import textwrap
@@ -9,7 +8,7 @@ from ..compress import CompressionSpec
 from ..constants import *  # NOQA
 from ..constants import *  # NOQA
 from ..helpers import msgpack
 from ..helpers import msgpack
 from ..helpers import sysinfo
 from ..helpers import sysinfo
-from ..helpers import bin_to_hex, prepare_dump_dict
+from ..helpers import bin_to_hex, hex_to_bin, prepare_dump_dict
 from ..helpers import dash_open
 from ..helpers import dash_open
 from ..helpers import StableDict
 from ..helpers import StableDict
 from ..helpers import positive_int_validator, archivename_validator
 from ..helpers import positive_int_validator, archivename_validator
@@ -179,7 +178,7 @@ class DebugMixIn:
         wanted = args.wanted
         wanted = args.wanted
         try:
         try:
             if wanted.startswith("hex:"):
             if wanted.startswith("hex:"):
-                wanted = unhexlify(wanted[4:])
+                wanted = hex_to_bin(wanted[4:])
             elif wanted.startswith("str:"):
             elif wanted.startswith("str:"):
                 wanted = wanted[4:].encode()
                 wanted = wanted[4:].encode()
             else:
             else:
@@ -235,9 +234,7 @@ class DebugMixIn:
         """get object contents from the repository and write it into file"""
         """get object contents from the repository and write it into file"""
         hex_id = args.id
         hex_id = args.id
         try:
         try:
-            id = unhexlify(hex_id)
-            if len(id) != 32:  # 256bit
-                raise ValueError("id must be 256bits or 64 hex digits")
+            id = hex_to_bin(hex_id, length=32)
         except ValueError as err:
         except ValueError as err:
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
         try:
         try:
@@ -264,9 +261,7 @@ class DebugMixIn:
         # get the object from id
         # get the object from id
         hex_id = args.id
         hex_id = args.id
         try:
         try:
-            id = unhexlify(hex_id)
-            if len(id) != 32:  # 256bit
-                raise ValueError("id must be 256bits or 64 hex digits")
+            id = hex_to_bin(hex_id, length=32)
         except ValueError as err:
         except ValueError as err:
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
 
 
@@ -289,9 +284,7 @@ class DebugMixIn:
         # get the object from id
         # get the object from id
         hex_id = args.id
         hex_id = args.id
         try:
         try:
-            id = unhexlify(hex_id)
-            if len(id) != 32:  # 256bit
-                raise ValueError("id must be 256bits or 64 hex digits")
+            id = hex_to_bin(hex_id, length=32)
         except ValueError as err:
         except ValueError as err:
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
 
 
@@ -315,9 +308,7 @@ class DebugMixIn:
             data = f.read()
             data = f.read()
         hex_id = args.id
         hex_id = args.id
         try:
         try:
-            id = unhexlify(hex_id)
-            if len(id) != 32:  # 256bit
-                raise ValueError("id must be 256bits or 64 hex digits")
+            id = hex_to_bin(hex_id, length=32)
         except ValueError as err:
         except ValueError as err:
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
             raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
 
 
@@ -331,7 +322,7 @@ class DebugMixIn:
         modified = False
         modified = False
         for hex_id in args.ids:
         for hex_id in args.ids:
             try:
             try:
-                id = unhexlify(hex_id)
+                id = hex_to_bin(hex_id, length=32)
             except ValueError:
             except ValueError:
                 print("object id %s is invalid." % hex_id)
                 print("object id %s is invalid." % hex_id)
             else:
             else:
@@ -350,7 +341,7 @@ class DebugMixIn:
         """display refcounts for the objects with the given IDs"""
         """display refcounts for the objects with the given IDs"""
         for hex_id in args.ids:
         for hex_id in args.ids:
             try:
             try:
-                id = unhexlify(hex_id)
+                id = hex_to_bin(hex_id, length=32)
             except ValueError:
             except ValueError:
                 print("object id %s is invalid." % hex_id)
                 print("object id %s is invalid." % hex_id)
             else:
             else:
@@ -370,7 +361,7 @@ class DebugMixIn:
                 segments=repository.segments,
                 segments=repository.segments,
                 compact=repository.compact,
                 compact=repository.compact,
                 storage_quota_use=repository.storage_quota_use,
                 storage_quota_use=repository.storage_quota_use,
-                shadow_index={hexlify(k).decode(): v for k, v in repository.shadow_index.items()},
+                shadow_index={bin_to_hex(k): v for k, v in repository.shadow_index.items()},
             )
             )
             with dash_open(args.path, "w") as fd:
             with dash_open(args.path, "w") as fd:
                 json.dump(hints, fd, indent=4)
                 json.dump(hints, fd, indent=4)

+ 4 - 5
src/borg/cache.py

@@ -2,7 +2,6 @@ import configparser
 import os
 import os
 import shutil
 import shutil
 import stat
 import stat
-from binascii import unhexlify
 from collections import namedtuple
 from collections import namedtuple
 from time import perf_counter
 from time import perf_counter
 
 
@@ -17,7 +16,7 @@ from .hashindex import ChunkIndex, ChunkIndexEntry, CacheSynchronizer
 from .helpers import Location
 from .helpers import Location
 from .helpers import Error
 from .helpers import Error
 from .helpers import get_cache_dir, get_security_dir
 from .helpers import get_cache_dir, get_security_dir
-from .helpers import bin_to_hex, parse_stringified_list
+from .helpers import bin_to_hex, hex_to_bin, parse_stringified_list
 from .helpers import format_file_size
 from .helpers import format_file_size
 from .helpers import safe_ns
 from .helpers import safe_ns
 from .helpers import yes
 from .helpers import yes
@@ -299,7 +298,7 @@ class CacheConfig:
             self._config.read_file(fd)
             self._config.read_file(fd)
         self._check_upgrade(self.config_path)
         self._check_upgrade(self.config_path)
         self.id = self._config.get("cache", "repository")
         self.id = self._config.get("cache", "repository")
-        self.manifest_id = unhexlify(self._config.get("cache", "manifest"))
+        self.manifest_id = hex_to_bin(self._config.get("cache", "manifest"))
         self.timestamp = self._config.get("cache", "timestamp", fallback=None)
         self.timestamp = self._config.get("cache", "timestamp", fallback=None)
         self.key_type = self._config.get("cache", "key_type", fallback=None)
         self.key_type = self._config.get("cache", "key_type", fallback=None)
         self.ignored_features = set(parse_stringified_list(self._config.get("cache", "ignored_features", fallback="")))
         self.ignored_features = set(parse_stringified_list(self._config.get("cache", "ignored_features", fallback="")))
@@ -733,8 +732,8 @@ class LocalCache(CacheStatsMixin):
                 fns = os.listdir(archive_path)
                 fns = os.listdir(archive_path)
                 # filenames with 64 hex digits == 256bit,
                 # filenames with 64 hex digits == 256bit,
                 # or compact indices which are 64 hex digits + ".compact"
                 # or compact indices which are 64 hex digits + ".compact"
-                return {unhexlify(fn) for fn in fns if len(fn) == 64} | {
-                    unhexlify(fn[:64]) for fn in fns if len(fn) == 72 and fn.endswith(".compact")
+                return {hex_to_bin(fn) for fn in fns if len(fn) == 64} | {
+                    hex_to_bin(fn[:64]) for fn in fns if len(fn) == 72 and fn.endswith(".compact")
                 }
                 }
             else:
             else:
                 return set()
                 return set()

+ 4 - 5
src/borg/crypto/key.py

@@ -2,7 +2,6 @@ import binascii
 import hmac
 import hmac
 import os
 import os
 import textwrap
 import textwrap
-from binascii import a2b_base64, b2a_base64, hexlify
 from hashlib import sha256, pbkdf2_hmac
 from hashlib import sha256, pbkdf2_hmac
 from typing import Literal, Callable, ClassVar
 from typing import Literal, Callable, ClassVar
 
 
@@ -382,7 +381,7 @@ class FlexiKey:
         return key
         return key
 
 
     def _load(self, key_data, passphrase):
     def _load(self, key_data, passphrase):
-        cdata = a2b_base64(key_data)
+        cdata = binascii.a2b_base64(key_data)
         data = self.decrypt_key_file(cdata, passphrase)
         data = self.decrypt_key_file(cdata, passphrase)
         if data:
         if data:
             data = msgpack.unpackb(data)
             data = msgpack.unpackb(data)
@@ -507,7 +506,7 @@ class FlexiKey:
             chunk_seed=self.chunk_seed,
             chunk_seed=self.chunk_seed,
         )
         )
         data = self.encrypt_key_file(msgpack.packb(key.as_dict()), passphrase, algorithm)
         data = self.encrypt_key_file(msgpack.packb(key.as_dict()), passphrase, algorithm)
-        key_data = "\n".join(textwrap.wrap(b2a_base64(data).decode("ascii")))
+        key_data = "\n".join(textwrap.wrap(binascii.b2a_base64(data).decode("ascii")))
         return key_data
         return key_data
 
 
     def change_passphrase(self, passphrase=None):
     def change_passphrase(self, passphrase=None):
@@ -547,7 +546,7 @@ class FlexiKey:
 
 
     def sanity_check(self, filename, id):
     def sanity_check(self, filename, id):
         file_id = self.FILE_ID.encode() + b" "
         file_id = self.FILE_ID.encode() + b" "
-        repo_id = hexlify(id)
+        repo_id = bin_to_hex(id).encode("ascii")
         with open(filename, "rb") as fd:
         with open(filename, "rb") as fd:
             # we do the magic / id check in binary mode to avoid stumbling over
             # we do the magic / id check in binary mode to avoid stumbling over
             # decoding errors if somebody has binary files in the keys dir for some reason.
             # decoding errors if somebody has binary files in the keys dir for some reason.
@@ -567,7 +566,7 @@ class FlexiKey:
                 raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
                 raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
             key_b64 = "".join(lines[1:])
             key_b64 = "".join(lines[1:])
             try:
             try:
-                key = a2b_base64(key_b64)
+                key = binascii.a2b_base64(key_b64)
             except binascii.Error:
             except binascii.Error:
                 logger.warning(f"borg key sanity check: key line 2+ does not look like base64. [{filename}]")
                 logger.warning(f"borg key sanity check: key line 2+ does not look like base64. [{filename}]")
                 raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
                 raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)

+ 6 - 7
src/borg/crypto/keymanager.py

@@ -1,10 +1,9 @@
 import binascii
 import binascii
 import pkgutil
 import pkgutil
 import textwrap
 import textwrap
-from binascii import unhexlify, a2b_base64, b2a_base64
 from hashlib import sha256
 from hashlib import sha256
 
 
-from ..helpers import Error, yes, bin_to_hex, dash_open
+from ..helpers import Error, yes, bin_to_hex, hex_to_bin, dash_open
 from ..manifest import Manifest, NoManifestError
 from ..manifest import Manifest, NoManifestError
 from ..repository import Repository
 from ..repository import Repository
 from ..repoobj import RepoObj
 from ..repoobj import RepoObj
@@ -127,7 +126,7 @@ class KeyManager:
 
 
         export = "To restore key use borg key import --paper /path/to/repo\n\n"
         export = "To restore key use borg key import --paper /path/to/repo\n\n"
 
 
-        binary = a2b_base64(self.keyblob)
+        binary = binascii.a2b_base64(self.keyblob)
         export += "BORG PAPER KEY v1\n"
         export += "BORG PAPER KEY v1\n"
         lines = (len(binary) + 17) // 18
         lines = (len(binary) + 17) // 18
         repoid = bin_to_hex(self.repository.id)[:18]
         repoid = bin_to_hex(self.repository.id)[:18]
@@ -218,9 +217,9 @@ class KeyManager:
                         print("each line must contain exactly one '-', try again")
                         print("each line must contain exactly one '-', try again")
                         continue
                         continue
                     try:
                     try:
-                        part = unhexlify(data)
-                    except binascii.Error:
-                        print("only characters 0-9 and a-f and '-' are valid, try again")
+                        part = hex_to_bin(data)
+                    except ValueError as e:
+                        print(f"only characters 0-9 and a-f and '-' are valid, try again [{e}]")
                         continue
                         continue
                     if sha256_truncated(idx.to_bytes(2, byteorder="big") + part, 2) != checksum:
                     if sha256_truncated(idx.to_bytes(2, byteorder="big") + part, 2) != checksum:
                         print(f"line checksum did not match, try line {idx} again")
                         print(f"line checksum did not match, try line {idx} again")
@@ -234,7 +233,7 @@ class KeyManager:
                     print("The overall checksum did not match, retry or enter a blank line to abort.")
                     print("The overall checksum did not match, retry or enter a blank line to abort.")
                     continue
                     continue
 
 
-                self.keyblob = "\n".join(textwrap.wrap(b2a_base64(result).decode("ascii"))) + "\n"
+                self.keyblob = "\n".join(textwrap.wrap(binascii.b2a_base64(result).decode("ascii"))) + "\n"
                 self.store_keyblob(args)
                 self.store_keyblob(args)
                 break
                 break
 
 

+ 2 - 3
src/borg/repository.py

@@ -5,7 +5,6 @@ import shutil
 import stat
 import stat
 import struct
 import struct
 import time
 import time
-from binascii import unhexlify
 from collections import defaultdict
 from collections import defaultdict
 from configparser import ConfigParser
 from configparser import ConfigParser
 from datetime import datetime, timezone
 from datetime import datetime, timezone
@@ -18,7 +17,7 @@ from .hashindex import NSIndexEntry, NSIndex, NSIndex1, hashindex_variant
 from .helpers import Error, ErrorWithTraceback, IntegrityError, format_file_size, parse_file_size
 from .helpers import Error, ErrorWithTraceback, IntegrityError, format_file_size, parse_file_size
 from .helpers import Location
 from .helpers import Location
 from .helpers import ProgressIndicatorPercent
 from .helpers import ProgressIndicatorPercent
-from .helpers import bin_to_hex
+from .helpers import bin_to_hex, hex_to_bin
 from .helpers import secure_erase, safe_unlink
 from .helpers import secure_erase, safe_unlink
 from .helpers import msgpack
 from .helpers import msgpack
 from .helpers.lrucache import LRUCache
 from .helpers.lrucache import LRUCache
@@ -490,7 +489,7 @@ class Repository:
         if self.storage_quota is None:
         if self.storage_quota is None:
             # self.storage_quota is None => no explicit storage_quota was specified, use repository setting.
             # self.storage_quota is None => no explicit storage_quota was specified, use repository setting.
             self.storage_quota = parse_file_size(self.config.get("repository", "storage_quota", fallback=0))
             self.storage_quota = parse_file_size(self.config.get("repository", "storage_quota", fallback=0))
-        self.id = unhexlify(self.config.get("repository", "id").strip())
+        self.id = hex_to_bin(self.config.get("repository", "id").strip(), length=32)
         self.io = LoggedIO(self.path, self.max_segment_size, self.segments_per_dir)
         self.io = LoggedIO(self.path, self.max_segment_size, self.segments_per_dir)
 
 
     def _load_hints(self):
     def _load_hints(self):

+ 9 - 9
src/borg/testsuite/archiver/key_cmds.py

@@ -1,5 +1,5 @@
+import binascii
 import os
 import os
-from binascii import unhexlify, b2a_base64, a2b_base64
 
 
 import pytest
 import pytest
 
 
@@ -7,7 +7,7 @@ from ...constants import *  # NOQA
 from ...crypto.key import AESOCBRepoKey, AESOCBKeyfileKey, CHPOKeyfileKey, Passphrase
 from ...crypto.key import AESOCBRepoKey, AESOCBKeyfileKey, CHPOKeyfileKey, Passphrase
 from ...crypto.keymanager import RepoIdMismatch, NotABorgKeyFile
 from ...crypto.keymanager import RepoIdMismatch, NotABorgKeyFile
 from ...helpers import EXIT_ERROR, CommandError
 from ...helpers import EXIT_ERROR, CommandError
-from ...helpers import bin_to_hex
+from ...helpers import bin_to_hex, hex_to_bin
 from ...helpers import msgpack
 from ...helpers import msgpack
 from ...repository import Repository
 from ...repository import Repository
 from .. import key
 from .. import key
@@ -223,12 +223,12 @@ def test_key_export_paperkey(archivers, request):
     repo_id = "e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239"
     repo_id = "e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239"
     export_file = archiver.output_path + "/exported"
     export_file = archiver.output_path + "/exported"
     cmd(archiver, "rcreate", KF_ENCRYPTION)
     cmd(archiver, "rcreate", KF_ENCRYPTION)
-    _set_repository_id(archiver.repository_path, unhexlify(repo_id))
+    _set_repository_id(archiver.repository_path, hex_to_bin(repo_id))
     key_file = archiver.keys_path + "/" + os.listdir(archiver.keys_path)[0]
     key_file = archiver.keys_path + "/" + os.listdir(archiver.keys_path)[0]
 
 
     with open(key_file, "w") as fd:
     with open(key_file, "w") as fd:
         fd.write(CHPOKeyfileKey.FILE_ID + " " + repo_id + "\n")
         fd.write(CHPOKeyfileKey.FILE_ID + " " + repo_id + "\n")
-        fd.write(b2a_base64(b"abcdefghijklmnopqrstu").decode())
+        fd.write(binascii.b2a_base64(b"abcdefghijklmnopqrstu").decode())
 
 
     cmd(archiver, "key", "export", "--paper", export_file)
     cmd(archiver, "key", "export", "--paper", export_file)
 
 
@@ -251,12 +251,12 @@ def test_key_import_paperkey(archivers, request):
     archiver = request.getfixturevalue(archivers)
     archiver = request.getfixturevalue(archivers)
     repo_id = "e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239"
     repo_id = "e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239"
     cmd(archiver, "rcreate", KF_ENCRYPTION)
     cmd(archiver, "rcreate", KF_ENCRYPTION)
-    _set_repository_id(archiver.repository_path, unhexlify(repo_id))
+    _set_repository_id(archiver.repository_path, hex_to_bin(repo_id))
 
 
     key_file = archiver.keys_path + "/" + os.listdir(archiver.keys_path)[0]
     key_file = archiver.keys_path + "/" + os.listdir(archiver.keys_path)[0]
     with open(key_file, "w") as fd:
     with open(key_file, "w") as fd:
         fd.write(AESOCBKeyfileKey.FILE_ID + " " + repo_id + "\n")
         fd.write(AESOCBKeyfileKey.FILE_ID + " " + repo_id + "\n")
-        fd.write(b2a_base64(b"abcdefghijklmnopqrstu").decode())
+        fd.write(binascii.b2a_base64(b"abcdefghijklmnopqrstu").decode())
 
 
     typed_input = (
     typed_input = (
         b"2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41  02\n"  # Forgot to type "-"
         b"2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41  02\n"  # Forgot to type "-"
@@ -298,7 +298,7 @@ def test_init_defaults_to_argon2(archivers, request):
     archiver = request.getfixturevalue(archivers)
     archiver = request.getfixturevalue(archivers)
     cmd(archiver, "rcreate", RK_ENCRYPTION)
     cmd(archiver, "rcreate", RK_ENCRYPTION)
     with Repository(archiver.repository_path) as repository:
     with Repository(archiver.repository_path) as repository:
-        key = msgpack.unpackb(a2b_base64(repository.load_key()))
+        key = msgpack.unpackb(binascii.a2b_base64(repository.load_key()))
         assert key["algorithm"] == "argon2 chacha20-poly1305"
         assert key["algorithm"] == "argon2 chacha20-poly1305"
 
 
 
 
@@ -309,7 +309,7 @@ def test_change_passphrase_does_not_change_algorithm_argon2(archivers, request):
     cmd(archiver, "key", "change-passphrase")
     cmd(archiver, "key", "change-passphrase")
 
 
     with Repository(archiver.repository_path) as repository:
     with Repository(archiver.repository_path) as repository:
-        key = msgpack.unpackb(a2b_base64(repository.load_key()))
+        key = msgpack.unpackb(binascii.a2b_base64(repository.load_key()))
         assert key["algorithm"] == "argon2 chacha20-poly1305"
         assert key["algorithm"] == "argon2 chacha20-poly1305"
 
 
 
 
@@ -319,5 +319,5 @@ def test_change_location_does_not_change_algorithm_argon2(archivers, request):
     cmd(archiver, "key", "change-location", "repokey")
     cmd(archiver, "key", "change-location", "repokey")
 
 
     with Repository(archiver.repository_path) as repository:
     with Repository(archiver.repository_path) as repository:
-        key = msgpack.unpackb(a2b_base64(repository.load_key()))
+        key = msgpack.unpackb(binascii.a2b_base64(repository.load_key()))
         assert key["algorithm"] == "argon2 chacha20-poly1305"
         assert key["algorithm"] == "argon2 chacha20-poly1305"

+ 2 - 10
src/borg/testsuite/archiver/rcompress_cmd.py

@@ -1,10 +1,10 @@
 import os
 import os
-from binascii import hexlify
 
 
 from ...constants import *  # NOQA
 from ...constants import *  # NOQA
 from ...repository import Repository
 from ...repository import Repository
 from ...manifest import Manifest
 from ...manifest import Manifest
 from ...compress import ZSTD, ZLIB, LZ4, CNONE
 from ...compress import ZSTD, ZLIB, LZ4, CNONE
+from ...helpers import bin_to_hex
 
 
 from . import create_regular_file, cmd, RK_ENCRYPTION
 from . import create_regular_file, cmd, RK_ENCRYPTION
 
 
@@ -27,15 +27,7 @@ def test_rcompress(archiver):
                     )  # will also decompress according to metadata
                     )  # will also decompress according to metadata
                     m_olevel = meta.get("olevel", -1)
                     m_olevel = meta.get("olevel", -1)
                     m_psize = meta.get("psize", -1)
                     m_psize = meta.get("psize", -1)
-                    print(
-                        hexlify(id).decode(),
-                        meta["ctype"],
-                        meta["clevel"],
-                        meta["csize"],
-                        meta["size"],
-                        m_olevel,
-                        m_psize,
-                    )
+                    print(bin_to_hex(id), meta["ctype"], meta["clevel"], meta["csize"], meta["size"], m_olevel, m_psize)
                     # this is not as easy as one thinks due to the DecidingCompressor choosing the smallest of
                     # this is not as easy as one thinks due to the DecidingCompressor choosing the smallest of
                     # (desired compressed, lz4 compressed, not compressed).
                     # (desired compressed, lz4 compressed, not compressed).
                     assert meta["ctype"] in (ctype, LZ4.ID, CNONE.ID)
                     assert meta["ctype"] in (ctype, LZ4.ID, CNONE.ID)

+ 2 - 4
src/borg/testsuite/checksums.py

@@ -1,7 +1,5 @@
-from binascii import unhexlify
-
 from .. import checksums
 from .. import checksums
-from ..helpers import bin_to_hex
+from ..helpers import bin_to_hex, hex_to_bin
 
 
 
 
 def test_xxh64():
 def test_xxh64():
@@ -10,7 +8,7 @@ def test_xxh64():
     assert (
     assert (
         bin_to_hex(
         bin_to_hex(
             checksums.xxh64(
             checksums.xxh64(
-                unhexlify(
+                hex_to_bin(
                     "6f663f01c118abdea553373d5eae44e7dac3b6829b46b9bbeff202b6c592c22d724"
                     "6f663f01c118abdea553373d5eae44e7dac3b6829b46b9bbeff202b6c592c22d724"
                     "fb3d25a347cca6c5b8f20d567e4bb04b9cfa85d17f691590f9a9d32e8ccc9102e9d"
                     "fb3d25a347cca6c5b8f20d567e4bb04b9cfa85d17f691590f9a9d32e8ccc9102e9d"
                     "cf8a7e6716280cd642ce48d03fdf114c9f57c20d9472bb0f81c147645e6fa3d331"
                     "cf8a7e6716280cd642ce48d03fdf114c9f57c20d9472bb0f81c147645e6fa3d331"

+ 2 - 2
src/borg/testsuite/chunker_slow.py

@@ -1,10 +1,10 @@
 from io import BytesIO
 from io import BytesIO
-from binascii import unhexlify
 
 
 from .chunker import cf
 from .chunker import cf
 from ..chunker import Chunker
 from ..chunker import Chunker
 from ..crypto.low_level import blake2b_256
 from ..crypto.low_level import blake2b_256
 from ..constants import *  # NOQA
 from ..constants import *  # NOQA
+from ..helpers import hex_to_bin
 
 
 
 
 def test_chunkpoints_unchanged():
 def test_chunkpoints_unchanged():
@@ -34,4 +34,4 @@ def test_chunkpoints_unchanged():
     # The "correct" hash below matches the existing chunker behavior.
     # The "correct" hash below matches the existing chunker behavior.
     # Future chunker optimisations must not change this, or existing repos will bloat.
     # Future chunker optimisations must not change this, or existing repos will bloat.
     overall_hash = blake2b_256(b"", b"".join(runs))
     overall_hash = blake2b_256(b"", b"".join(runs))
-    assert overall_hash == unhexlify("b559b0ac8df8daaa221201d018815114241ea5c6609d98913cd2246a702af4e3")
+    assert overall_hash == hex_to_bin("b559b0ac8df8daaa221201d018815114241ea5c6609d98913cd2246a702af4e3")

+ 25 - 26
src/borg/testsuite/crypto.py

@@ -1,7 +1,6 @@
 # Note: these tests are part of the self test, do not use or import pytest functionality here.
 # Note: these tests are part of the self test, do not use or import pytest functionality here.
 #       See borg.selftest for details. If you add/remove test methods, update SELFTEST_COUNT
 #       See borg.selftest for details. If you add/remove test methods, update SELFTEST_COUNT
 
 
-from binascii import hexlify
 from unittest.mock import MagicMock
 from unittest.mock import MagicMock
 import unittest
 import unittest
 
 
@@ -9,7 +8,7 @@ from ..crypto.low_level import AES256_CTR_HMAC_SHA256, AES256_OCB, CHACHA20_POLY
 from ..crypto.low_level import bytes_to_long, bytes_to_int, long_to_bytes
 from ..crypto.low_level import bytes_to_long, bytes_to_int, long_to_bytes
 from ..crypto.low_level import AES, hmac_sha256
 from ..crypto.low_level import AES, hmac_sha256
 from ..crypto.key import CHPOKeyfileKey, AESOCBRepoKey, FlexiKey
 from ..crypto.key import CHPOKeyfileKey, AESOCBRepoKey, FlexiKey
-from ..helpers import msgpack
+from ..helpers import msgpack, bin_to_hex
 
 
 from . import BaseTestCase
 from . import BaseTestCase
 
 
@@ -46,10 +45,10 @@ class CryptoTestCase(BaseTestCase):
         mac = hdr_mac_iv_cdata[1:33]
         mac = hdr_mac_iv_cdata[1:33]
         iv = hdr_mac_iv_cdata[33:41]
         iv = hdr_mac_iv_cdata[33:41]
         cdata = hdr_mac_iv_cdata[41:]
         cdata = hdr_mac_iv_cdata[41:]
-        self.assert_equal(hexlify(hdr), b"42")
-        self.assert_equal(hexlify(mac), b"af90b488b0cc4a8f768fe2d6814fa65aec66b148135e54f7d4d29a27f22f57a8")
-        self.assert_equal(hexlify(iv), b"0000000000000000")
-        self.assert_equal(hexlify(cdata), b"c6efb702de12498f34a2c2bbc8149e759996d08bf6dc5c610aefc0c3a466")
+        self.assert_equal(bin_to_hex(hdr), "42")
+        self.assert_equal(bin_to_hex(mac), "af90b488b0cc4a8f768fe2d6814fa65aec66b148135e54f7d4d29a27f22f57a8")
+        self.assert_equal(bin_to_hex(iv), "0000000000000000")
+        self.assert_equal(bin_to_hex(cdata), "c6efb702de12498f34a2c2bbc8149e759996d08bf6dc5c610aefc0c3a466")
         self.assert_equal(cs.next_iv(), 2)
         self.assert_equal(cs.next_iv(), 2)
         # auth-then-decrypt
         # auth-then-decrypt
         cs = AES256_CTR_HMAC_SHA256(mac_key, enc_key, header_len=len(header), aad_offset=1)
         cs = AES256_CTR_HMAC_SHA256(mac_key, enc_key, header_len=len(header), aad_offset=1)
@@ -74,10 +73,10 @@ class CryptoTestCase(BaseTestCase):
         mac = hdr_mac_iv_cdata[3:35]
         mac = hdr_mac_iv_cdata[3:35]
         iv = hdr_mac_iv_cdata[35:43]
         iv = hdr_mac_iv_cdata[35:43]
         cdata = hdr_mac_iv_cdata[43:]
         cdata = hdr_mac_iv_cdata[43:]
-        self.assert_equal(hexlify(hdr), b"123456")
-        self.assert_equal(hexlify(mac), b"7659a915d9927072ef130258052351a17ef882692893c3850dd798c03d2dd138")
-        self.assert_equal(hexlify(iv), b"0000000000000000")
-        self.assert_equal(hexlify(cdata), b"c6efb702de12498f34a2c2bbc8149e759996d08bf6dc5c610aefc0c3a466")
+        self.assert_equal(bin_to_hex(hdr), "123456")
+        self.assert_equal(bin_to_hex(mac), "7659a915d9927072ef130258052351a17ef882692893c3850dd798c03d2dd138")
+        self.assert_equal(bin_to_hex(iv), "0000000000000000")
+        self.assert_equal(bin_to_hex(cdata), "c6efb702de12498f34a2c2bbc8149e759996d08bf6dc5c610aefc0c3a466")
         self.assert_equal(cs.next_iv(), 2)
         self.assert_equal(cs.next_iv(), 2)
         # auth-then-decrypt
         # auth-then-decrypt
         cs = AES256_CTR_HMAC_SHA256(mac_key, enc_key, header_len=len(header), aad_offset=1)
         cs = AES256_CTR_HMAC_SHA256(mac_key, enc_key, header_len=len(header), aad_offset=1)
@@ -99,13 +98,13 @@ class CryptoTestCase(BaseTestCase):
             # (ciphersuite class, exp_mac, exp_cdata)
             # (ciphersuite class, exp_mac, exp_cdata)
             (
             (
                 AES256_OCB,
                 AES256_OCB,
-                b"b6909c23c9aaebd9abbe1ff42097652d",
-                b"877ce46d2f62dee54699cebc3ba41d9ab613f7c486778c1b3636664b1493",
+                "b6909c23c9aaebd9abbe1ff42097652d",
+                "877ce46d2f62dee54699cebc3ba41d9ab613f7c486778c1b3636664b1493",
             ),
             ),
             (
             (
                 CHACHA20_POLY1305,
                 CHACHA20_POLY1305,
-                b"fd08594796e0706cde1e8b461e3e0555",
-                b"a093e4b0387526f085d3c40cca84a35230a5c0dd766453b77ba38bcff775",
+                "fd08594796e0706cde1e8b461e3e0555",
+                "a093e4b0387526f085d3c40cca84a35230a5c0dd766453b77ba38bcff775",
             ),
             ),
         ]
         ]
         for cs_cls, exp_mac, exp_cdata in tests:
         for cs_cls, exp_mac, exp_cdata in tests:
@@ -117,10 +116,10 @@ class CryptoTestCase(BaseTestCase):
             iv = hdr_mac_iv_cdata[1:13]
             iv = hdr_mac_iv_cdata[1:13]
             mac = hdr_mac_iv_cdata[13:29]
             mac = hdr_mac_iv_cdata[13:29]
             cdata = hdr_mac_iv_cdata[29:]
             cdata = hdr_mac_iv_cdata[29:]
-            self.assert_equal(hexlify(hdr), b"23")
-            self.assert_equal(hexlify(mac), exp_mac)
-            self.assert_equal(hexlify(iv), b"000000000000000000000000")
-            self.assert_equal(hexlify(cdata), exp_cdata)
+            self.assert_equal(bin_to_hex(hdr), "23")
+            self.assert_equal(bin_to_hex(mac), exp_mac)
+            self.assert_equal(bin_to_hex(iv), "000000000000000000000000")
+            self.assert_equal(bin_to_hex(cdata), exp_cdata)
             self.assert_equal(cs.next_iv(), 1)
             self.assert_equal(cs.next_iv(), 1)
             # auth/decrypt
             # auth/decrypt
             cs = cs_cls(key, iv_int, header_len=len(header), aad_offset=1)
             cs = cs_cls(key, iv_int, header_len=len(header), aad_offset=1)
@@ -142,13 +141,13 @@ class CryptoTestCase(BaseTestCase):
             # (ciphersuite class, exp_mac, exp_cdata)
             # (ciphersuite class, exp_mac, exp_cdata)
             (
             (
                 AES256_OCB,
                 AES256_OCB,
-                b"f2748c412af1c7ead81863a18c2c1893",
-                b"877ce46d2f62dee54699cebc3ba41d9ab613f7c486778c1b3636664b1493",
+                "f2748c412af1c7ead81863a18c2c1893",
+                "877ce46d2f62dee54699cebc3ba41d9ab613f7c486778c1b3636664b1493",
             ),
             ),
             (
             (
                 CHACHA20_POLY1305,
                 CHACHA20_POLY1305,
-                b"b7e7c9a79f2404e14f9aad156bf091dd",
-                b"a093e4b0387526f085d3c40cca84a35230a5c0dd766453b77ba38bcff775",
+                "b7e7c9a79f2404e14f9aad156bf091dd",
+                "a093e4b0387526f085d3c40cca84a35230a5c0dd766453b77ba38bcff775",
             ),
             ),
         ]
         ]
         for cs_cls, exp_mac, exp_cdata in tests:
         for cs_cls, exp_mac, exp_cdata in tests:
@@ -160,10 +159,10 @@ class CryptoTestCase(BaseTestCase):
             iv = hdr_mac_iv_cdata[3:15]
             iv = hdr_mac_iv_cdata[3:15]
             mac = hdr_mac_iv_cdata[15:31]
             mac = hdr_mac_iv_cdata[15:31]
             cdata = hdr_mac_iv_cdata[31:]
             cdata = hdr_mac_iv_cdata[31:]
-            self.assert_equal(hexlify(hdr), b"123456")
-            self.assert_equal(hexlify(mac), exp_mac)
-            self.assert_equal(hexlify(iv), b"000000000000000000000000")
-            self.assert_equal(hexlify(cdata), exp_cdata)
+            self.assert_equal(bin_to_hex(hdr), "123456")
+            self.assert_equal(bin_to_hex(mac), exp_mac)
+            self.assert_equal(bin_to_hex(iv), "000000000000000000000000")
+            self.assert_equal(bin_to_hex(cdata), exp_cdata)
             self.assert_equal(cs.next_iv(), 1)
             self.assert_equal(cs.next_iv(), 1)
             # auth/decrypt
             # auth/decrypt
             cs = cs_cls(key, iv_int, header_len=len(header), aad_offset=1)
             cs = cs_cls(key, iv_int, header_len=len(header), aad_offset=1)

+ 7 - 7
src/borg/testsuite/key.py

@@ -1,10 +1,9 @@
 import tempfile
 import tempfile
-from binascii import hexlify, unhexlify, a2b_base64
+from binascii import a2b_base64
 from unittest.mock import MagicMock
 from unittest.mock import MagicMock
 
 
 import pytest
 import pytest
 
 
-from ..crypto.key import bin_to_hex
 from ..crypto.key import PlaintextKey, AuthenticatedKey, Blake2AuthenticatedKey
 from ..crypto.key import PlaintextKey, AuthenticatedKey, Blake2AuthenticatedKey
 from ..crypto.key import RepoKey, KeyfileKey, Blake2RepoKey, Blake2KeyfileKey
 from ..crypto.key import RepoKey, KeyfileKey, Blake2RepoKey, Blake2KeyfileKey
 from ..crypto.key import AEADKeyBase
 from ..crypto.key import AEADKeyBase
@@ -18,6 +17,7 @@ from ..helpers import IntegrityError
 from ..helpers import Location
 from ..helpers import Location
 from ..helpers import msgpack
 from ..helpers import msgpack
 from ..constants import KEY_ALGORITHMS
 from ..constants import KEY_ALGORITHMS
+from ..helpers import hex_to_bin, bin_to_hex
 
 
 
 
 class TestKey:
 class TestKey:
@@ -35,10 +35,10 @@ class TestKey:
         F84MsMMiqpbz4KVICeBZhfAaTPs4W7BC63qml0ZXJhdGlvbnPOAAGGoKRzYWx02gAgLENQ
         F84MsMMiqpbz4KVICeBZhfAaTPs4W7BC63qml0ZXJhdGlvbnPOAAGGoKRzYWx02gAgLENQ
         2uVCoR7EnAoiRzn8J+orbojKtJlNCnQ31SSC8rendmVyc2lvbgE=""".strip()
         2uVCoR7EnAoiRzn8J+orbojKtJlNCnQ31SSC8rendmVyc2lvbgE=""".strip()
 
 
-    keyfile2_cdata = bytes.fromhex(
+    keyfile2_cdata = hex_to_bin(
         "003be7d57280d1a42add9f3f36ea363bbc5e9349ad01ddec0634a54dd02959e70500000000000003ec063d2cbcacba6b"
         "003be7d57280d1a42add9f3f36ea363bbc5e9349ad01ddec0634a54dd02959e70500000000000003ec063d2cbcacba6b"
     )
     )
-    keyfile2_id = unhexlify("c3fbf14bc001ebcc3cd86e696c13482ed071740927cd7cbe1b01b4bfcee49314")
+    keyfile2_id = hex_to_bin("c3fbf14bc001ebcc3cd86e696c13482ed071740927cd7cbe1b01b4bfcee49314")
 
 
     keyfile_blake2_key_file = """
     keyfile_blake2_key_file = """
         BORG_KEY 0000000000000000000000000000000000000000000000000000000000000000
         BORG_KEY 0000000000000000000000000000000000000000000000000000000000000000
@@ -54,7 +54,7 @@ class TestKey:
         UTHFJg343jqml0ZXJhdGlvbnPOAAGGoKRzYWx02gAgz3YaUZZ/s+UWywj97EY5b4KhtJYi
         UTHFJg343jqml0ZXJhdGlvbnPOAAGGoKRzYWx02gAgz3YaUZZ/s+UWywj97EY5b4KhtJYi
         qkPqtDDxs2j/T7+ndmVyc2lvbgE=""".strip()
         qkPqtDDxs2j/T7+ndmVyc2lvbgE=""".strip()
 
 
-    keyfile_blake2_cdata = bytes.fromhex(
+    keyfile_blake2_cdata = hex_to_bin(
         "04d6040f5ef80e0a8ac92badcbe3dee83b7a6b53d5c9a58c4eed14964cb10ef591040404040404040d1e65cc1f435027"
         "04d6040f5ef80e0a8ac92badcbe3dee83b7a6b53d5c9a58c4eed14964cb10ef591040404040404040d1e65cc1f435027"
     )
     )
     # Verified against b2sum. Entire string passed to BLAKE2, including the padded 64 byte key contained in
     # Verified against b2sum. Entire string passed to BLAKE2, including the padded 64 byte key contained in
@@ -64,7 +64,7 @@ class TestKey:
     # 000000000000000000000000000000000000000000000000000000000000000000000000000000
     # 000000000000000000000000000000000000000000000000000000000000000000000000000000
     # 00000000000000000000007061796c6f6164
     # 00000000000000000000007061796c6f6164
     #                       p a y l o a d
     #                       p a y l o a d
-    keyfile_blake2_id = bytes.fromhex("d8bc68e961c79f99be39061589e5179b2113cd9226e07b08ddd4a1fef7ce93fb")
+    keyfile_blake2_id = hex_to_bin("d8bc68e961c79f99be39061589e5179b2113cd9226e07b08ddd4a1fef7ce93fb")
 
 
     @pytest.fixture
     @pytest.fixture
     def keys_dir(self, request, monkeypatch, tmpdir):
     def keys_dir(self, request, monkeypatch, tmpdir):
@@ -119,7 +119,7 @@ class TestKey:
         key = PlaintextKey.create(None, None)
         key = PlaintextKey.create(None, None)
         chunk = b"foo"
         chunk = b"foo"
         id = key.id_hash(chunk)
         id = key.id_hash(chunk)
-        assert hexlify(id) == b"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
+        assert bin_to_hex(id) == "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
         assert chunk == key.decrypt(id, key.encrypt(id, chunk))
         assert chunk == key.decrypt(id, key.encrypt(id, chunk))
 
 
     def test_keyfile(self, monkeypatch, keys_dir):
     def test_keyfile(self, monkeypatch, keys_dir):