import hmac import hashlib import base64 salt = "UqEEf08yMDE3qIQodAGAyNQ2EPFhb26f2o85MTIyMeAAkqlANiXcF" def generate_secret_key(identifier: str, expiry: str) -> str: """ Generate a secret key with the following structure: - encoded_data: Base64 encoding of (identifier + expiry) - signature: 32 characters from the URL-safe base64 HMAC signature - id_length_hex: 2-character hexadecimal number representing the length of the identifier The HMAC is computed over (encoded_data + id_length_hex). """ global salt if len(identifier) > 255: raise ValueError("Identifier must be at most 255 characters long.") if len(expiry) != 8: raise ValueError("Expiry must be an 8-character string in DDMMYYYY format.") # Concatenate identifier and expiry, then base64 encode the result. plain_data = identifier + expiry # Remove padding for compactness; we'll add it back during decoding. encoded_data = base64.urlsafe_b64encode(plain_data.encode()).decode().rstrip("=") # Format the length of the identifier as a 2-digit hexadecimal string. id_length_hex = f"{len(identifier):02X}" # Build the message for HMAC: encoded_data + id_length_hex. message = encoded_data + id_length_hex # Compute HMAC using SHA-256. hmac_digest = hmac.new(salt.encode(), message.encode(), hashlib.sha256).digest() # Get a URL-safe base64 representation and take the first 32 characters as the signature. signature = base64.urlsafe_b64encode(hmac_digest).decode()[:32] # Construct the final secret key. secret_key = encoded_data + signature + id_length_hex return secret_key def decode_secret_key(secret_key: str): """ Decode the secret key and extract the identifier and expiry. Token structure: - encoded_data: secret_key[0 : (len(secret_key)-34)] (variable length) - signature: secret_key[-34:-2] (32 characters) - id_length_hex: secret_key[-2:] (2 characters) The HMAC is verified over (encoded_data + id_length_hex). Then, encoded_data is base64-decoded (with proper padding added) to yield (identifier + expiry), where the last 8 characters represent expiry. The identifier length is determined from id_length_hex. """ global salt if len(secret_key) < 34: # Must at least have signature (32) + id_length_hex (2) raise ValueError("Invalid key length.") # Extract the last 2 characters: id_length_hex. id_length_hex = secret_key[-2:] try: id_length = int(id_length_hex, 16) except ValueError: raise ValueError("Invalid identifier length prefix in key.") # The signature is the 32 characters preceding the last 2. signature = secret_key[-34:-2] # The remainder is the encoded_data. encoded_data = secret_key[:-34] # Verify the signature. message = encoded_data + id_length_hex expected_hmac = hmac.new(salt.encode(), message.encode(), hashlib.sha256).digest() expected_signature = base64.urlsafe_b64encode(expected_hmac).decode()[:32] if not hmac.compare_digest(signature, expected_signature): raise ValueError("Invalid key signature.") # Base64-decode the encoded_data. # Add back any missing '=' padding. padding = '=' * (-len(encoded_data) % 4) plain_data = base64.urlsafe_b64decode(encoded_data + padding).decode() # plain_data should be (identifier + expiry), where expiry is the last 8 characters. if len(plain_data) != id_length + 8: raise ValueError("Decoded data length does not match expected identifier length and expiry.") identifier = plain_data[:id_length] expiry = plain_data[id_length:] return identifier, expiry if __name__ == '__main__': key = generate_secret_key("Besondere Gottesdienste", "30042025") print(key) identifier, expiry = decode_secret_key(key) print("identifier:", identifier) print("expiry:", expiry)