demo.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # -*- coding: utf-8 -*-
  2. # Author : Charley
  3. # Python : 3.10.8
  4. # Date : 2025/6/23 15:41
  5. import time
  6. import requests
  7. import base64
  8. from urllib.parse import urlparse, parse_qs, urlencode
  9. from Crypto.PublicKey import RSA
  10. from Crypto.Signature import pkcs1_15
  11. from Crypto.Hash import SHA256
  12. from pyasn1.codec.der import decoder
  13. from pyasn1_modules import rfc8017
  14. import urllib3
  15. urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
  16. HEADERS = {
  17. "user-agent": "Kuril+/5.47.1 (Android 11)",
  18. "cache-control": "max-age=3600",
  19. "x-request-version": "5.47.1",
  20. "x-request-sign-type": "RSA2",
  21. "x-request-package-sign-version": "0.0.1",
  22. "x-request-utm_source": "yingyongbao",
  23. "content-type": "application/json",
  24. "x-request-id": "",
  25. "x-request-sign-version": "v1",
  26. "referer": "https://qiandao.cn",
  27. "x-request-channel": "yingyongbao",
  28. "x-device-id": "fbc87d023bf9ff1b",
  29. "host": "api.qiandao.com",
  30. "x-request-package-id": "1006",
  31. "x-request-device": "android",
  32. "x-request-version-code": "245"
  33. }
  34. def get_params_dict(url):
  35. '''
  36. 因app对list值做过特殊处理
  37. 从URL里提取出params
  38. :param url:
  39. :return:
  40. '''
  41. query = urlparse(url).query
  42. parsed = parse_qs(query, keep_blank_values=True)
  43. params = {}
  44. for key, value in parsed.items():
  45. clean_key = key.rstrip("[]")
  46. params[clean_key] = value[0] if len(value) == 1 else value
  47. return params
  48. def get_sign(s):
  49. '''
  50. 获取签名
  51. :param s:
  52. :return:
  53. '''
  54. modulus_b64_str = (
  55. "MIIEpQIBAAKCAQEA5d6YfNTsNo5qrD16Lu4TrfrWND5QhuXQH/1ysF1p9rmcJ0SebEAJvaw0rWbZdDFK"
  56. "YYQwKiw6wikZhX5tXPf0XvQ1aTzzPqc5d3ZYm0kCSaP5+Qs4Mv+xVT10f/xoFxrCXCqq/kEpOvS/GGJAP"
  57. "H7zQV3fTwYZWQFrj01sJ2wO/nckn6TCNLt56dBUgYEL6lmOaLPbYxQqDFBkCIjYYKJJ228wpBLyuoCYxa"
  58. "8tqUHz2WgRTpWmlL/1sco8p7bC2DTYM9n6LgEyndDmfGGlIX09Tu+wXZmPbny3IZ97BpvB5pa7x1X/9lr"
  59. "n5FIOxBdH9kh2/n/hb5UB/wR1/C9tJuuafwIDAQABAoIBAQDGIV79+ei//XEklLjDyqFbzGDlFvEB1QPX"
  60. "DvXT3jB/YOyfTB3g4DGFMvEUpRm5dOLPushpEUZ0JEjDL33ELFSNo6CF3OssjaaSuYcWEY/POW80od8G1"
  61. "i1bc2T/C+gMQhxUpNJN5IxNLLeppMYJXsL9DJR14KPoe7jiA7G9KP6jhRuHH4JC+b91swCqB1bNsh6kWr"
  62. "C6dxfhJtlFNkIkZXdenQ3KCoFnApBvsv7bMsLIFqYyv2QRH+KVioV0t+XrCfHoQ4qMzUgFgel6ITcT4JZ"
  63. "DGGH+CmPEeiP4TXFtSs32Ii0qJ0CDjXaBWjwRCGWK07VRRZL6FLNYf6el12JgWACBAoGBAOfVDK5jnD9i"
  64. "5YLY9OdDFtyO4LtojObGJUnYH3Y2IoEiaArKWYsKQtgGI76TwdJwj2F7blQNXm2C/3tlrqeZOhz1LZqVa"
  65. "Nflkhml8/ghYZENcrTUTGKWTLmZnWWUQQZHN5aqHb0DTQPbqD6YspqVvlrjF2yJs1/UBZc7GiGbYSc5Ao"
  66. "GBAP3VKrl+QpESRa7B/6+GezvPV0DNf/b9zuTEMO/EljWLEnnSghBFBAlk2qihL4Hoo/qmWk2POwMHLYm"
  67. "b9Q9zHYzvwbcUOiQIpraFrN0Wn9B+nwDff0Wl+6vyA0W6AylBQHU4GfCZuts6qAMX+fKTAF/h24Ml36/1"
  68. "Lci45NIN3ld3AoGBANSSKXqNo2sLh16fCJA0l/XMnIu6pdfEv9Qh81c09BZsMfIS8F/pHLlvh77rRMFsr"
  69. "Eu6HcO8LmVDxHalGaxbd0muFg60CNpNidUysa1HDmsuZYshTpjnL5rPG99UPPtAudvQSExThn6PHomnAb"
  70. "10qII1z/iZmnu3sRil/KPsEP0hAoGBAK2BETQ77sp0//almt1jAkduwciE74xoDwzmYkDyUm6FAnsM/mS"
  71. "amFjHfIM5slyNJdFF9oH/fqniNSlT1l3aJP/aPsKi698HntUyaGezeEgu1QbmvntgKrhss/nsXQ7NEH9P"
  72. "esOwgT4rSP7cW7iI7P+dRcvOjqka4VHLuHUwj6OfAoGACezeNNpkxd8PhgjYkAHjtSnrEHgzQB1YErvAr"
  73. "jssDWQtkne6jL669lyx0al/f2dIfUxaPp79aOCETle9n/2mpng7vjrM8sGZuvKcsgs1ZsEOLepIEUIaYv"
  74. "m0S4kmzwYKVmhxhJzPjWX9scCgORQkNctsOKujmzNvBLwSxbpuxTE="
  75. )
  76. # base64 解码 modulus_b64_str 得到 ASN.1 结构(modulus 和 privateExponent)
  77. key_der = base64.b64decode(modulus_b64_str)
  78. # 尝试解码为 RSAPrivateKey 格式(标准 PKCS#1)
  79. private_key_asn1, _ = decoder.decode(key_der, asn1Spec=rfc8017.RSAPrivateKey())
  80. n = int(private_key_asn1['modulus'])
  81. d = int(private_key_asn1['privateExponent'])
  82. # 构造 RSA 私钥
  83. key = RSA.construct((n, 65537, d)) # 公共指数默认是 65537(0x10001)
  84. # 进行 SHA256 with RSA 签名
  85. h = SHA256.new(s.encode('utf-8'))
  86. signature = pkcs1_15.new(key).sign(h)
  87. # 返回 base64 编码的签名结果
  88. return base64.b64encode(signature).decode()
  89. def get_query_string(params, reverse=False):
  90. '''
  91. 对参数进行倒序排列
  92. :param params:
  93. :return:
  94. '''
  95. if not params:
  96. return ''
  97. query_items = []
  98. for key, value in params.items():
  99. if isinstance(value, list):
  100. for v in value:
  101. query_items.append((f"{key}[]", v))
  102. else:
  103. query_items.append((key, value))
  104. if reverse:
  105. query_items = sorted(query_items, key=lambda x: x[0], reverse=reverse)
  106. return urlencode(query_items)
  107. def get_headers(url, params=None):
  108. '''
  109. 设置请求头
  110. :param url:
  111. :return:
  112. '''
  113. ts = str(int(time.time() * 1000))
  114. query_string = get_query_string(params, reverse=True)
  115. sign = get_sign(f'{urlparse(url).path}{query_string}{ts}')
  116. HEADERS["x-request-timestamp"] = ts
  117. HEADERS["x-request-sign"] = sign
  118. return HEADERS
  119. def reuqest_post_data(url, data):
  120. '''
  121. 发送POST请求
  122. :param url:
  123. :param data:
  124. :return:
  125. '''
  126. headers = get_headers(url)
  127. response = requests.post(url, headers=headers, json=data, verify=False)
  128. response.raise_for_status()
  129. return response.json()
  130. def reuqest_get_data(url, params):
  131. '''
  132. 发送GET请求
  133. :param url:
  134. :param params:
  135. :return:
  136. '''
  137. headers = get_headers(url, params)
  138. # GET请求对url参数进行了特殊的处理
  139. query_string = get_query_string(params)
  140. url = f'{url}?{query_string}'
  141. response = requests.get(url, headers=headers, verify=False)
  142. # print(response.url)
  143. response.raise_for_status()
  144. return response.json()