diff --git a/src/borg/archiver.py b/src/borg/archiver.py index a3258ce80..0234e757e 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -421,6 +421,15 @@ class Archiver: return EXIT_SUCCESS + @with_repository(exclusive=True, compatibility=(Manifest.Operation.CHECK,)) + def do_change_algorithm(self, args, repository, manifest, key): + """Change repository key algorithm""" + if not hasattr(key, 'change_passphrase'): + print('This repository is not encrypted, cannot change the algorithm.') + return EXIT_ERROR + key.save(key.target, key._passphrase, algorithm=KEY_ALGORITHMS[args.algorithm]) + return EXIT_SUCCESS + @with_repository(lock=False, exclusive=False, manifest=False, cache=False) def do_key_export(self, args, repository): """Export the repository key for backup""" @@ -4455,6 +4464,30 @@ 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. + + 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. + 1. 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. + """) + 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('location', metavar='REPOSITORY', nargs='?', default='', + type=location_validator(archive=False)) + 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 a repository or an archive. diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 97464627d..f356d2242 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -3630,6 +3630,16 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 def test_change_location_does_not_change_algorithm_pbkdf2(self): self.verify_change_location_does_not_change_algorithm('pbkdf2', b'sha256') + def test_key_change_algorithm(self): + self.cmd('init', '--encryption=repokey', '--key-algorithm=pbkdf2', self.repository_location) + + self.cmd('key', 'change-algorithm', self.repository_location, 'argon2') + + with Repository(self.repository_path) as repository: + _, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) + assert key._encrypted_key_algorithm == 'argon2 aes256-ctr hmac-sha256' + self.cmd('info', self.repository_location) + @unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available') class ArchiverTestCaseBinary(ArchiverTestCase):