safeheron-api-sdk-python 1.0.3__tar.gz → 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeheron_api_sdk_python
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Python for Safeheron API
5
5
  Home-page: https://github.com/Safeheron/safeheron-api-sdk-python
6
6
  Author: safeheron
@@ -0,0 +1,28 @@
1
+ from safeheron_api_sdk_python.tools import *
2
+
3
+ class Client:
4
+
5
+ def __init__(self, config):
6
+ global api_key
7
+ global platform_pub_key
8
+ global use_private_key
9
+ global base_url
10
+ api_key = config['apiKey']
11
+ platform_pub_key = config['safeheronPublicKey']
12
+ base_url = config['baseUrl']
13
+ if 'privateKey' in config:
14
+ use_private_key = PEM_PRIVATE_HEAD + config['privateKey'] + PEM_PRIVATE_END
15
+ if 'privateKeyPemFile' in config:
16
+ private_key_pem_file = config['privateKeyPemFile']
17
+ if private_key_pem_file is not None and private_key_pem_file != '':
18
+ use_private_key = load_rsa_private_key(private_key_pem_file)
19
+
20
+ def send_request(self, request, uri):
21
+ req = encrypt_request(api_key, request, platform_pub_key, use_private_key)
22
+ res = self.execution(req, uri)
23
+ res.raise_for_status()
24
+ res = res.json()
25
+ return decrypt_response(res, platform_pub_key, use_private_key)
26
+
27
+ def execution(self, request, uri):
28
+ return requests.post(base_url + uri, data=json.dumps(request), headers={"Content-Type": "application/json"})
@@ -0,0 +1,256 @@
1
+ # Crypto 可直接替换为 Cryptodome
2
+ from Cryptodome.Cipher import AES
3
+ from Cryptodome.Util.Padding import pad, unpad
4
+ from Cryptodome.PublicKey import RSA
5
+ from Cryptodome.Random import get_random_bytes
6
+ from base64 import b64encode, b64decode
7
+ from Cryptodome.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
8
+ from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
9
+ from Cryptodome.Hash import SHA256, SHA1
10
+ from Cryptodome import Random
11
+ import json
12
+ import time
13
+ import requests
14
+ from cryptography.hazmat.primitives import serialization
15
+ from cryptography.hazmat.backends import default_backend
16
+
17
+
18
+ PEM_PUBLIC_HEAD = "-----BEGIN PUBLIC KEY-----\n"
19
+ PEM_PUBLIC_END = "\n-----END PUBLIC KEY-----"
20
+ PEM_PRIVATE_HEAD = "-----BEGIN RSA PRIVATE KEY-----\n"
21
+ PEM_PRIVATE_END = "\n-----END RSA PRIVATE KEY-----"
22
+
23
+ def load_rsa_private_key(file_path, password=None):
24
+ try:
25
+ with open(file_path, "rb") as key_file:
26
+ private_key = serialization.load_pem_private_key(
27
+ key_file.read(),
28
+ password=password,
29
+ backend=default_backend()
30
+ )
31
+ pem = private_key.private_bytes(
32
+ encoding=serialization.Encoding.PEM,
33
+ format=serialization.PrivateFormat.PKCS8,
34
+ encryption_algorithm=serialization.NoEncryption()
35
+ )
36
+ return pem.decode()
37
+ except Exception as e:
38
+ raise Exception("Failed to parse rsa pem file")
39
+
40
+ def check_key_and_iv(key: bytes, iv: bytes):
41
+ if len(key) != 32:
42
+ raise Exception("aes error: key must have 32 bytes")
43
+ if len(iv) != 16:
44
+ raise Exception("aes error: iv must have 16 bytes")
45
+
46
+
47
+ def aes_encrypt(key: bytes, iv: bytes, message: bytes):
48
+ check_key_and_iv(key, iv)
49
+ message = pad(message, AES.block_size)
50
+ try:
51
+ cipher = AES.new(key, AES.MODE_CBC, iv)
52
+ encrypt_text = cipher.encrypt(message)
53
+ except Exception as e:
54
+ raise Exception("aes encrypt error: %s" % e)
55
+ return encrypt_text
56
+
57
+
58
+ def aes_decrypt(key: bytes, iv: bytes, encrypt_text: bytes):
59
+ check_key_and_iv(key, iv)
60
+ try:
61
+ cipher = AES.new(key, AES.MODE_CBC, iv)
62
+ pad_text = cipher.decrypt(encrypt_text)
63
+ except Exception as e:
64
+ raise Exception("aes decrypt error: %s" % e)
65
+ return unpad(pad_text, AES.block_size)
66
+
67
+
68
+ def get_rsa_key(key_pem, passphrase=None):
69
+ if key_pem == '':
70
+ raise Exception("rsa encrypt error: pubkey can't be none")
71
+ if passphrase is None:
72
+ return RSA.import_key(key_pem)
73
+ else:
74
+ return RSA.import_key(key_pem, passphrase=passphrase)
75
+
76
+
77
+ def rsa_encrypt(public_key, message: bytes):
78
+ try:
79
+ cipher_rsa = Cipher_pkcs1_v1_5.new(public_key)
80
+ encrypt_text = cipher_rsa.encrypt(message)
81
+ except Exception as e:
82
+ raise Exception("rsa encrypt error: %s" % e)
83
+ encrypt_text_base64 = b64encode(encrypt_text)
84
+ return encrypt_text_base64.decode('utf-8')
85
+
86
+
87
+ def rsa_decrypt(private_key, message: str):
88
+ encrypt_text = b64decode(message)
89
+ try:
90
+ cipher_rsa = Cipher_pkcs1_v1_5.new(private_key)
91
+ sentinel = Random.new().read(SHA1.digest_size)
92
+ return cipher_rsa.decrypt(encrypt_text, sentinel)
93
+ except Exception as e:
94
+ raise Exception("rsa decrypt error: %s" % e)
95
+
96
+
97
+ def rsa_sign(private_key, message_hash: bytes):
98
+ try:
99
+ signer = Signature_pkcs1_v1_5.new(private_key)
100
+ digest = SHA256.new()
101
+ digest.update(message_hash)
102
+ signed = signer.sign(digest)
103
+ return b64encode(signed).decode()
104
+ except Exception as e:
105
+ raise Exception("rsa sign error: %s" % e)
106
+
107
+
108
+ def rsa_verify(public_key, message_hash: bytes, signature):
109
+ try:
110
+ signer = Signature_pkcs1_v1_5.new(public_key)
111
+ digest = SHA256.new()
112
+ digest.update(message_hash)
113
+ return signer.verify(digest, b64decode(signature))
114
+ except Exception as e:
115
+ raise Exception("rsa sign error: %s" % e)
116
+
117
+
118
+ def sort_request(r: dict):
119
+ sortData = json.dumps(r, sort_keys=True).replace(' ', '')
120
+ strArr = []
121
+ data1 = json.loads(sortData)
122
+ for key, value in data1.items():
123
+ # print(value, type(value))
124
+ if isinstance(value, dict): # 注意:对于dict类型的数据,一定要保留双引号,但python默认都是单引号
125
+ val = json.dumps(value).replace(' ', '')
126
+ else:
127
+ val = value
128
+ strArr.append(key + '=' + str(val))
129
+ return "&".join(strArr).encode('utf-8')
130
+
131
+
132
+ def encrypt_request(api_key, request_dict, platform_rsa_pk, api_user_rsa_sk):
133
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + platform_rsa_pk + PEM_PUBLIC_END)
134
+ api_user_rsa_sk = get_rsa_key(api_user_rsa_sk)
135
+
136
+ ret = dict()
137
+
138
+ ret['apiKey'] = api_key
139
+
140
+ # prepare aes key and iv
141
+ aes_key = get_random_bytes(32)
142
+ aes_iv = get_random_bytes(16)
143
+
144
+
145
+ # 1 rsa encrypt aes key + iv
146
+ aes_data = aes_key + aes_iv
147
+ ret['key'] = rsa_encrypt(platform_rsa_pk, aes_data)
148
+
149
+ # 2 aes encrypt request data
150
+ if request_dict is not None:
151
+ request_data = json.dumps(request_dict.__dict__).replace('\'', '\"').replace('\n', '').encode('utf-8')
152
+ aes_encrypted_bytes = aes_encrypt(aes_key, aes_iv, request_data)
153
+ ret['bizContent'] = b64encode(aes_encrypted_bytes).decode()
154
+
155
+ # 3 set timestamp
156
+ ret['timestamp'] = str(int(time.time() * 1000))
157
+
158
+ # 4 sign request
159
+ need_sign_message = sort_request(ret)
160
+ ret['sig'] = rsa_sign(api_user_rsa_sk, need_sign_message)
161
+
162
+ return ret
163
+
164
+
165
+ def decrypt_response(response_dict, platform_rsa_pk, api_user_rsa_sk):
166
+ platform_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + platform_rsa_pk + PEM_PUBLIC_END)
167
+ api_user_rsa_sk = get_rsa_key(api_user_rsa_sk)
168
+ required_keys = {
169
+ 'key',
170
+ 'sig',
171
+ 'bizContent',
172
+ 'timestamp',
173
+ 'code',
174
+ 'message'
175
+ }
176
+ missing_keys = required_keys.difference(response_dict.keys())
177
+ if missing_keys:
178
+ raise Exception(response_dict)
179
+
180
+ # 1 rsa verify
181
+ sig = response_dict.pop('sig')
182
+ need_sign_message = sort_request(response_dict)
183
+ v = rsa_verify(platform_rsa_pk, need_sign_message, sig)
184
+ if not v:
185
+ raise Exception("rsa verify: false")
186
+
187
+ # 2 get aes key and iv
188
+ key = response_dict.pop('key')
189
+ aes_data = rsa_decrypt(api_user_rsa_sk, key)
190
+ aes_key = aes_data[0:32]
191
+ aes_iv = aes_data[32:48]
192
+
193
+ # 3 aes decrypt data, get response data
194
+ r = aes_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
195
+ #response_dict['bizContent'] = json.loads(r.decode())
196
+
197
+ return json.loads(r.decode())
198
+
199
+ #Used for webhook decryption callback request data
200
+ def decrypt_request(response_dict, verify_rsa_pk, decrypt_rsa_sk):
201
+ verify_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + verify_rsa_pk + PEM_PUBLIC_END)
202
+ decrypt_rsa_sk = get_rsa_key(decrypt_rsa_sk)
203
+ # 1 rsa verify
204
+ sig = response_dict.pop('sig')
205
+ need_sign_message = sort_request(response_dict)
206
+ v = rsa_verify(verify_rsa_pk, need_sign_message, sig)
207
+ if not v:
208
+ raise Exception("rsa verify: false")
209
+
210
+ # 2 get aes key and iv
211
+ aes_data = rsa_decrypt(decrypt_rsa_sk, response_dict['key'])
212
+ aes_key = aes_data[0:32]
213
+ aes_iv = aes_data[32:48]
214
+
215
+ # 3 aes decrypt data, get response data
216
+ r = aes_decrypt(aes_key, aes_iv, b64decode(response_dict['bizContent']))
217
+
218
+ return json.loads(r.decode())
219
+
220
+ #For webhook to send encrypted response content
221
+ def encrypt_response(raw_data,encrpyt_rsa_pk, sign_rsa_sk):
222
+ encrpyt_rsa_pk = get_rsa_key(PEM_PUBLIC_HEAD + encrpyt_rsa_pk + PEM_PUBLIC_END)
223
+ sign_rsa_sk = get_rsa_key(sign_rsa_sk)
224
+ ret = dict()
225
+ # prepare aes key and iv
226
+ aes_key = get_random_bytes(32)
227
+ aes_iv = get_random_bytes(16)
228
+ res_data = json.dumps(raw_data).replace('\n', '').encode('utf-8')
229
+
230
+ # 1 rsa encrypt aes key + iv
231
+ aes_data = aes_key + aes_iv
232
+ ret['key'] = rsa_encrypt(encrpyt_rsa_pk, aes_data)
233
+
234
+ # 2 aes encrypt request data
235
+ aes_encrypted_bytes = aes_encrypt(aes_key, aes_iv, res_data)
236
+ ret['bizContent'] = b64encode(aes_encrypted_bytes).decode()
237
+
238
+ # 3 set timestamp , no need to add
239
+ ret['timestamp'] = str(int(time.time() * 1000))
240
+
241
+ # 4 code
242
+ ret['code'] = "200"
243
+
244
+ # 5 message
245
+ ret['message'] = "callback_ok"
246
+
247
+ # 6 sign request
248
+ need_sign_message = sort_request(ret)
249
+ ret['sig'] = rsa_sign(sign_rsa_sk, need_sign_message)
250
+ return ret
251
+
252
+ def rsa_gen_key():
253
+ rsa_key = RSA.generate(4096)
254
+ sk = rsa_key.export_key()
255
+ pk = rsa_key.publickey().export_key()
256
+ return sk, pk
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
- Name: safeheron-api-sdk-python
3
- Version: 1.0.3
2
+ Name: safeheron_api_sdk_python
3
+ Version: 1.0.5
4
4
  Summary: Python for Safeheron API
