import binascii import hashlib import hmac import json from collections import OrderedDict from typing import Any def generate_signature_hash_for_sli_data(sli_data: str) -> str: parsed_data = json.loads(sli_data) normalized_json = normalize_json(parsed_data) data_hash = hashlib.sha256(normalized_json.encode()).digest() # Convert to 64-character hexadecimal string data_hash_hex = data_hash.hex() try: secret_key = irke(data_hash_hex) except Exception as e: raise ValueError(f"failed to generate secret_key: {e}") from e # calculate HMAC-SHA256 signature hmac_hash = hmac.new(secret_key.encode(), data_hash_hex.encode(), hashlib.sha256) signature_hash = hmac_hash.hexdigest() return signature_hash def irke(hash_str: str) -> str: # Verify input length is 64 characters if len(hash_str) != 64: raise ValueError("input must be a 64-character hexadecimal SHA-256 hash") # Step 1: Split hash into A and B (32 characters each, 128 bits) A = hash_str[:32] # First 32 characters B = hash_str[32:] # Last 32 characters # Convert A and B from hex strings to byte arrays (16 bytes each, 128 bits) try: a_bytes = binascii.unhexlify(A) b_bytes = binascii.unhexlify(B) except binascii.Error as e: raise ValueError(f"segment is not valid hexadecimal: {e}") # Step 2: XOR A and B to get K1 (128 bits) k1 = bytearray(16) for i in range(16): k1[i] = a_bytes[i] ^ b_bytes[i] # Step 3: Dynamic rotation to get K2 # 3.1 Extract last two characters of H (H63H64) last_two_chars = hash_str[62:] # 3.2 Convert to integer r (0-255) try: last_byte = binascii.unhexlify(last_two_chars) if len(last_byte) != 1: raise ValueError("failed to extract rotation amount") r = last_byte[0] except binascii.Error as e: raise ValueError(f"failed to extract rotation amount: {e}") # 3.3 Take rotation amount modulo 128 (since K1 is 128 bits) r_mod = r % 128 # 3.4 Perform left cyclic rotation on K1 by rMod bits to get K2 k2 = rotate_left_128(k1, r_mod) # Step 4: Interleaving and final XOR to get Kfinal # 4.1 Extract C1 (H17..32, i.e., indices 16-31, 16 characters) and C2 (H49..64, i.e., indices 48-63, 16 characters) C1 = hash_str[16:32] C2 = hash_str[48:64] # 4.2 Concatenate C1 and C2 to get P (32 characters, 128 bits) P = C1 + C2 try: p_bytes = binascii.unhexlify(P) except binascii.Error as e: raise ValueError(f"segment P is not valid hexadecimal: {e}") # 4.3 XOR K2 with P to get Kfinal k_final = bytearray(16) for i in range(16): k_final[i] = k2[i] ^ p_bytes[i] # Step 5: Convert to 32-character hexadecimal string k_secret = binascii.hexlify(k_final).decode() return k_secret def rotate_left_128(data: bytes, bits: int) -> bytes: if len(data) != 16: raise ValueError("rotate_left_128: input must be 16 bytes (128 bits)") if bits <= 0 or bits >= 128: return data # Rotating 0 or 128 bits is equivalent to no rotation # Convert 16-byte array to 128-bit slice (each element is 0 or 1) bits_slice = [] for i in range(16): for j in range(8): # Extract the j-th bit of each byte (high bit first) bit = (data[i] >> (7 - j)) & 0x01 bits_slice.append(bit) # Left cyclic rotation: move first 'bits' bits to the end rotated = bits_slice[bits:] + bits_slice[:bits] # Convert bit slice back to 16-byte array result = bytearray(16) for i in range(16): b = 0 for j in range(8): b = (b << 1) | rotated[i * 8 + j] result[i] = b return result def normalize_json(data: Any) -> str: def _normalize(obj: Any) -> Any: if isinstance(obj, dict): # create sorted dict,sorted by key sorted_dict = OrderedDict() for key in sorted(obj.keys()): sorted_dict[key] = _normalize(obj[key]) return sorted_dict elif isinstance(obj, list): return [_normalize(item) for item in obj] else: return obj normalized_data = _normalize(data) return json.dumps(normalized_data, separators=(",", ":"))