wecom_rs/utils/
crypto.rs

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    /// 消息加密
36    /// @params:
37    ///   msg: 消息体明文
38    ///   timestamp: 时间戳
39    ///   nonce: 随机字符串
40    /// @return
41    ///   0: 加密消息
42    ///   1: 签名
43    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, &timestamp, &nonce);
56
57        Ok((cipher_msg, signature))
58    }
59
60    /// 加密明文字符串
61    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    /// 解密消息数据
104    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        // Get number of padding bytes applied during encryption & remove padding
126        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
167/// 生成随机字符串
168fn 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
177/// 加密补位
178fn 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}