dingtalk_rs/client/event_subscribe/
callback_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 DingTalkCrypto {
11    aes_token: String,
12    app_key: String,
13    byte_aes_key: Vec<u8>,
14}
15
16impl DingTalkCrypto {
17    pub fn new(aes_token: &str, app_key: &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            aes_token: aes_token.to_string(),
30            app_key: app_key.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.app_key
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, message: &str) -> Vec<u8> {
62        let padded_message = pad_pkcs7(message, 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..message.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        signature: &str,
85        timestamp: &str,
86        nonce: &str,
87        cipher_msg: &str,
88    ) -> Result<String> {
89        if !self.verification_signature(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 cropid = String::from_utf8_lossy(&plant_text[size..]);
97
98        assert_eq!(self.app_key, cropid);
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.aes_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 = "123456";
193        let aes_key = "4g5j64qlyl3zvetqxz5jiocdr586fn2zvjpa8zls3ij";
194        let app_key = "suite4xxxxxxxxxxxxxxx";
195        let content = "1a3NBxmCFwkCJvfoQ7WhJHB+iX3qHPsc9JbaDznE1i03peOk1LaOQoRz3+nlyGNhwmwJ3vDMG+OzrHMeiZI7gTRWVdUBmfxjZ8Ej23JVYa9VrYeJ5as7XM/ZpulX8NEQis44w53h1qAgnC3PRzM7Zc/D6Ibr0rgUathB6zRHP8PYrfgnNOS9PhSBdHlegK+AGGanfwjXuQ9+0pZcy0w9lQ==";
196        let signature = "5a65ceeef9aab2d149439f82dc191dd6c5cbe2c0";
197        let timestamp = "1445827045067";
198        let nonce = "nEXhMP4r";
199
200        let c = DingTalkCrypto::new(token, app_key, aes_key)?;
201        let data = c.decrypt_msg(signature, timestamp, nonce, content)?;
202        assert_eq!(
203            r#"{"EventType":"check_create_suite_url","Random":"LPIdSnlF","TestSuiteKey":"suite4xxxxxxxxxxxxxxx"}"#,
204            data
205        );
206        Ok(())
207    }
208
209    #[test]
210    fn test_decrypt() -> Result<()> {
211        let token = "123456";
212        let aes_key = "4g5j64qlyl3zvetqxz5jiocdr586fn2zvjpa8zls3ij";
213        let corp_id = "suite4xxxxxxxxxxxxxxx221";
214
215        let c = DingTalkCrypto::new(token, corp_id, aes_key)?;
216        let x = c.decrypt("DwCJ9BMxu4r+VTe9TD+AWc1yfxqs7d2YgepFe4XruMgh1+6pTjCCq4nqAM6StRcafOx5a1OfwmX3ckSBifatlA==");
217        assert_eq!(
218            "5p7b4jXMWUshiBCosucc111esssuite4xxxxxxxxxxxxxxx221",
219            String::from_utf8_lossy(&x)
220        );
221        Ok(())
222    }
223
224    #[test]
225    fn test_msg_encrypt() -> Result<()> {
226        let token = "123456";
227        let aes_key = "4g5j64qlyl3zvetqxz5jiocdr586fn2zvjpa8zls3ij";
228        let corp_id = "suite4xxxxxxxxxxxxxxx221";
229        let timestamp = "1445827045067";
230        let content = "succ111ess";
231        let nonce = "nEXhMP4r";
232        let c = DingTalkCrypto::new(token, corp_id, aes_key)?;
233        let (_cipher_msg, _signature) = c.encrypt_msg(content, timestamp, nonce)?;
234        println!("signature:{_signature}, cipher_msg:{_cipher_msg}");
235        Ok(())
236    }
237
238    #[test]
239    fn test_encrypt() -> Result<()> {
240        let token = "123456";
241        let aes_key = "4g5j64qlyl3zvetqxz5jiocdr586fn2zvjpa8zls3ij";
242        let corp_id = "suite4xxxxxxxxxxxxxxx221";
243
244        let c = DingTalkCrypto::new(token, corp_id, aes_key)?;
245        let x = c.encrypt("5p7b4jXMWUshiBCosucc111esssuite4xxxxxxxxxxxxxxx221");
246        assert_eq!("DwCJ9BMxu4r+VTe9TD+AWc1yfxqs7d2YgepFe4XruMgh1+6pTjCCq4nqAM6StRcafOx5a1OfwmX3ckSBifatlA==", base64::encode(&x));
247        Ok(())
248    }
249}