dingtalk_rs/client/event_subscribe/
callback_crypto.rs1use 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 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, ×tamp, &nonce);
56
57 Ok((cipher_msg, signature))
58 }
59
60 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 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.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
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 = "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}