diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 30044bf92..555d8b5ef 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -28,7 +28,7 @@ from ..platform import SaveFile from ..repoobj import RepoObj -from .low_level import AES, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256 +from .low_level import bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256 from .low_level import AES256_OCB, CHACHA20_POLY1305 from . import low_level @@ -438,9 +438,7 @@ class FlexiKey: raise UnsupportedKeyFormatError() else: self._encrypted_key_algorithm = encrypted_key.algorithm - if encrypted_key.algorithm == "sha256": - return self.decrypt_key_file_pbkdf2(encrypted_key, passphrase) - elif encrypted_key.algorithm == "argon2 chacha20-poly1305": + if encrypted_key.algorithm == "argon2 chacha20-poly1305": return self.decrypt_key_file_argon2(encrypted_key, passphrase) else: raise UnsupportedKeyFormatError() @@ -478,13 +476,6 @@ class FlexiKey: ) return key - def decrypt_key_file_pbkdf2(self, encrypted_key, passphrase): - key = self.pbkdf2(passphrase, encrypted_key.salt, encrypted_key.iterations, 32) - data = AES(key, b"\0" * 16).decrypt(encrypted_key.data) - if hmac.compare_digest(hmac_sha256(key, data), encrypted_key.hash): - return data - return None - def decrypt_key_file_argon2(self, encrypted_key, passphrase): key = self.argon2( passphrase, @@ -502,22 +493,11 @@ class FlexiKey: return None def encrypt_key_file(self, data, passphrase, algorithm): - if algorithm == "sha256": - return self.encrypt_key_file_pbkdf2(data, passphrase) - elif algorithm == "argon2 chacha20-poly1305": + if algorithm == "argon2 chacha20-poly1305": return self.encrypt_key_file_argon2(data, passphrase) else: raise ValueError(f"Unexpected algorithm: {algorithm}") - def encrypt_key_file_pbkdf2(self, data, passphrase): - salt = os.urandom(32) - iterations = PBKDF2_ITERATIONS - key = self.pbkdf2(passphrase, salt, iterations, 32) - hash = hmac_sha256(key, data) - cdata = AES(key, b"\0" * 16).encrypt(data) - enc_key = EncryptedKey(version=1, salt=salt, iterations=iterations, algorithm="sha256", hash=hash, data=cdata) - return msgpack.packb(enc_key.as_dict()) - def encrypt_key_file_argon2(self, data, passphrase): salt = os.urandom(ARGON2_SALT_BYTES) key = self.argon2(passphrase, output_len_in_bytes=32, salt=salt, **ARGON2_ARGS) diff --git a/src/borg/legacy/crypto/key.py b/src/borg/legacy/crypto/key.py index 4454015ee..9ce6d2ef3 100644 --- a/src/borg/legacy/crypto/key.py +++ b/src/borg/legacy/crypto/key.py @@ -1,9 +1,52 @@ +import hmac +import os + from ...constants import * # NOQA -from ...crypto.low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b -from ...crypto.key import ID_HMAC_SHA_256, ID_BLAKE2b_256, AESKeyBase, FlexiKey +from ...crypto.low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b, hmac_sha256 +from ...crypto.key import ID_HMAC_SHA_256, ID_BLAKE2b_256, AESKeyBase, FlexiKey, UnsupportedKeyFormatError +from ...helpers import get_limited_unpacker, msgpack +from ...item import EncryptedKey +from .low_level import AES -class KeyfileKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey): # type: ignore[misc] +class Pbkdf2FileMixin: + """Mixin for borg 1.x key files encrypted with PBKDF2 + AES-CTR.""" + + def decrypt_key_file(self, data, passphrase): + unpacker = get_limited_unpacker("key") + unpacker.feed(data) + unpacked = unpacker.unpack() + encrypted_key = EncryptedKey(internal_dict=unpacked) + if encrypted_key.version != 1: + raise UnsupportedKeyFormatError() + self._encrypted_key_algorithm = encrypted_key.algorithm + if encrypted_key.algorithm == "sha256": + return self.decrypt_key_file_pbkdf2(encrypted_key, passphrase) + return super().decrypt_key_file(data, passphrase) + + def encrypt_key_file(self, data, passphrase, algorithm): + if algorithm == "sha256": + return self.encrypt_key_file_pbkdf2(data, passphrase) + return super().encrypt_key_file(data, passphrase, algorithm) + + def decrypt_key_file_pbkdf2(self, encrypted_key, passphrase): + key = self.pbkdf2(passphrase, encrypted_key.salt, encrypted_key.iterations, 32) + data = AES(key, b"\0" * 16).decrypt(encrypted_key.data) + if hmac.compare_digest(hmac_sha256(key, data), encrypted_key.hash): + return data + return None + + def encrypt_key_file_pbkdf2(self, data, passphrase): + salt = os.urandom(32) + iterations = PBKDF2_ITERATIONS + key = self.pbkdf2(passphrase, salt, iterations, 32) + hash = hmac_sha256(key, data) + cdata = AES(key, b"\0" * 16).encrypt(data) + enc_key = EncryptedKey(version=1, salt=salt, iterations=iterations, algorithm="sha256", hash=hash, data=cdata) + return msgpack.packb(enc_key.as_dict()) + + +class KeyfileKey(Pbkdf2FileMixin, ID_HMAC_SHA_256, AESKeyBase, FlexiKey): # type: ignore[misc] TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE} TYPE = KeyType.KEYFILE NAME = "key file" @@ -12,7 +55,7 @@ class KeyfileKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey): # type: ignore[misc] CIPHERSUITE = AES256_CTR_HMAC_SHA256 -class RepoKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey): # type: ignore[misc] +class RepoKey(Pbkdf2FileMixin, ID_HMAC_SHA_256, AESKeyBase, FlexiKey): # type: ignore[misc] TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE} TYPE = KeyType.REPO NAME = "repokey" @@ -21,7 +64,7 @@ class RepoKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey): # type: ignore[misc] CIPHERSUITE = AES256_CTR_HMAC_SHA256 -class Blake2KeyfileKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey): # type: ignore[misc] +class Blake2KeyfileKey(Pbkdf2FileMixin, ID_BLAKE2b_256, AESKeyBase, FlexiKey): # type: ignore[misc] TYPES_ACCEPTABLE = {KeyType.BLAKE2KEYFILE, KeyType.BLAKE2REPO} TYPE = KeyType.BLAKE2KEYFILE NAME = "key file BLAKE2b" @@ -30,7 +73,7 @@ class Blake2KeyfileKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey): # type: ignore[mi CIPHERSUITE = AES256_CTR_BLAKE2b -class Blake2RepoKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey): # type: ignore[misc] +class Blake2RepoKey(Pbkdf2FileMixin, ID_BLAKE2b_256, AESKeyBase, FlexiKey): # type: ignore[misc] TYPES_ACCEPTABLE = {KeyType.BLAKE2KEYFILE, KeyType.BLAKE2REPO} TYPE = KeyType.BLAKE2REPO NAME = "repokey BLAKE2b" diff --git a/src/borg/testsuite/crypto/crypto_test.py b/src/borg/testsuite/crypto/crypto_test.py index 6d72b2dbf..5d32e39e7 100644 --- a/src/borg/testsuite/crypto/crypto_test.py +++ b/src/borg/testsuite/crypto/crypto_test.py @@ -6,9 +6,11 @@ import unittest from ...crypto.low_level import AES256_CTR_HMAC_SHA256, AES256_OCB, CHACHA20_POLY1305, UNENCRYPTED, IntegrityError from ...crypto.low_level import bytes_to_long, bytes_to_int, long_to_bytes -from ...crypto.low_level import AES, hmac_sha256 +from ...crypto.low_level import hmac_sha256 +from ...legacy.crypto.low_level import AES from hashlib import sha256 from ...crypto.key import CHPOKeyfileKey, AESOCBRepoKey, FlexiKey, KeyBase, PlaintextKey +from ...legacy.crypto.key import KeyfileKey as LegacyKeyfileKey from ...helpers import msgpack, bin_to_hex from .. import BaseTestCase @@ -232,7 +234,7 @@ def test_decrypt_key_file_pbkdf2_sha256_aes256_ctr_hmac_sha256(): encrypted = msgpack.packb( {"version": 1, "algorithm": "sha256", "iterations": 1, "salt": salt, "data": data, "hash": hash} ) - key = CHPOKeyfileKey(None) + key = LegacyKeyfileKey(None) decrypted = key.decrypt_key_file(encrypted, passphrase)