From 64ce6d59234fc0e18cb5dda860f6bb3750cc03dd Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 30 Nov 2023 19:48:29 +0100 Subject: [PATCH] crypto: use a one-step kdf for session keys, fixes #7953 also: - fixes, simplifies, speeds up _get_session_key - convert sessionid memoryview to bytes before calling _get_cipher, to avoid TypeError in (crypt_key + sessionid + domain) operation. - add docstring and comments --- src/borg/crypto/key.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 978007d1d..ee820c8ba 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -27,7 +27,7 @@ from ..platform import SaveFile from ..repoobj import RepoObj -from .low_level import AES, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256, hkdf_hmac_sha512 +from .low_level import AES, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256 from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b, AES256_OCB, CHACHA20_POLY1305 from . import low_level @@ -833,7 +833,7 @@ class AEADKeyBase(KeyBase): # to decrypt existing data, we need to get a cipher configured for the sessionid and iv from header self.assert_type(data[0], id) iv_48bit = data[2:8] - sessionid = data[8:32] + sessionid = bytes(data[8:32]) iv = int.from_bytes(iv_48bit, "big") cipher = self._get_cipher(sessionid, iv) try: @@ -857,15 +857,22 @@ class AEADKeyBase(KeyBase): chunk_seed = chunk_seed - 0xFFFFFFFF - 1 self.init_from_given_data(crypt_key=data[0:64], id_key=data[64:96], chunk_seed=chunk_seed) - def _get_session_key(self, sessionid): + def _get_session_key(self, sessionid, domain=None): + """ + Derive a session key from the secret long-term static crypt_key (which is a fully random PRK) + and the session id (which is fully random also). + Optionally, a domain can be given for domain separation (defaults to a different binary string + per cipher suite). + """ + # Performance note: + # While this is only invoked once per session to generate a new key for encrypting new data, it is invoked + # frequently (per encrypted repo object) to compute the corresponding key for decrypting existing data. assert len(sessionid) == 24 # 192bit - key = hkdf_hmac_sha512( - ikm=self.crypt_key, - salt=sessionid, - info=b"borg-session-key-" + self.CIPHERSUITE.__name__.encode(), - output_length=32, - ) - return key + if domain is None: + domain = b"borg-session-key-" + self.CIPHERSUITE.__name__.encode() + # Because crypt_key is already a PRK, we do not need KDF security here, PRF security is good enough. + # See https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf section 4 "one-step KDF". + return sha256(self.crypt_key + sessionid + domain).digest() def _get_cipher(self, sessionid, iv): assert isinstance(iv, int)