From cb65b32464c2963c3a360f04d625a52d1c15fdf3 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 30 Jun 2022 01:13:49 +0200 Subject: [PATCH] rcreate: always use argon2 kdf for new repos, fixes #6820 this way, we can remove the legacy pbkdf2 key code in next release. --- setup_docs.py | 1 - src/borg/archiver.py | 58 ---------------------------------- src/borg/crypto/key.py | 2 +- src/borg/crypto/low_level.pyx | 2 +- src/borg/testsuite/archiver.py | 41 ++++-------------------- src/borg/testsuite/key.py | 7 ++-- 6 files changed, 11 insertions(+), 100 deletions(-) diff --git a/setup_docs.py b/setup_docs.py index 2f9dcb35dff..7d985ebffcf 100644 --- a/setup_docs.py +++ b/setup_docs.py @@ -303,7 +303,6 @@ class build_man(Command): 'key_change-passphrase': 'key', 'key_change-location': 'key', - 'key_change-algorithm': 'key', 'key_export': 'key', 'key_import': 'key', 'key_migrate-to-repokey': 'key', diff --git a/src/borg/archiver.py b/src/borg/archiver.py index e7c2bf50201..9f612850141 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -4381,22 +4381,6 @@ def define_borg_mount(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. - Key derivation functions - ++++++++++++++++++++++++ - - - ``--key-algorithm argon2`` is the default and is recommended. - The key encryption key is derived from your passphrase via argon2-id. - Argon2 is considered more modern and secure than pbkdf2. - - Our implementation of argon2-based key algorithm follows the cryptographic best practices: - - - It derives two separate keys from your passphrase: one to encrypt your key and another one - to sign it. ``--key-algorithm pbkdf2`` uses the same key for both. - - - It uses encrypt-then-mac instead of encrypt-and-mac used by ``--key-algorithm pbkdf2`` - - Neither is inherently linked to the key derivation function, but since we were going - to break backwards compatibility anyway we took the opportunity to fix all 3 issues at once. """) subparser = subparsers.add_parser('rcreate', parents=[common_parser], add_help=False, description=self.do_rcreate.__doc__, epilog=rcreate_epilog, @@ -4419,8 +4403,6 @@ def define_borg_mount(parser): help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.') subparser.add_argument('--make-parent-dirs', dest='make_parent_dirs', action='store_true', help='create the parent directories of the repository directory, if they are missing.') - subparser.add_argument('--key-algorithm', dest='key_algorithm', default='argon2', choices=list(KEY_ALGORITHMS), - help='the algorithm we use to derive a key encryption key from your passphrase. Default: argon2') # borg key subparser = subparsers.add_parser('key', parents=[mid_common_parser], add_help=False, @@ -4545,46 +4527,6 @@ def define_borg_mount(parser): subparser.add_argument('--keep', dest='keep', action='store_true', help='keep the key also at the current location (default: remove it)') - change_algorithm_epilog = process_epilog(""" - Change the algorithm we use to encrypt and authenticate the borg key. - - Important: In a `repokey` mode (e.g. repokey-blake2) all users share the same key. - In this mode upgrading to `argon2` will make it impossible to access the repo for users who use an old version of borg. - We recommend upgrading to the latest stable version. - - Important: In a `keyfile` mode (e.g. keyfile-blake2) each user has their own key (in ``~/.config/borg/keys``). - In this mode this command will only change the key used by the current user. - If you want to upgrade to `argon2` to strengthen security, you will have to upgrade each user's key individually. - - Your repository is encrypted and authenticated with a key that is randomly generated by ``borg init``. - The key is encrypted and authenticated with your passphrase. - - We currently support two choices: - - 1. argon2 - recommended. This algorithm is used by default when initialising a new repository. - The key encryption key is derived from your passphrase via argon2-id. - Argon2 is considered more modern and secure than pbkdf2. - 2. pbkdf2 - the legacy algorithm. Use this if you want to access your repo via old versions of borg. - The key encryption key is derived from your passphrase via PBKDF2-HMAC-SHA256. - - Examples:: - - # Upgrade an existing key to argon2 - borg key change-algorithm /path/to/repo argon2 - # Downgrade to pbkdf2 - use this if upgrading borg is not an option - borg key change-algorithm /path/to/repo pbkdf2 - - - """) - subparser = key_parsers.add_parser('change-algorithm', parents=[common_parser], add_help=False, - description=self.do_change_algorithm.__doc__, - epilog=change_algorithm_epilog, - formatter_class=argparse.RawDescriptionHelpFormatter, - help='change key algorithm') - subparser.set_defaults(func=self.do_change_algorithm) - subparser.add_argument('algorithm', metavar='ALGORITHM', choices=list(KEY_ALGORITHMS), - help='select key algorithm') - # borg list list_epilog = process_epilog(""" This command lists the contents of an archive. diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index e1c58c47af5..cd1fd9324fb 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -620,7 +620,7 @@ def create(cls, repository, args, *, other_key=None): passphrase = Passphrase.new(allow_empty=True) key.init_ciphers() target = key.get_new_target(args) - key.save(target, passphrase, create=True, algorithm=KEY_ALGORITHMS[args.key_algorithm]) + key.save(target, passphrase, create=True, algorithm=KEY_ALGORITHMS['argon2']) logger.info('Key in "%s" created.' % target) logger.info('Keep this key safe. Your data will be inaccessible without it.') return key diff --git a/src/borg/crypto/low_level.pyx b/src/borg/crypto/low_level.pyx index c0818abc51e..d44160b5044 100644 --- a/src/borg/crypto/low_level.pyx +++ b/src/borg/crypto/low_level.pyx @@ -613,7 +613,7 @@ cdef class CHACHA20_POLY1305(_AEAD_BASE): super().__init__(key, iv=iv, header_len=header_len, aad_offset=aad_offset) -cdef class AES: +cdef class AES: # legacy """A thin wrapper around the OpenSSL EVP cipher API - for legacy code, like key file encryption""" cdef CIPHER cipher cdef EVP_CIPHER_CTX *ctx diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 7c34da23805..6fda62be796 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -3472,53 +3472,24 @@ def test_init_defaults_to_argon2(self): key = msgpack.unpackb(a2b_base64(repository.load_key())) assert key['algorithm'] == 'argon2 chacha20-poly1305' - def test_init_with_explicit_key_algorithm(self): - """https://github.com/borgbackup/borg/issues/747#issuecomment-1076160401""" - self.cmd(f'--repo={self.repository_location}', 'rcreate', RK_ENCRYPTION, '--key-algorithm=pbkdf2') - with Repository(self.repository_path) as repository: - key = msgpack.unpackb(a2b_base64(repository.load_key())) - assert key['algorithm'] == 'sha256' - - def verify_change_passphrase_does_not_change_algorithm(self, given_algorithm, expected_algorithm): - self.cmd(f'--repo={self.repository_location}', 'rcreate', RK_ENCRYPTION, '--key-algorithm', given_algorithm) + def test_change_passphrase_does_not_change_algorithm_argon2(self): + self.cmd(f'--repo={self.repository_location}', 'rcreate', RK_ENCRYPTION) os.environ['BORG_NEW_PASSPHRASE'] = 'newpassphrase' self.cmd(f'--repo={self.repository_location}', 'key', 'change-passphrase') with Repository(self.repository_path) as repository: key = msgpack.unpackb(a2b_base64(repository.load_key())) - assert key['algorithm'] == expected_algorithm - - def test_change_passphrase_does_not_change_algorithm_argon2(self): - self.verify_change_passphrase_does_not_change_algorithm('argon2', 'argon2 chacha20-poly1305') + assert key['algorithm'] == 'argon2 chacha20-poly1305' - def test_change_passphrase_does_not_change_algorithm_pbkdf2(self): - self.verify_change_passphrase_does_not_change_algorithm('pbkdf2', 'sha256') - - def verify_change_location_does_not_change_algorithm(self, given_algorithm, expected_algorithm): - self.cmd(f'--repo={self.repository_location}', 'rcreate', KF_ENCRYPTION, '--key-algorithm', given_algorithm) + def test_change_location_does_not_change_algorithm_argon2(self): + self.cmd(f'--repo={self.repository_location}', 'rcreate', KF_ENCRYPTION) self.cmd(f'--repo={self.repository_location}', 'key', 'change-location', 'repokey') with Repository(self.repository_path) as repository: key = msgpack.unpackb(a2b_base64(repository.load_key())) - assert key['algorithm'] == expected_algorithm - - def test_change_location_does_not_change_algorithm_argon2(self): - self.verify_change_location_does_not_change_algorithm('argon2', 'argon2 chacha20-poly1305') - - def test_change_location_does_not_change_algorithm_pbkdf2(self): - self.verify_change_location_does_not_change_algorithm('pbkdf2', 'sha256') - - def test_key_change_algorithm(self): - self.cmd(f'--repo={self.repository_location}', 'rcreate', RK_ENCRYPTION, '--key-algorithm=pbkdf2') - - self.cmd(f'--repo={self.repository_location}', 'key', 'change-algorithm', 'argon2') - - with Repository(self.repository_path) as repository: - _, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) - assert key._encrypted_key_algorithm == 'argon2 chacha20-poly1305' - self.cmd(f'--repo={self.repository_location}', 'rinfo') + assert key['algorithm'] == 'argon2 chacha20-poly1305' @unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available') diff --git a/src/borg/testsuite/key.py b/src/borg/testsuite/key.py index 01db1633545..b202d10784c 100644 --- a/src/borg/testsuite/key.py +++ b/src/borg/testsuite/key.py @@ -396,8 +396,7 @@ def test_decrypt_key_file_v2_is_unsupported(): key.decrypt_key_file(encrypted, "hello, pass phrase") -@pytest.mark.parametrize('cli_argument, expected_algorithm', KEY_ALGORITHMS.items()) -def test_key_file_roundtrip(monkeypatch, cli_argument, expected_algorithm): +def test_key_file_roundtrip(monkeypatch): def to_dict(key): extract = 'repository_id', 'enc_key', 'enc_hmac_key', 'id_key', 'chunk_seed' return {a: getattr(key, a) for a in extract} @@ -405,10 +404,10 @@ def to_dict(key): repository = MagicMock(id=b'repository_id') monkeypatch.setenv('BORG_PASSPHRASE', "hello, pass phrase") - save_me = AESOCBRepoKey.create(repository, args=MagicMock(key_algorithm=cli_argument)) + save_me = AESOCBRepoKey.create(repository, args=MagicMock(key_algorithm='argon2')) saved = repository.save_key.call_args.args[0] repository.load_key.return_value = saved load_me = AESOCBRepoKey.detect(repository, manifest_data=None) assert to_dict(load_me) == to_dict(save_me) - assert msgpack.unpackb(a2b_base64(saved))['algorithm'] == expected_algorithm + assert msgpack.unpackb(a2b_base64(saved))['algorithm'] == KEY_ALGORITHMS['argon2']