import base64 from urllib.parse import quote from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes class AlipayAESCrypto: def __init__(self, aes_key: str): raw = base64.b64decode(aes_key + "=" * (4 - len(aes_key) % 4)) self._key = raw[:16] def decrypt(self, encrypted: str | bytes) -> str: from cryptography.hazmat.primitives import padding as sym_padding data = base64.b64decode(encrypted) iv = self._key cipher = Cipher(algorithms.AES(self._key), modes.CBC(iv)) decryptor = cipher.decryptor() padded = decryptor.update(data) + decryptor.finalize() unpadder = sym_padding.PKCS7(128).unpadder() plaintext = unpadder.update(padded) + unpadder.finalize() return plaintext.decode("utf-8") def encrypt(self, plaintext: str) -> str: from cryptography.hazmat.primitives import padding as sym_padding iv = self._key padder = sym_padding.PKCS7(128).padder() padded = padder.update(plaintext.encode("utf-8")) + padder.finalize() cipher = Cipher(algorithms.AES(self._key), modes.CBC(iv)) encryptor = cipher.encryptor() encrypted = encryptor.update(padded) + encryptor.finalize() return base64.b64encode(encrypted).decode("utf-8") class AlipayCrypto: def __init__(self, app_private_key_pem: str, alipay_public_key_pem: str): self._private_key = serialization.load_pem_private_key( app_private_key_pem.encode("utf-8"), password=None, backend=default_backend(), ) self._public_key = serialization.load_pem_public_key( alipay_public_key_pem.encode("utf-8"), backend=default_backend(), ) def sign(self, params: dict) -> str: content = self._build_sign_string(params) signature = self._private_key.sign( content.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256(), ) return base64.b64encode(signature).decode("utf-8") def verify(self, params: dict, signature: str) -> bool: content = self._build_sign_string(params) try: self._public_key.verify( base64.b64decode(signature), content.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256(), ) return True except Exception: return False @staticmethod def _build_sign_string(params: dict) -> str: filtered = {k: v for k, v in params.items() if k not in ("sign", "sign_type") and v is not None} sorted_items = sorted(filtered.items(), key=lambda x: x[0]) parts = [] for k, v in sorted_items: encoded_key = quote(str(k), safe="~") encoded_value = quote(str(v), safe="~") parts.append(f"{encoded_key}={encoded_value}") return "&".join(parts)