wx_rust_common/util/crypto/
wx_crypt_util.rs1use aes::Aes256;
11use cbc::cipher::block_padding::NoPadding;
12use cbc::cipher::{BlockModeDecrypt, BlockModeEncrypt, Iv, Key, KeyIvInit};
13
14use super::byte_group::ByteGroup;
15use super::pkcs7_encoder::Pkcs7Encoder;
16use super::sha1::Sha1;
17
18type Aes256Cbc = cbc::Encryptor<Aes256>;
20type Aes256CbcDec = cbc::Decryptor<Aes256>;
21
22#[derive(Debug, Clone)]
24pub struct EncryptContext {
25 pub encrypted_xml: String,
27 pub signature: String,
29 pub timestamp: String,
31 pub nonce: String,
33}
34
35#[derive(Debug, Clone)]
37pub struct WxCryptUtil {
38 aes_key: Vec<u8>,
40 token: String,
42 appid_or_corpid: String,
44}
45
46impl WxCryptUtil {
47 pub fn new(
57 token: impl Into<String>,
58 aes_key: impl Into<String>,
59 appid_or_corpid: impl Into<String>,
60 ) -> Result<Self, String> {
61 let token = token.into();
62 let aes_key_str = aes_key.into();
63 let aes_key =
68 lenient_base64_decode(&aes_key_str).map_err(|e| format!("aesKey 解码失败: {e}"))?;
69 if aes_key.len() != 32 {
70 return Err(format!("aesKey 解码后长度应为 32,实际 {}", aes_key.len()));
71 }
72 Ok(Self {
73 aes_key,
74 token,
75 appid_or_corpid: appid_or_corpid.into(),
76 })
77 }
78
79 pub fn encrypt(&self, plain_text: &str) -> Result<String, String> {
93 let ctx = self.encrypt_context(plain_text)?;
94 Ok(Self::generate_xml(
95 &ctx.encrypted_xml,
96 &ctx.signature,
97 &ctx.timestamp,
98 &ctx.nonce,
99 ))
100 }
101
102 pub fn encrypt_context(&self, plain_text: &str) -> Result<EncryptContext, String> {
110 let random_str = Self::gen_random_str();
112 let encrypted_xml = self.encrypt_with_random(&random_str, plain_text)?;
113
114 let timestamp = (std::time::SystemTime::now()
116 .duration_since(std::time::UNIX_EPOCH)
117 .map_err(|e| e.to_string())?
118 .as_secs())
119 .to_string();
120 let nonce = Self::gen_random_str();
121
122 let signature = Sha1::digest_with_amp(&[&self.token, ×tamp, &nonce, &encrypted_xml])?;
123 Ok(EncryptContext {
124 encrypted_xml,
125 signature,
126 timestamp,
127 nonce,
128 })
129 }
130
131 pub fn encrypt_with_random(
140 &self,
141 random_str: &str,
142 plain_text: &str,
143 ) -> Result<String, String> {
144 let mut collector = ByteGroup::new();
145 let random_bytes = random_str.as_bytes();
146 let plain_bytes = plain_text.as_bytes();
147 let size_bytes = Self::number_2_bytes_in_network_order(plain_bytes.len() as i32);
148 let appid_bytes = self.appid_or_corpid.as_bytes();
149
150 collector.add_bytes(random_bytes);
152 collector.add_bytes(&size_bytes);
153 collector.add_bytes(plain_bytes);
154 collector.add_bytes(appid_bytes);
155
156 let pad_bytes = Pkcs7Encoder::encode(collector.size());
158 collector.add_bytes(&pad_bytes);
159
160 let unencrypted = collector.to_bytes();
162
163 let mut key = Key::<Aes256Cbc>::default();
165 key.clone_from_slice(&self.aes_key);
166 let mut iv = Iv::<Aes256Cbc>::default();
167 iv.clone_from_slice(&self.aes_key[..16]);
168 let cipher = Aes256Cbc::new(&key, &iv);
169
170 let mut buf = vec![0u8; unencrypted.len()];
172 cipher
173 .encrypt_padded_b2b::<NoPadding>(&unencrypted, &mut buf)
174 .map_err(|e| format!("加密失败: {e}"))?;
175
176 Ok(base64::Engine::encode(
177 &base64::engine::general_purpose::STANDARD,
178 buf,
179 ))
180 }
181
182 pub fn decrypt_xml(
199 &self,
200 msg_signature: &str,
201 timestamp: &str,
202 nonce: &str,
203 encrypted_xml: &str,
204 ) -> Result<String, String> {
205 let cipher_text = Self::extract_encrypt_part(encrypted_xml)?;
207 self.decrypt_content(msg_signature, timestamp, nonce, &cipher_text)
208 }
209
210 pub fn decrypt_content(
212 &self,
213 msg_signature: &str,
214 timestamp: &str,
215 nonce: &str,
216 cipher_text: &str,
217 ) -> Result<String, String> {
218 let signature = Sha1::digest_with_amp(&[&self.token, timestamp, nonce, cipher_text])?;
220 if signature != msg_signature {
221 return Err("签名验证错误".to_string());
222 }
223
224 let encrypted =
226 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, cipher_text)
227 .map_err(|e| format!("base64 解码失败: {e}"))?;
228 let mut key = Key::<Aes256CbcDec>::default();
229 key.clone_from_slice(&self.aes_key);
230 let mut iv = Iv::<Aes256CbcDec>::default();
231 iv.clone_from_slice(&self.aes_key[..16]);
232 let cipher = Aes256CbcDec::new(&key, &iv);
233
234 let mut decrypted_buf = vec![0u8; encrypted.len()];
236 let decrypted_all = cipher
237 .decrypt_padded_b2b::<NoPadding>(&encrypted, &mut decrypted_buf)
238 .map_err(|e| format!("解密失败: {e}"))?
239 .to_vec();
240
241 let decrypted = Pkcs7Encoder::decode(&decrypted_all);
243
244 if decrypted.len() < 20 {
246 return Err("解密后数据长度非法".to_string());
247 }
248 let len_bytes: [u8; 4] = decrypted[16..20].try_into().unwrap();
249 let xml_len = Self::bytes_network_order_2_number(&len_bytes) as usize;
250 if 20 + xml_len > decrypted.len() {
251 return Err("解密后 xml 长度非法".to_string());
252 }
253 let xml = String::from_utf8_lossy(&decrypted[20..20 + xml_len]).into_owned();
254 let from_appid = String::from_utf8_lossy(&decrypted[20 + xml_len..]).into_owned();
255
256 if from_appid != self.appid_or_corpid {
258 return Err(format!(
259 "appid 校验失败:报文 appid={from_appid},本地 appid={}",
260 self.appid_or_corpid
261 ));
262 }
263 Ok(xml)
264 }
265
266 pub fn decrypt(&self, cipher_text: &str) -> Result<String, String> {
276 let encrypted =
277 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, cipher_text)
278 .map_err(|e| format!("base64 解码失败: {e}"))?;
279 let key = Key::<Aes256CbcDec>::default();
280 let mut key_copy = key;
281 key_copy.clone_from_slice(&self.aes_key);
282 let mut iv = Iv::<Aes256CbcDec>::default();
283 iv.clone_from_slice(&self.aes_key[..16]);
284 let cipher = Aes256CbcDec::new(&key_copy, &iv);
285
286 let mut decrypted_buf = vec![0u8; encrypted.len()];
287 let decrypted_all = cipher
288 .decrypt_padded_b2b::<NoPadding>(&encrypted, &mut decrypted_buf)
289 .map_err(|e| format!("解密失败: {e}"))?
290 .to_vec();
291
292 let decrypted = Pkcs7Encoder::decode(&decrypted_all);
294
295 if decrypted.len() < 20 {
297 return Err("解密后数据长度非法".to_string());
298 }
299 let len_bytes: [u8; 4] = decrypted[16..20].try_into().unwrap();
300 let xml_len = Self::bytes_network_order_2_number(&len_bytes) as usize;
301 if 20 + xml_len > decrypted.len() {
302 return Err("解密后 xml 长度非法".to_string());
303 }
304 let xml = String::from_utf8_lossy(&decrypted[20..20 + xml_len]).into_owned();
305 let from_appid = String::from_utf8_lossy(&decrypted[20 + xml_len..]).into_owned();
306
307 if from_appid != self.appid_or_corpid {
309 return Err(format!(
310 "appid 校验失败:报文 appid={from_appid},本地 appid={}",
311 self.appid_or_corpid
312 ));
313 }
314 Ok(xml)
315 }
316
317 fn extract_encrypt_part(xml: &str) -> Result<String, String> {
319 let start = xml.find("<Encrypt>").ok_or("xml 中未找到 <Encrypt>")? + "<Encrypt>".len();
321 let end = xml[start..]
322 .find("</Encrypt>")
323 .ok_or("xml 中未找到 </Encrypt>")?;
324 let content = &xml[start..start + end];
325 let content = content.trim();
327 if let Some(rest) = content.strip_prefix("<![CDATA[") {
328 if let Some(v) = rest.strip_suffix("]]>") {
329 return Ok(v.to_string());
330 }
331 }
332 Ok(content.to_string())
333 }
334
335 fn generate_xml(encrypt: &str, signature: &str, timestamp: &str, nonce: &str) -> String {
337 format!(
338 "<xml>\n<Encrypt><![CDATA[{encrypt}]]></Encrypt>\n<MsgSignature><![CDATA[{signature}]]></MsgSignature>\n<TimeStamp>{timestamp}</TimeStamp>\n<Nonce><![CDATA[{nonce}]]></Nonce>\n</xml>"
339 )
340 }
341
342 fn number_2_bytes_in_network_order(number: i32) -> [u8; 4] {
344 (number as u32).to_be_bytes()
345 }
346
347 fn bytes_network_order_2_number(bytes: &[u8]) -> i32 {
349 let mut arr = [0u8; 4];
350 arr.copy_from_slice(&bytes[..4]);
351 i32::from_be_bytes(arr)
352 }
353
354 pub fn gen_random_str() -> String {
356 const CHARS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
357 (0..16)
358 .map(|_| {
359 let idx = rand::random_range(0..CHARS.len());
360 CHARS[idx] as char
361 })
362 .collect()
363 }
364}
365
366fn lenient_base64_decode(input: &str) -> Result<Vec<u8>, String> {
371 let s = input.trim();
372 if let Ok(v) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s) {
374 return Ok(v);
375 }
376 if let Ok(v) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD_NO_PAD, s) {
378 return Ok(v);
379 }
380 let rem = s.len() % 4;
382 if rem == 1 {
383 let s2 = &s[..s.len() - 1];
385 return base64::Engine::decode(&base64::engine::general_purpose::STANDARD_NO_PAD, s2)
386 .map_err(|e| e.to_string());
387 }
388 if rem == 2 || rem == 3 {
389 if let Some(&last) = s.as_bytes().last() {
392 if let Some(idx) = base64_charset_index(last) {
393 let masked = idx & !0b11;
394 if let Some(ch) = base64_charset_char(masked) {
395 let mut s2 = s[..s.len() - 1].to_string();
396 s2.push(ch as char);
397 return base64::Engine::decode(
398 &base64::engine::general_purpose::STANDARD_NO_PAD,
399 &s2,
400 )
401 .map_err(|e| e.to_string());
402 }
403 }
404 }
405 }
406 Err("无法解码".to_string())
407}
408
409fn base64_charset_index(c: u8) -> Option<u8> {
411 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
412 CHARS.iter().position(|&x| x == c).map(|i| i as u8)
413}
414
415fn base64_charset_char(idx: u8) -> Option<u8> {
417 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
418 CHARS.get(idx as usize).copied()
419}