1use aes::{
22 self,
23 cipher::{
24 block_padding::{NoPadding, Pkcs7},
25 BlockDecryptMut, BlockEncryptMut, KeyIvInit,
26 },
27 Aes256,
28};
29use base64::{alphabet, engine, Engine as _};
30use cbc::{Decryptor, Encryptor};
31use sha1::{Digest, Sha1};
32use thiserror::Error;
33
34#[derive(Error, Debug)]
36pub enum CryptoError {
37 #[error("Wecom decode error. {0}")]
38 WecomDecode(String),
39 #[error("Base64 decode error. {0}")]
40 Base64Decode(#[from] base64::DecodeError),
41 #[error("UTF-8 decode error. {0}")]
42 Utf8Decode(#[from] std::string::FromUtf8Error),
43 #[error("AES decryption error. {0}")]
44 AesDecryption(String),
45}
46
47pub fn generate_sha1_signature(inputs: &[&str]) -> String {
49 let mut content = inputs.to_vec();
50 content.sort_unstable();
51 let digest = Sha1::digest(content.concat().as_bytes());
52 base16ct::lower::encode_string(&digest)
53}
54
55#[derive(PartialEq, Debug)]
57pub struct Source {
58 pub text: String,
60 pub receive_id: String,
62}
63
64const AES_BLOCK_SIZE: usize = 32;
66
67#[derive(Clone)]
69pub struct Agent {
70 token: String,
71 key: [u8; AES_BLOCK_SIZE],
72 nonce: [u8; 16],
73}
74
75impl Agent {
76 pub fn new(token: &str, key: &str) -> Self {
78 let config = engine::GeneralPurposeConfig::new()
81 .with_encode_padding(false)
82 .with_decode_allow_trailing_bits(true)
83 .with_decode_padding_mode(engine::DecodePaddingMode::Indifferent);
84 let key_as_vec = engine::GeneralPurpose::new(&alphabet::STANDARD, config)
85 .decode(key)
86 .expect("AES key should be valid Base64 string");
87 let key =
88 <[u8; AES_BLOCK_SIZE]>::try_from(key_as_vec).expect("AES key length should be 32");
89 let nonce = <[u8; 16]>::try_from(&key[..16]).unwrap();
90 Self {
91 token: token.to_owned(),
92 key,
93 nonce,
94 }
95 }
96
97 pub fn generate_signature(&self, inputs: &[&str]) -> String {
109 let mut content = inputs.to_vec();
110 content.push(&self.token);
111 generate_sha1_signature(&content)
112 }
113
114 pub fn encrypt(&self, input: &Source) -> String {
116 let mut block: Vec<u8> = Vec::new();
118
119 block.extend(rand::random::<[u8; 16]>());
121
122 block.extend((input.text.len() as u32).to_be_bytes());
124
125 block.extend(input.text.as_bytes());
127
128 block.extend(input.receive_id.as_bytes());
130
131 let cipher_bytes = Encryptor::<Aes256>::new(&self.key.into(), &self.nonce.into())
133 .encrypt_padded_vec_mut::<Pkcs7>(&block);
134 engine::general_purpose::STANDARD.encode(cipher_bytes)
135 }
136
137 pub fn decrypt(&self, encoded: &str) -> Result<Source, CryptoError> {
139 let cipher_bytes = engine::general_purpose::STANDARD.decode(encoded)?;
141 let block = Decryptor::<Aes256>::new(&self.key.into(), &self.nonce.into())
143 .decrypt_padded_vec_mut::<NoPadding>(&cipher_bytes)
144 .map_err(|e| CryptoError::AesDecryption(e.to_string()))?;
145 let Some(padding_size) = block.last().copied() else {
147 return Err(CryptoError::WecomDecode(
148 "Failed to get padding size, empty block".to_string(),
149 ));
150 };
151 let Ok(msg_len_bytes): Result<[u8; 4], _> = block[16..20].try_into() else {
152 return Err(CryptoError::WecomDecode("Invalid message size".to_string()));
153 };
154 let msg_len = u32::from_be_bytes(msg_len_bytes) as usize;
156 let text = String::from_utf8(block[20..20 + msg_len].to_vec())?;
157 let receive_id =
158 String::from_utf8(block[20 + msg_len..block.len() - padding_size as usize].to_vec())?;
159 Ok(Source { text, receive_id })
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 #[test]
167 fn test_signature() {
168 let token = "a";
169 let key = "cGCVnNJRgRu6wDgo7gxG2diBovGnRQq1Tqy4Rm4V4qF";
170 let agent = Agent::new(token, key);
171 assert_eq!(
172 agent.generate_signature(&["0", "c", "b"]),
173 "a8addbc99f8b3f51d2adbceb605d650b9a8940e2",
174 );
175 }
176
177 #[test]
178 fn test_mod_signature() {
179 let token = "a";
180 assert_eq!(
181 super::generate_sha1_signature(&[token, "0", "c", "b"]),
182 "a8addbc99f8b3f51d2adbceb605d650b9a8940e2",
183 );
184 }
185
186 #[test]
187 fn test_encrypt_decrypt() {
188 let token = "a";
189 let key = "cGCVnNJRgRu6wDgo7gxG2diBovGnRQq1Tqy4Rm4V4qF";
190 let agent = Agent::new(token, key);
191 let source = Source {
192 text: "abcd".to_string(),
193 receive_id: "xyz".to_string(),
194 };
195 let enc = agent.encrypt(&source);
196 let dec = agent.decrypt(enc.as_str()).unwrap();
197 assert_eq!(source, dec);
198 }
199}