5
5
  Home-page: https://github.com/Safeheron/safeheron-api-sdk-python
6
6
  Author: safeheron
@@ -2,6 +2,8 @@ LICENSE
2
2
  README.md
3
3
  README.rst
4
4
  setup.py
5
+ safeheron_api_sdk_python/client.py
6
+ safeheron_api_sdk_python/tools.py
5
7
  safeheron_api_sdk_python.egg-info/PKG-INFO
6
8
  safeheron_api_sdk_python.egg-info/SOURCES.txt
7
9
  safeheron_api_sdk_python.egg-info/dependency_links.txt
@@ -3,7 +3,7 @@ from setuptools import find_packages
3
3
  with open("README.rst", "r") as f:
4
4
  long_description = f.read()
5
5
  setup(name='safeheron_api_sdk_python',
6
- version='1.0.3',
6
+ version='1.0.5',
7
7
  description='Python for Safeheron API',
8
8
  long_description=long_description,
9
9
  author='safeheron',
@@ -11,7 +11,7 @@ setup(name='safeheron_api_sdk_python',
11
11
  url='https://github.com/Safeheron/safeheron-api-sdk-python',
12
12
  install_requires=[],
13
13
  license='MIT License',
14
- packages=find_packages(),
14
+ packages=find_packages(include=['safeheron_api_sdk_python', 'safeheron_api_sdk_python.*']),
15
15
  platforms=["all"],
16
16
  classifiers=[
17
17
  'Operating System :: OS Independent',