44from cryptography .hazmat .primitives .kdf .hkdf import HKDF
55
66
7+ _MAX_SEQUENCE = (1 << 64 ) - 1
8+
9+
710def hkdf_expand (secret : bytes , * , length : int , salt : bytes | None , info : bytes ) -> bytes :
811 return HKDF (
912 algorithm = hashes .SHA256 (),
@@ -28,7 +31,46 @@ def derive_session_root(
2831 )
2932
3033
31- def derive_message_key (root_key : bytes , sequence : int , suite_name : str ) -> tuple [bytes , bytes ]:
32- info = f"CypherSyntax/message/{ suite_name } /v1/{ sequence } " .encode ("utf-8" )
33- material = hkdf_expand (root_key , length = 44 , salt = None , info = info )
34+ def _length_prefixed (value : bytes ) -> bytes :
35+ return len (value ).to_bytes (4 , "big" ) + value
36+
37+
38+ def derive_directional_secret (
39+ root_key : bytes ,
40+ * ,
41+ suite_name : str ,
42+ sender_public_key : bytes ,
43+ recipient_public_key : bytes ,
44+ ) -> bytes :
45+ suite_bytes = suite_name .encode ("utf-8" )
46+ info = b"" .join (
47+ (
48+ b"CypherSyntax/traffic-secret/v1" ,
49+ _length_prefixed (suite_bytes ),
50+ _length_prefixed (sender_public_key ),
51+ _length_prefixed (recipient_public_key ),
52+ )
53+ )
54+ return hkdf_expand (root_key , length = 32 , salt = None , info = info )
55+
56+
57+ def derive_message_key (
58+ root_key : bytes ,
59+ sequence : int ,
60+ suite_name : str ,
61+ * ,
62+ sender_public_key : bytes ,
63+ recipient_public_key : bytes ,
64+ ) -> tuple [bytes , bytes ]:
65+ if not 0 <= sequence <= _MAX_SEQUENCE :
66+ raise ValueError ("message sequence must fit in an unsigned 64-bit integer" )
67+
68+ directional_secret = derive_directional_secret (
69+ root_key ,
70+ suite_name = suite_name ,
71+ sender_public_key = sender_public_key ,
72+ recipient_public_key = recipient_public_key ,
73+ )
74+ info = b"CypherSyntax/message-key/v1" + sequence .to_bytes (8 , "big" )
75+ material = hkdf_expand (directional_secret , length = 44 , salt = None , info = info )
3476 return material [:32 ], material [32 :44 ]
0 commit comments