Merge pull request #6412 from ThomasWaldmann/remove-passphrasekey

remove PassphraseKey code and borg key migrate-to-repokey command
This commit is contained in:
TW 2022-03-06 15:51:06 +01:00 committed by GitHub
commit 0714339d3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 14 additions and 156 deletions

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ try:
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 @@ class Archiver:
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 @@ class Archiver:
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.

View File

@ -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 @@ class ID_HMAC_SHA_256:
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 @@ class AESKeyBase(KeyBase):
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 @@ class Passphrase(str):
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,
)

View File

@ -7,7 +7,7 @@ from binascii import hexlify, unhexlify
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 @@ class TestKey:
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