rcreate: always use argon2 kdf for new repos, fixes #6820

this way, we can remove the legacy pbkdf2 key code in next release.
This commit is contained in:
Thomas Waldmann 2022-06-30 01:13:49 +02:00
parent ef24dafb15
commit eabad3e3b7
6 changed files with 11 additions and 100 deletions

View File

@ -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',

View File

@ -4380,22 +4380,6 @@ class Archiver:
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,
@ -4418,8 +4402,6 @@ class Archiver:
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,
@ -4544,46 +4526,6 @@ class Archiver:
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.

View File

@ -620,7 +620,7 @@ class FlexiKey:
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

View File

@ -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

View File

@ -3472,53 +3472,24 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02
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
assert key['algorithm'] == 'argon2 chacha20-poly1305'
def test_change_passphrase_does_not_change_algorithm_argon2(self):
self.verify_change_passphrase_does_not_change_algorithm('argon2', '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')

View File

@ -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 test_key_file_roundtrip(monkeypatch, cli_argument, expected_algorithm):
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']