浏览代码

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
Thomas Waldmann 1 年之前
父节点
当前提交
64ce6d5923
共有 1 个文件被更改,包括 17 次插入10 次删除
  1. 17 10
      src/borg/crypto/key.py

+ 17 - 10
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)