"""AES-256-GCM utilities for QQBot scan-to-configure credential decryption.""" from __future__ import annotations import base64 import os def generate_bind_key() -> str: """Generate a 256-bit random AES key and return it as base64. The key is passed to ``create_bind_task`` so the server can encrypt the bot's *client_secret* before returning it. Only this CLI holds the key, ensuring the secret never travels in plaintext. """ return base64.b64encode(os.urandom(32)).decode() def decrypt_secret(encrypted_base64: str, key_base64: str) -> str: """Decrypt a base64-encoded AES-256-GCM ciphertext. Ciphertext layout (after base64-decoding):: IV (12 bytes) ‖ ciphertext (N bytes) ‖ AuthTag (16 bytes) Args: encrypted_base64: The ``bot_encrypt_secret`` value from ``poll_bind_result``. key_base64: The base64 AES key generated by :func:`generate_bind_key`. Returns: The decrypted *client_secret* as a UTF-8 string. """ from cryptography.hazmat.primitives.ciphers.aead import AESGCM key = base64.b64decode(key_base64) raw = base64.b64decode(encrypted_base64) iv = raw[:12] ciphertext_with_tag = raw[12:] # AESGCM expects ciphertext + tag concatenated aesgcm = AESGCM(key) plaintext = aesgcm.decrypt(iv, ciphertext_with_tag, None) return plaintext.decode("utf-8")