1use crate::{error::Error, Result};
2use aes::{
3 cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit},
4 Aes256,
5};
6use sha1::{Digest, Sha1};
7
8const AES_ENCODE_KEY_LEN: usize = 43;
9
10pub struct WecomCrypto {
11 token: String,
12 receive_id: String,
13 byte_aes_key: Vec<u8>,
14}
15
16impl WecomCrypto {
17 pub fn new(token: &str, receive_id: &str, aes_key: &str) -> Result<Self> {
18 if aes_key.len() != AES_ENCODE_KEY_LEN {
19 return Err(Error::General(String::from("不合法的EncodingAESKey")));
20 }
21
22 let byte_aes_key = base64::decode_config(
23 format!("{}=", aes_key),
24 base64::STANDARD.decode_allow_trailing_bits(true),
25 )
26 .unwrap();
27
28 Ok(Self {
29 token: token.to_string(),
30 receive_id: receive_id.to_string(),
31 byte_aes_key,
32 })
33 }
34
35 pub fn encrypt_msg(&self, msg: &str, timestamp: &str, nonce: &str) -> Result<(String, String)> {
44 let size = (msg.len() as u32).to_be_bytes();
45
46 let msg = format!(
47 "{}{}{}{}",
48 random_string(16),
49 String::from_utf8_lossy(&size),
50 msg,
51 self.receive_id
52 );
53
54 let cipher_msg = base64::encode(self.encrypt(&msg));
55 let signature = self.create_signature(&cipher_msg, ×tamp, &nonce);
56
57 Ok((cipher_msg, signature))
58 }
59
60 fn encrypt(&self, msg: &str) -> Vec<u8> {
62 let padded_message = pad_pkcs7(msg, 16);
63 let msg_bytes = padded_message.as_bytes();
64 let iv = &self.byte_aes_key[..16].to_vec();
65
66 let b_key_slice = &self.byte_aes_key[..];
67 let cipher = Aes256::new(b_key_slice.into());
68
69 let mut encrypted_blocks: Vec<Vec<u8>> = Vec::new();
70 (0..msg.len()).step_by(16).for_each(|x| {
71 let last = encrypted_blocks.last().unwrap_or(iv);
72 let xor_block = xor_bytes(last, &msg_bytes[x..x + 16]);
73 let mut block = GenericArray::clone_from_slice(&xor_block);
74
75 cipher.encrypt_block(&mut block);
76 encrypted_blocks.push(block.into_iter().collect::<Vec<u8>>());
77 });
78
79 encrypted_blocks.into_iter().flatten().collect::<Vec<u8>>()
80 }
81
82 pub fn decrypt_msg(
83 &self,
84 msg_signature: &str,
85 timestamp: &str,
86 nonce: &str,
87 cipher_msg: &str,
88 ) -> Result<String> {
89 if !self.verification_signature(msg_signature, timestamp, nonce, cipher_msg) {
90 return Err(Error::General("签名不匹配".to_string()));
91 }
92
93 let msg = self.decrypt(cipher_msg);
94 let size = u32::from_be_bytes(msg[16..16 + 4].try_into().unwrap()) as usize;
95 let plant_text = &msg[16 + 4..];
96 let receive_id = String::from_utf8_lossy(&plant_text[size..]);
97
98 assert_eq!(self.receive_id, receive_id);
99
100 Ok(format!("{}", String::from_utf8_lossy(&plant_text[..size])))
101 }
102
103 pub fn decrypt(&self, secret_msg: &str) -> Vec<u8> {
105 let encrypted_bytes = base64::decode(secret_msg).unwrap();
106 let iv = &self.byte_aes_key[..16].to_vec();
107 let b_key_slice = &self.byte_aes_key[..];
108
109 let cipher = Aes256::new(b_key_slice.into());
110 let mut decrypted_blocks: Vec<Vec<u8>> = Vec::new();
111 (0..encrypted_bytes.len()).step_by(16).for_each(|x| {
112 let last = if x == 0 {
113 &iv
114 } else {
115 &encrypted_bytes[x - 16..x]
116 };
117
118 let mut block = GenericArray::clone_from_slice(&encrypted_bytes[x..x + 16]);
119 cipher.decrypt_block(&mut block);
120 let decrypted_block = block.into_iter().collect::<Vec<u8>>();
121 let xor_block = xor_bytes(last, &decrypted_block);
122 decrypted_blocks.push(xor_block);
123 });
124
125 let padding_byte = *decrypted_blocks.last().unwrap().last().unwrap() as usize;
127
128 decrypted_blocks
129 .into_iter()
130 .flatten()
131 .take(encrypted_bytes.len() - padding_byte)
132 .collect::<Vec<u8>>()
133 }
134
135 fn verification_signature(
136 &self,
137 signature: &str,
138 timestamp: &str,
139 nonce: &str,
140 msg: &str,
141 ) -> bool {
142 self.create_signature(msg, timestamp, nonce) == signature
143 }
144
145 fn create_signature(&self, msg: &str, timestamp: &str, nonce: &str) -> String {
146 let mut params: Vec<String> = vec![];
147 params.push(self.token.clone());
148 params.push(timestamp.to_string());
149 params.push(nonce.to_string());
150 params.push(msg.to_string());
151 params.sort();
152 let x = params.join("");
153 let mut hasher = Sha1::new();
154 hasher.update(x.as_bytes());
155 format!("{:x}", hasher.finalize())
156 }
157}
158
159fn xor_bytes(bytes1: &[u8], bytes2: &[u8]) -> Vec<u8> {
160 bytes1
161 .iter()
162 .zip(bytes2.iter())
163 .map(|(&b1, &b2)| b1 ^ b2)
164 .collect()
165}
166
167fn random_string(len: usize) -> String {
169 use rand::{distributions::Alphanumeric, thread_rng, Rng};
170 thread_rng()
171 .sample_iter(&Alphanumeric)
172 .take(len)
173 .map(char::from)
174 .collect()
175}
176
177fn pad_pkcs7(message: &str, block_size: usize) -> String {
179 let padding_size = block_size - message.len() % block_size;
180 let padding_char = padding_size as u8 as char;
181 let padding: String = (0..padding_size).map(|_| padding_char).collect();
182 format!("{}{}", message, padding)
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use anyhow::Result;
189
190 #[test]
191 fn test_msg_decrypt() -> Result<()> {
192 let token = "QDG6eK";
193 let aes_key = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C";
194 let corp_id = "wx5823bf96d3bd56c7";
195 let content = "RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==";
196 let signature = "477715d11cdb4164915debcba66cb864d751f3e6";
197 let timestamp = "1409659813";
198 let nonce = "1372623149";
199
200 let c = WecomCrypto::new(token, corp_id, aes_key)?;
201 let data = c.decrypt_msg(signature, timestamp, nonce, content)?;
202 assert_eq!(
203 r#"<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName>
204<FromUserName><![CDATA[mycreate]]></FromUserName>
205<CreateTime>1409659813</CreateTime>
206<MsgType><![CDATA[text]]></MsgType>
207<Content><![CDATA[hello]]></Content>
208<MsgId>4561255354251345929</MsgId>
209<AgentID>218</AgentID>
210</xml>"#,
211 data
212 );
213 Ok(())
214 }
215
216 #[test]
217 fn test_msg_encrypt() -> Result<()> {
218 let token = "123456";
219 let aes_key = "4g5j64qlyl3zvetqxz5jiocdr586fn2zvjpa8zls3ij";
220 let corp_id = "suite4xxxxxxxxxxxxxxx221";
221 let timestamp = "1445827045067";
222 let content = "succ111ess";
223 let nonce = "nEXhMP4r";
224 let c = WecomCrypto::new(token, corp_id, aes_key)?;
225 let (_cipher_msg, _signature) = c.encrypt_msg(content, timestamp, nonce)?;
226 println!("signature:{_signature}, cipher_msg:{_cipher_msg}");
227 Ok(())
228 }
229}