diff --git a/docs/faq.rst b/docs/faq.rst index ef9d35656..f678c3d83 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -1289,8 +1289,8 @@ There are some caveats: This means that data added by Borg won't deduplicate with the existing data stored by Attic. The effect is lessened if the files cache is used with Borg. - Repositories in "passphrase" mode *must* be migrated to "repokey" mode using - :ref:`borg_key_migrate-to-repokey`. Borg does not support the "passphrase" mode - any other way. + "borg key migrate-to-repokey" (only available in borg <= 1.2.x). Borg does not + support the "passphrase" mode in any other way. Why is my backup bigger than with attic? ---------------------------------------- diff --git a/docs/internals/security.rst b/docs/internals/security.rst index 3f79e66bd..1b9764e1b 100644 --- a/docs/internals/security.rst +++ b/docs/internals/security.rst @@ -129,7 +129,7 @@ which is generally seen as the most robust way to create an authenticated encryption scheme from encryption and message authentication primitives. Every operation (encryption, MAC / authentication, chunk ID derivation) -uses independent, random keys generated by `os.urandom`_ [#]_. +uses independent, random keys generated by `os.urandom`_. Borg does not support unauthenticated encryption -- only authenticated encryption schemes are supported. No unauthenticated encryption schemes will be added @@ -208,13 +208,6 @@ untrusted, but a trusted synchronization channel exists between clients, the security database could be synchronized between them over said trusted channel. This is not part of Borg's functionality. -.. [#] Using the :ref:`borg key migrate-to-repokey ` - command a user can convert repositories created using Attic in "passphrase" - mode to "repokey" mode. In this case the keys were directly derived from - the user's passphrase at some point using PBKDF2. - - Borg does not support "passphrase" mode otherwise any more. - .. _key_encryption: Offline key security diff --git a/docs/usage/upgrade.rst b/docs/usage/upgrade.rst index 044c81cf6..8ebd96e4c 100644 --- a/docs/usage/upgrade.rst +++ b/docs/usage/upgrade.rst @@ -14,17 +14,3 @@ Examples converting borg 0.xx to borg current no key file found for repository -.. _borg_key_migrate-to-repokey: - -Upgrading a passphrase encrypted attic repo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -attic offered a "passphrase" encryption mode, but this was removed in borg 1.0 -and replaced by the "repokey" mode (which stores the passphrase-protected -encryption key into the repository config). - -Thus, to upgrade a "passphrase" attic repo to a "repokey" borg repo, 2 steps -are needed, in this order: - -- borg upgrade repo -- borg key migrate-to-repokey repo diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 5eb329d2d..522af30b2 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -44,7 +44,7 @@ from .cache import Cache, assert_secure, SecurityManager from .constants import * # NOQA from .compress import CompressionSpec - from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required, RepoKey, PassphraseKey + from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required, RepoKey from .crypto.keymanager import KeyManager from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE from .helpers import Error, NoManifestError, set_ec @@ -400,22 +400,6 @@ def do_key_import(self, args, repository): manager.import_keyfile(args) return EXIT_SUCCESS - @with_repository(manifest=False) - def do_migrate_to_repokey(self, args, repository): - """Migrate passphrase -> repokey""" - manifest_data = repository.get(Manifest.MANIFEST_ID) - key_old = PassphraseKey.detect(repository, manifest_data) - key_new = RepoKey(repository) - key_new.target = repository - key_new.repository_id = repository.id - key_new.enc_key = key_old.enc_key - key_new.enc_hmac_key = key_old.enc_hmac_key - key_new.id_key = key_old.id_key - key_new.chunk_seed = key_old.chunk_seed - key_new.change_passphrase() # option to change key protection passphrase, save - logger.info('Key updated') - return EXIT_SUCCESS - def do_benchmark_crud(self, args): """Benchmark Create, Read, Update, Delete for archives.""" def measurement_run(repo, path): @@ -4266,33 +4250,6 @@ def define_borg_mount(parser): subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', type=location_validator(archive=False)) - migrate_to_repokey_epilog = process_epilog(""" - This command migrates a repository from passphrase mode (removed in Borg 1.0) - to repokey mode. - - You will be first asked for the repository passphrase (to open it in passphrase - mode). This is the same passphrase as you used to use for this repo before 1.0. - - It will then derive the different secrets from this passphrase. - - Then you will be asked for a new passphrase (twice, for safety). This - passphrase will be used to protect the repokey (which contains these same - secrets in encrypted form). You may use the same passphrase as you used to - use, but you may also use a different one. - - After migrating to repokey mode, you can change the passphrase at any time. - But please note: the secrets will always stay the same and they could always - be derived from your (old) passphrase-mode passphrase. - """) - subparser = key_parsers.add_parser('migrate-to-repokey', parents=[common_parser], add_help=False, - description=self.do_migrate_to_repokey.__doc__, - epilog=migrate_to_repokey_epilog, - formatter_class=argparse.RawDescriptionHelpFormatter, - help='migrate passphrase-mode repository to repokey') - subparser.set_defaults(func=self.do_migrate_to_repokey) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False)) - # borg list list_epilog = process_epilog(""" This command lists the contents of a repository or an archive. diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index daed70009..bf2d23817 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -119,9 +119,7 @@ def key_argument_names(): def identify_key(manifest_data): key_type = manifest_data[0] if key_type == PassphraseKey.TYPE: - # we just dispatch to repokey mode and assume the passphrase was migrated to a repokey. - # see also comment in PassphraseKey class. - return RepoKey + return RepoKey # see comment in PassphraseKey class. for key in AVAILABLE_KEY_TYPES: if key.TYPE == key_type: @@ -327,8 +325,7 @@ class ID_BLAKE2b_256: def id_hash(self, data): return blake2b_256(self.id_key, data) - def init_from_random_data(self, data=None): - assert data is None # PassphraseKey is the only caller using *data* + def init_from_random_data(self): super().init_from_random_data() self.enc_hmac_key = random_blake2b_256_key() self.id_key = random_blake2b_256_key() @@ -347,8 +344,6 @@ def id_hash(self, data): class AESKeyBase(KeyBase): """ - Common base class shared by KeyfileKey and PassphraseKey - Chunks are encrypted using 256bit AES in Counter Mode (CTR) Payload layout: TYPE(1) + HMAC(32) + NONCE(8) + CIPHERTEXT @@ -386,9 +381,8 @@ def decrypt(self, id, data, decompress=True): self.assert_id(id, data) return data - def init_from_random_data(self, data=None): - if data is None: - data = os.urandom(100) + def init_from_random_data(self): + data = os.urandom(100) self.enc_key = data[0:32] self.enc_hmac_key = data[32:64] self.id_key = data[64:96] @@ -523,59 +517,13 @@ def kdf(self, salt, iterations, length): return pbkdf2_hmac('sha256', self.encode('utf-8'), salt, iterations, length) -class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase): - # This mode was killed in borg 1.0, see: https://github.com/borgbackup/borg/issues/97 - # Reasons: - # - you can never ever change your passphrase for existing repos. - # - you can never ever use a different iterations count for existing repos. - # "Killed" means: - # - there is no automatic dispatch to this class via type byte - # - --encryption=passphrase is an invalid argument now - # This class is kept for a while to support migration from passphrase to repokey mode. +class PassphraseKey: + # this is only a stub, repos with this mode could not be created any more since borg 1.0, see #97. + # in borg 1.3 all of its code and also the "borg key migrate-to-repokey" command was removed. + # if you still need to, you can use "borg key migrate-to-repokey" with borg 1.0, 1.1 and 1.2. + # Nowadays, we just dispatch this to RepoKey and assume the passphrase was migrated to a repokey. TYPE = 0x01 NAME = 'passphrase' - ARG_NAME = None - STORAGE = KeyBlobStorage.NO_STORAGE - - iterations = 100000 # must not be changed ever! - - @classmethod - def create(cls, repository, args): - key = cls(repository) - logger.warning('WARNING: "passphrase" mode is unsupported since borg 1.0.') - passphrase = Passphrase.new(allow_empty=False) - key.init(repository, passphrase) - return key - - @classmethod - def detect(cls, repository, manifest_data): - prompt = 'Enter passphrase for %s: ' % repository._location.canonical_path() - key = cls(repository) - passphrase = Passphrase.env_passphrase() - if passphrase is None: - passphrase = Passphrase.getpass(prompt) - for retry in range(1, 3): - key.init(repository, passphrase) - try: - key.decrypt(None, manifest_data) - key.init_ciphers(manifest_data) - key._passphrase = passphrase - return key - except IntegrityError: - passphrase = Passphrase.getpass(prompt) - else: - raise PasswordRetriesExceeded - - def change_passphrase(self): - class ImmutablePassphraseError(Error): - """The passphrase for this encryption key type can't be changed.""" - - raise ImmutablePassphraseError - - def init(self, repository, passphrase): - self.init_from_random_data(passphrase.kdf(repository.id, self.iterations, 100)) - self.init_ciphers() - self.tam_required = False class KeyfileKeyBase(AESKeyBase): @@ -888,7 +836,6 @@ class Blake2AuthenticatedKey(ID_BLAKE2b_256, AuthenticatedKeyBase): AVAILABLE_KEY_TYPES = ( PlaintextKey, - PassphraseKey, KeyfileKey, RepoKey, AuthenticatedKey, Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey, ) diff --git a/src/borg/testsuite/key.py b/src/borg/testsuite/key.py index 92f763584..534b4738a 100644 --- a/src/borg/testsuite/key.py +++ b/src/borg/testsuite/key.py @@ -7,7 +7,7 @@ import pytest from ..crypto.key import Passphrase, PasswordRetriesExceeded, bin_to_hex -from ..crypto.key import PlaintextKey, PassphraseKey, AuthenticatedKey, RepoKey, KeyfileKey, \ +from ..crypto.key import PlaintextKey, AuthenticatedKey, RepoKey, KeyfileKey, \ Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey from ..crypto.key import ID_HMAC_SHA_256, ID_BLAKE2b_256 from ..crypto.key import TAMRequiredError, TAMInvalid, TAMUnsupportedSuiteError, UnsupportedManifestError @@ -182,31 +182,6 @@ def test_keyfile_blake2(self, monkeypatch, keys_dir): key = Blake2KeyfileKey.detect(self.MockRepository(), self.keyfile_blake2_cdata) assert key.decrypt(self.keyfile_blake2_id, self.keyfile_blake2_cdata) == b'payload' - def test_passphrase(self, keys_dir, monkeypatch): - monkeypatch.setenv('BORG_PASSPHRASE', 'test') - key = PassphraseKey.create(self.MockRepository(), None) - assert key.cipher.next_iv() == 0 - assert hexlify(key.id_key) == b'793b0717f9d8fb01c751a487e9b827897ceea62409870600013fbc6b4d8d7ca6' - assert hexlify(key.enc_hmac_key) == b'b885a05d329a086627412a6142aaeb9f6c54ab7950f996dd65587251f6bc0901' - assert hexlify(key.enc_key) == b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a' - assert key.chunk_seed == -775740477 - manifest = key.encrypt(b'ABC') - assert key.cipher.extract_iv(manifest) == 0 - manifest2 = key.encrypt(b'ABC') - assert manifest != manifest2 - assert key.decrypt(None, manifest) == key.decrypt(None, manifest2) - assert key.cipher.extract_iv(manifest2) == 1 - iv = key.cipher.extract_iv(manifest) - key2 = PassphraseKey.detect(self.MockRepository(), manifest) - assert key2.cipher.next_iv() == iv + key2.cipher.block_count(len(manifest)) - assert key.id_key == key2.id_key - assert key.enc_hmac_key == key2.enc_hmac_key - assert key.enc_key == key2.enc_key - assert key.chunk_seed == key2.chunk_seed - chunk = b'foo' - assert hexlify(key.id_hash(chunk)) == b'818217cf07d37efad3860766dcdf1d21e401650fed2d76ed1d797d3aae925990' - assert chunk == key2.decrypt(key2.id_hash(chunk), key.encrypt(chunk)) - def _corrupt_byte(self, key, data, offset): data = bytearray(data) data[offset] ^= 1