|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
10 |
|
""" |
|
3
|
|
|
wechatpy.crypto |
|
4
|
|
|
~~~~~~~~~~~~~~~~ |
|
5
|
|
|
|
|
6
|
|
|
This module provides some crypto tools for WeChat and WeChat enterprise |
|
7
|
|
|
|
|
8
|
|
|
:copyright: (c) 2014 by messense. |
|
9
|
|
|
:license: MIT, see LICENSE for more details. |
|
10
|
|
|
""" |
|
11
|
10 |
|
from __future__ import absolute_import, unicode_literals |
|
12
|
10 |
|
import time |
|
13
|
10 |
|
import base64 |
|
14
|
|
|
|
|
15
|
10 |
|
from wechatpy.utils import to_text, to_binary, WeChatSigner |
|
16
|
10 |
|
from wechatpy.exceptions import ( |
|
17
|
|
|
InvalidAppIdException, |
|
18
|
|
|
InvalidSignatureException |
|
19
|
|
|
) |
|
20
|
10 |
|
from wechatpy.crypto.base import BasePrpCrypto |
|
21
|
|
|
|
|
22
|
|
|
|
|
23
|
10 |
|
def _get_signature(token, timestamp, nonce, encrypt): |
|
24
|
10 |
|
signer = WeChatSigner() |
|
25
|
10 |
|
signer.add_data(token, timestamp, nonce, encrypt) |
|
26
|
10 |
|
return signer.signature |
|
27
|
|
|
|
|
28
|
|
|
|
|
29
|
10 |
|
class PrpCrypto(BasePrpCrypto): |
|
30
|
|
|
|
|
31
|
10 |
|
def encrypt(self, text, app_id): |
|
32
|
|
|
return self._encrypt(text, app_id) |
|
33
|
|
|
|
|
34
|
10 |
|
def decrypt(self, text, app_id): |
|
35
|
|
|
return self._decrypt(text, app_id, InvalidAppIdException) |
|
36
|
|
|
|
|
37
|
|
|
|
|
38
|
10 |
|
class BaseWeChatCrypto(object): |
|
39
|
|
|
|
|
40
|
10 |
|
def __init__(self, token, encoding_aes_key, _id): |
|
41
|
10 |
|
encoding_aes_key = to_binary(encoding_aes_key + '=') |
|
42
|
10 |
|
self.key = base64.b64decode(encoding_aes_key) |
|
43
|
10 |
|
assert len(self.key) == 32 |
|
44
|
10 |
|
self.token = token |
|
45
|
10 |
|
self._id = _id |
|
46
|
|
|
|
|
47
|
10 |
|
def _check_signature(self, |
|
48
|
|
|
signature, |
|
49
|
|
|
timestamp, |
|
50
|
|
|
nonce, |
|
51
|
|
|
echo_str, |
|
52
|
|
|
crypto_class=None): |
|
53
|
10 |
|
_signature = _get_signature(self.token, timestamp, nonce, echo_str) |
|
54
|
10 |
|
if _signature != signature: |
|
55
|
10 |
|
raise InvalidSignatureException() |
|
56
|
10 |
|
pc = crypto_class(self.key) |
|
57
|
10 |
|
return pc.decrypt(echo_str, self._id) |
|
58
|
|
|
|
|
59
|
10 |
|
def _encrypt_message(self, |
|
60
|
|
|
msg, |
|
61
|
|
|
nonce, |
|
62
|
|
|
timestamp=None, |
|
63
|
|
|
crypto_class=None): |
|
64
|
10 |
|
from wechatpy.replies import BaseReply |
|
65
|
|
|
|
|
66
|
10 |
|
xml = """<xml> |
|
67
|
|
|
<Encrypt><![CDATA[{encrypt}]]></Encrypt> |
|
68
|
|
|
<MsgSignature><![CDATA[{signature}]]></MsgSignature> |
|
69
|
|
|
<TimeStamp>{timestamp}</TimeStamp> |
|
70
|
|
|
<Nonce><![CDATA[{nonce}]]></Nonce> |
|
71
|
|
|
</xml>""" |
|
72
|
10 |
|
if isinstance(msg, BaseReply): |
|
73
|
|
|
msg = msg.render() |
|
74
|
10 |
|
timestamp = timestamp or to_binary(int(time.time())) |
|
75
|
10 |
|
pc = crypto_class(self.key) |
|
76
|
10 |
|
encrypt = to_text(pc.encrypt(msg, self._id)) |
|
77
|
10 |
|
signature = _get_signature(self.token, timestamp, nonce, encrypt) |
|
78
|
10 |
|
return to_text(xml.format( |
|
79
|
|
|
encrypt=encrypt, |
|
80
|
|
|
signature=signature, |
|
81
|
|
|
timestamp=timestamp, |
|
82
|
|
|
nonce=nonce |
|
83
|
|
|
)) |
|
84
|
|
|
|
|
85
|
10 |
|
def _decrypt_message(self, |
|
86
|
|
|
msg, |
|
87
|
|
|
signature, |
|
88
|
|
|
timestamp, |
|
89
|
|
|
nonce, |
|
90
|
|
|
crypto_class=None): |
|
91
|
10 |
|
if not isinstance(msg, dict): |
|
92
|
10 |
|
import xmltodict |
|
93
|
|
|
|
|
94
|
10 |
|
msg = xmltodict.parse(to_text(msg))['xml'] |
|
95
|
|
|
|
|
96
|
10 |
|
encrypt = msg['Encrypt'] |
|
97
|
10 |
|
_signature = _get_signature(self.token, timestamp, nonce, encrypt) |
|
98
|
10 |
|
if _signature != signature: |
|
99
|
|
|
raise InvalidSignatureException() |
|
100
|
10 |
|
pc = crypto_class(self.key) |
|
101
|
10 |
|
return pc.decrypt(encrypt, self._id) |
|
102
|
|
|
|
|
103
|
|
|
|
|
104
|
10 |
|
class WeChatCrypto(BaseWeChatCrypto): |
|
105
|
|
|
|
|
106
|
10 |
|
def __init__(self, token, encoding_aes_key, app_id): |
|
107
|
10 |
|
super(WeChatCrypto, self).__init__(token, encoding_aes_key, app_id) |
|
108
|
10 |
|
self.app_id = app_id |
|
109
|
|
|
|
|
110
|
10 |
|
def encrypt_message(self, msg, nonce, timestamp=None): |
|
111
|
|
|
return self._encrypt_message(msg, nonce, timestamp, PrpCrypto) |
|
112
|
|
|
|
|
113
|
10 |
|
def decrypt_message(self, msg, signature, timestamp, nonce): |
|
114
|
|
|
return self._decrypt_message( |
|
115
|
|
|
msg, |
|
116
|
|
|
signature, |
|
117
|
|
|
timestamp, |
|
118
|
|
|
nonce, |
|
119
|
|
|
PrpCrypto |
|
120
|
|
|
) |
|
121
|
|
|
|