#!/usr/bin/env python3 """Verify Alibaba AOP signatureWithParamsAndUrlPath against a known-good URL.""" import hashlib import hmac from urllib.parse import unquote EXPECTED = "c950e08db859f1d3899d88a30dc2b8b46e5b3394" URL_PATH = "param2/1/ali.intl.onetouch/logistics.order.notifyBizEvent/842172" # Exact notifyBizEventDTO value from the working URL (query-decoded once) DTO_FROM_URL = unquote( "%7B%22aliOrderNo%22%3A%22ALS01691955122%22%2C%22apiMessageCode%22%3A31%2C" "%22eventCode%22%3A%22WAREHOUSE_INBOUND%22%2C%22eventTime%22%3A%222026-06-06T09%3A24%3A50%2B0800%22%2C" "%22extInfo%22%3A%22%22%2C%22location%22%3A%22CHINA+SHENZHEN%22%2C%22reason%22%3A%22%22%2C%22status%22%3A%22SUCCESS%22%7D" ) # C# JsonConvert.SerializeObject often uses space after colon/comma DTO_CSHARP_LIKE = ( '{"aliOrderNo":"ALS01691955122","apiMessageCode":31,"eventCode":"WAREHOUSE_INBOUND",' '"eventTime":"2026-06-06T09:24:50+0800","extInfo":"","location":"CHINA SHENZHEN",' '"reason":"","status":"SUCCESS"}' ) # location with + instead of space (as in URL before decode) DTO_LOCATION_PLUS = DTO_FROM_URL.replace("CHINA SHENZHEN", "CHINA+SHENZHEN") def hmac_sha1_hex_lower(secret: str, data: str) -> str: return hmac.new(secret.encode("utf-8"), data.encode("utf-8"), hashlib.sha1).hexdigest() def hmac_sha1_hex_upper(secret: str, data: str) -> str: return hmac_sha1_hex_lower(secret, data).upper() def sign_url_path_params(secret: str, url_path: str, params: dict[str, str]) -> str: """1688 CommonUtil.signatureWithParamsAndUrlPath style.""" parts = [url_path] for key in sorted(params.keys()): parts.append(key + params[key]) sign_str = "".join(parts) return hmac_sha1_hex_lower(secret, sign_str), sign_str def sign_py_style(secret: str, url_path: str, dto: str) -> tuple[str, str, str]: """Match E:/py脚本/平台exe/al_tax_core.py sign().""" param_list = ["notifyBizEventDTO" + dto] param_list.sort() sign_str = url_path + "".join(param_list) digest = hmac.new(secret.encode("utf-8"), sign_str.encode("utf-8"), hashlib.sha1).hexdigest() return digest, digest.upper(), sign_str def main() -> None: import sys secret = sys.argv[1] if len(sys.argv) > 1 else "5VGqKYWs4UOA" candidates = { "dto_from_url": DTO_FROM_URL, "dto_csharp_like": DTO_CSHARP_LIKE, "dto_location_plus": DTO_LOCATION_PLUS, } print("Expected:", EXPECTED) print("Secret:", secret[:4] + "***") for label, dto in candidates.items(): lower_sorted, sign_str = sign_url_path_params( secret, URL_PATH, {"notifyBizEventDTO": dto} ) py_lower, py_upper, py_str = sign_py_style(secret, URL_PATH, dto) match_lower = lower_sorted == EXPECTED match_py_lower = py_lower == EXPECTED match_py_upper = py_upper.lower() == EXPECTED print(f"\n[{label}]") print(" sorted-dict lower:", lower_sorted, "MATCH" if match_lower else "") print(" py-style lower:", py_lower, "MATCH" if match_py_lower else "") print(" py-style upper:", py_upper, "MATCH" if py_upper == EXPECTED else "") print(" sign_str len:", len(py_str)) if py_str != sign_str: print(" NOTE: sign_str differs between implementations") if __name__ == "__main__": main()