"""core/wechat/wecom_crypto.py 单测 —— 企微回调加解密(WXBizMsgCrypt 等价实现)。 模块只做入站解密+验签(出站走主动推不需要加密),测试侧按同一方案**自造密文**: key = b64decode(EncodingAESKey+'='),AES-256-CBC(IV=key[:16]),明文体 = random(16) || len(4B 大端) || msg || receiveid,PKCS7 pad 到 32。 凭据走 mock.patch.dict 注入合成值,不碰真实 env / 网络。 此前 wechat 子系统零测试(架构审查 Top3),crypto 是其中唯一可纯测的一块。 """ from __future__ import annotations import base64 import os import secrets import struct import unittest from unittest import mock from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from core.wechat import wecom_crypto as wc _TOKEN = "testtoken" # 43 字符合法 EncodingAESKey(b64 去掉尾部 '=') _AESKEY = base64.b64encode(secrets.token_bytes(32)).decode().rstrip("=") _CORPID = "ww1234567890abcdef" _ENV = {"WECOM_CALLBACK_TOKEN": _TOKEN, "WECOM_CALLBACK_AESKEY": _AESKEY} def _encrypt(msg: str, receiveid: str = _CORPID) -> str: """按企业微信方案造密文(测试侧加密,与被测解密互逆)。""" key = base64.b64decode(_AESKEY + "=") body = secrets.token_bytes(16) + struct.pack(">I", len(msg.encode())) \ + msg.encode() + receiveid.encode() pad = 32 - (len(body) % 32) or 32 body += bytes([pad]) * pad enc = Cipher(algorithms.AES(key), modes.CBC(key[:16])).encryptor() return base64.b64encode(enc.update(body) + enc.finalize()).decode() class WecomCryptoTests(unittest.TestCase): def setUp(self): patcher = mock.patch.dict(os.environ, _ENV) patcher.start() self.addCleanup(patcher.stop) def test_configured_gate(self): self.assertTrue(wc.callback_configured()) with mock.patch.dict(os.environ, {"WECOM_CALLBACK_TOKEN": ""}): self.assertFalse(wc.callback_configured()) def test_verify_url_roundtrip(self): echostr_plain = "echo-123-测试" enc = _encrypt(echostr_plain) sig = wc._signature("111", "222", enc) self.assertEqual(wc.verify_url(sig, "111", "222", enc, corpid=_CORPID), echostr_plain) def test_verify_url_bad_signature(self): enc = _encrypt("x") with self.assertRaises(ValueError): wc.verify_url("deadbeef", "111", "222", enc, corpid=_CORPID) def test_decrypt_message_roundtrip(self): plain_xml = ( "zhangsantext" "你好 世界42" ) enc = _encrypt(plain_xml) sig = wc._signature("111", "222", enc) envelope = f"{enc}" msg = wc.decrypt_message(sig, "111", "222", envelope, corpid=_CORPID) self.assertEqual(msg["FromUserName"], "zhangsan") self.assertEqual(msg["MsgType"], "text") self.assertEqual(msg["Content"], "你好 世界") def test_receiveid_mismatch_rejected(self): enc = _encrypt("text", receiveid="ww_other_corp") sig = wc._signature("111", "222", enc) with self.assertRaises(ValueError): wc.decrypt_message(sig, "111", "222", f"{enc}", corpid=_CORPID) def test_bad_padding_rejected(self): # 用错 key 加密 → 被测侧解出的 padding 大概率非法(1..32 之外)或后续解析崩 key = secrets.token_bytes(32) body = secrets.token_bytes(64) enc_obj = Cipher(algorithms.AES(key), modes.CBC(key[:16])).encryptor() garbage = base64.b64encode(enc_obj.update(body) + enc_obj.finalize()).decode() sig = wc._signature("111", "222", garbage) with self.assertRaises(Exception): wc.verify_url(sig, "111", "222", garbage, corpid=_CORPID) def test_parse_message_flat_tags(self): d = wc.parse_message("1") self.assertEqual(d, {"A": "1", "B": ""}) if __name__ == "__main__": unittest.main()