Skip to content

Commit

Permalink
rcreate: always use argon2 kdf for new repos, fixes #6820
Browse files Browse the repository at this point in the history
this way, we can remove the legacy pbkdf2 key code in next release.
  • Loading branch information
ThomasWaldmann committed Jun 29, 2022
1 parent eff91ee commit cb65b32
Show file tree
Hide file tree
Showing 6 changed files with 11 additions and 100 deletions.
1 change: 0 additions & 1 deletion setup_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
58 changes: 0 additions & 58 deletions src/borg/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/borg/crypto/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/borg/crypto/low_level.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 6 additions & 35 deletions src/borg/testsuite/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
7 changes: 3 additions & 4 deletions src/borg/testsuite/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,19 +396,18 @@ 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}

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']

0 comments on commit cb65b32

Please sign in to comment.