Skip to main content

futu_net/
encrypt.rs

1// AES-128 ECB 加解密
2//
3// FutuOpenD 协议使用 AES-128 ECB 模式加密 body:
4// - InitConnect 成功后,服务端返回 16 字节 AES key(hex 字符串)
5// - 后续所有请求/响应的 body 使用此 key 加解密
6// - SHA1 校验基于明文(加密前计算、解密后验证)
7//
8// 注意:AES-ECB 模式不使用 IV,相同明文始终产生相同密文。
9// 这是 FutuOpenD 协议的设计,Rust 侧需保持兼容。
10
11use futu_core::error::FutuError;
12
13/// AES-128 ECB 加密
14///
15/// key 必须是 16 字节。data 会被 PKCS7 填充到 16 字节的倍数。
16pub fn aes_ecb_encrypt(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
17    use std::iter;
18
19    // PKCS7 padding
20    let block_size = 16;
21    let padding_len = block_size - (data.len() % block_size);
22    let mut padded: Vec<u8> = data.to_vec();
23    padded.extend(iter::repeat_n(padding_len as u8, padding_len));
24
25    // AES-128 ECB: 逐块加密
26    let mut output = vec![0u8; padded.len()];
27    for (i, chunk) in padded.chunks(block_size).enumerate() {
28        let block = aes_encrypt_block(key, chunk);
29        output[i * block_size..(i + 1) * block_size].copy_from_slice(&block);
30    }
31
32    output
33}
34
35/// AES-128 ECB 解密(PKCS7 padding)
36///
37/// key 必须是 16 字节。返回去除 PKCS7 padding 后的明文。
38pub fn aes_ecb_decrypt(
39    key: &[u8; 16],
40    data: &[u8],
41) -> Result<Vec<u8>, futu_core::error::FutuError> {
42    if data.is_empty() || !data.len().is_multiple_of(16) {
43        return Err(futu_core::error::FutuError::Encryption(
44            "ciphertext length must be a multiple of 16".into(),
45        ));
46    }
47
48    let block_size = 16;
49    let mut output = vec![0u8; data.len()];
50    for (i, chunk) in data.chunks(block_size).enumerate() {
51        let block = aes_decrypt_block(key, chunk);
52        output[i * block_size..(i + 1) * block_size].copy_from_slice(&block);
53    }
54
55    // Remove PKCS7 padding
56    let padding_len = output
57        .last()
58        .copied()
59        .ok_or_else(|| futu_core::error::FutuError::Encryption("empty decrypted body".into()))?
60        as usize;
61    if padding_len == 0 || padding_len > block_size {
62        return Err(futu_core::error::FutuError::Encryption(
63            "invalid PKCS7 padding".into(),
64        ));
65    }
66    if output[output.len() - padding_len..]
67        .iter()
68        .any(|&b| b as usize != padding_len)
69    {
70        return Err(futu_core::error::FutuError::Encryption(
71            "invalid PKCS7 padding bytes".into(),
72        ));
73    }
74    output.truncate(output.len() - padding_len);
75
76    Ok(output)
77}
78
79// ===== AES-128 核心实现 =====
80// 使用 aes crate(硬件加速)
81
82/// 加密单个 16 字节块(使用 aes crate)—— AES-128
83fn aes_encrypt_block(key: &[u8; 16], block: &[u8]) -> [u8; 16] {
84    use aes::cipher::{BlockEncrypt, KeyInit};
85    let encryptor = aes::Aes128::new(key.into());
86    let mut out = [0u8; 16];
87    out.copy_from_slice(block);
88    let ga = aes::cipher::generic_array::GenericArray::from_mut_slice(&mut out);
89    encryptor.encrypt_block(ga);
90    out
91}
92
93/// 解密单个 16 字节块(AES-128)
94fn aes_decrypt_block(key: &[u8; 16], block: &[u8]) -> [u8; 16] {
95    use aes::cipher::{BlockDecrypt, KeyInit};
96    let decryptor = aes::Aes128::new(key.into());
97    let mut out = [0u8; 16];
98    out.copy_from_slice(block);
99    let ga = aes::cipher::generic_array::GenericArray::from_mut_slice(&mut out);
100    decryptor.decrypt_block(ga);
101    out
102}
103
104/// AES 加密单块 —— 支持 16 / 24 / 32 字节 key(对应 AES-128/192/256)。
105/// 对齐 C++ `OMCrypt_FTAES_MD5_Encrypt` 的可变 key 长度支持(auth_cryptor.cpp 里
106/// S3 是 32 字节,走 AES-256)。
107fn aes_encrypt_block_var(key: &[u8], block: &[u8]) -> Result<[u8; 16], FutuError> {
108    use aes::cipher::{BlockEncrypt, KeyInit};
109    let mut out = [0u8; 16];
110    out.copy_from_slice(block);
111    let ga = aes::cipher::generic_array::GenericArray::from_mut_slice(&mut out);
112    match key.len() {
113        16 => {
114            aes::Aes128::new(aes::cipher::generic_array::GenericArray::from_slice(key))
115                .encrypt_block(ga);
116        }
117        24 => {
118            aes::Aes192::new(aes::cipher::generic_array::GenericArray::from_slice(key))
119                .encrypt_block(ga);
120        }
121        32 => {
122            aes::Aes256::new(aes::cipher::generic_array::GenericArray::from_slice(key))
123                .encrypt_block(ga);
124        }
125        _ => {
126            return Err(unsupported_aes_var_key_len(
127                "aes_encrypt_block_var",
128                key.len(),
129            ));
130        }
131    }
132    Ok(out)
133}
134
135/// AES 解密单块 —— 可变 key 长度(16/24/32 字节)
136fn aes_decrypt_block_var(key: &[u8], block: &[u8]) -> Result<[u8; 16], FutuError> {
137    use aes::cipher::{BlockDecrypt, KeyInit};
138    let mut out = [0u8; 16];
139    out.copy_from_slice(block);
140    let ga = aes::cipher::generic_array::GenericArray::from_mut_slice(&mut out);
141    match key.len() {
142        16 => {
143            aes::Aes128::new(aes::cipher::generic_array::GenericArray::from_slice(key))
144                .decrypt_block(ga);
145        }
146        24 => {
147            aes::Aes192::new(aes::cipher::generic_array::GenericArray::from_slice(key))
148                .decrypt_block(ga);
149        }
150        32 => {
151            aes::Aes256::new(aes::cipher::generic_array::GenericArray::from_slice(key))
152                .decrypt_block(ga);
153        }
154        _ => {
155            return Err(unsupported_aes_var_key_len(
156                "aes_decrypt_block_var",
157                key.len(),
158            ));
159        }
160    }
161    Ok(out)
162}
163
164fn unsupported_aes_var_key_len(context: &str, len: usize) -> FutuError {
165    FutuError::Encryption(format!("{context}: unsupported key length {len}"))
166}
167
168// ===== AES-128 CBC + MD5 校验 (自定义格式) =====
169// 用于 tgtgt 加密和内部通信的 OMCrypt_AES_Encrypt/Decrypt
170// 格式: [CBC加密的数据块] + [padding块(标记最后块大小)] + [MD5(明文所有16字节块)]
171
172/// AES-128 CBC + MD5 加密 (对应 C++ om_aes_encrypt_cbc_md5)
173pub fn aes_cbc_md5_encrypt(key: &[u8; 16], data: &[u8]) -> Result<Vec<u8>, FutuError> {
174    aes_cbc_md5_encrypt_var(key, data)
175}
176
177/// AES-128/192/256 CBC + MD5 加密(可变 key 长度版,对齐 C++
178/// `OMCrypt_FTAES_MD5_Encrypt` 接受任意 16/24/32 字节 key)
179///
180/// 用途:登录流程的 `CreateNewTgtgt` 在 salt32 存在时用 S3(32 字节)作 key
181/// 做 AES-256 加密 tgtgt。
182pub fn aes_cbc_md5_encrypt_var(key: &[u8], data: &[u8]) -> Result<Vec<u8>, FutuError> {
183    let input_len = data.len();
184    let modv = input_len % 16;
185    let last_block_size = if modv == 0 { 0u8 } else { modv as u8 };
186
187    let data_blocks = if modv == 0 {
188        input_len
189    } else {
190        input_len - modv + 16
191    };
192    let total_size = data_blocks + 16 + 16;
193    let mut output = vec![0u8; total_size];
194
195    let mut md5_ctx = md5::Context::new();
196    let mut encrypt_pos = 0usize;
197    let aligned_end = input_len - modv;
198
199    while encrypt_pos < aligned_end {
200        let block = &data[encrypt_pos..encrypt_pos + 16];
201        md5_ctx.consume(block);
202        if encrypt_pos == 0 {
203            let enc = aes_encrypt_block_var(key, block)?;
204            output[..16].copy_from_slice(&enc);
205        } else {
206            let mut xor_block = [0u8; 16];
207            for i in 0..16 {
208                xor_block[i] = output[encrypt_pos - 16 + i] ^ block[i];
209            }
210            let enc = aes_encrypt_block_var(key, &xor_block)?;
211            output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&enc);
212        }
213        encrypt_pos += 16;
214    }
215
216    if modv != 0 {
217        let mut tmp = [0u8; 16];
218        tmp[..last_block_size as usize].copy_from_slice(&data[encrypt_pos..encrypt_pos + modv]);
219        md5_ctx.consume(tmp);
220        if encrypt_pos > 0 {
221            for i in 0..16 {
222                tmp[i] ^= output[encrypt_pos - 16 + i];
223            }
224        }
225        let enc = aes_encrypt_block_var(key, &tmp)?;
226        output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&enc);
227        encrypt_pos += 16;
228    }
229
230    let mut padding = [0u8; 16];
231    padding[15] = last_block_size;
232    let enc_pad = aes_encrypt_block_var(key, &padding)?;
233    output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&enc_pad);
234    encrypt_pos += 16;
235
236    let md5_hash = md5_ctx.compute();
237    output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&md5_hash.0);
238
239    Ok(output)
240}
241
242/// AES-128/192/256 CBC + MD5 解密(可变 key 长度版)
243pub fn aes_cbc_md5_decrypt_var(
244    key: &[u8],
245    data: &[u8],
246) -> std::result::Result<Vec<u8>, futu_core::error::FutuError> {
247    let input_len = data.len();
248    if input_len < 32 || !input_len.is_multiple_of(16) {
249        return Err(futu_core::error::FutuError::Encryption(format!(
250            "cbc_md5_var: invalid ciphertext length {input_len}"
251        )));
252    }
253
254    let padding_block = aes_decrypt_block_var(key, &data[input_len - 32..input_len - 16])?;
255    let last_block_size = padding_block[15] as usize;
256    if last_block_size > 15 {
257        return Err(futu_core::error::FutuError::Encryption(format!(
258            "cbc_md5_var: last_block_size {last_block_size} > 15"
259        )));
260    }
261
262    let data_blocks_end = input_len - 32;
263    let plaintext_len = if last_block_size == 0 {
264        data_blocks_end
265    } else {
266        data_blocks_end - 16 + last_block_size
267    };
268
269    let mut output = vec![0u8; plaintext_len];
270    let mut md5_ctx = md5::Context::new();
271    let mut pos = 0usize;
272    while pos < data_blocks_end {
273        let block = &data[pos..pos + 16];
274        let decrypted = aes_decrypt_block_var(key, block)?;
275        let mut plain_block = [0u8; 16];
276        if pos == 0 {
277            plain_block.copy_from_slice(&decrypted);
278        } else {
279            for i in 0..16 {
280                plain_block[i] = decrypted[i] ^ data[pos - 16 + i];
281            }
282        }
283        md5_ctx.consume(plain_block);
284
285        let is_last_block = pos + 16 == data_blocks_end;
286        let effective_len = if is_last_block && last_block_size != 0 {
287            last_block_size
288        } else {
289            16
290        };
291        output[pos..pos + effective_len].copy_from_slice(&plain_block[..effective_len]);
292        pos += 16;
293    }
294
295    let computed_md5 = md5_ctx.compute();
296    if computed_md5.0 != data[input_len - 16..] {
297        return Err(futu_core::error::FutuError::Encryption(
298            "cbc_md5_var: MD5 checksum mismatch".into(),
299        ));
300    }
301
302    Ok(output)
303}
304
305/// AES-128 CBC + MD5 解密 (对应 C++ om_aes_decrypt_cbc_md5)
306pub fn aes_cbc_md5_decrypt(
307    key: &[u8; 16],
308    data: &[u8],
309) -> std::result::Result<Vec<u8>, futu_core::error::FutuError> {
310    aes_cbc_md5_decrypt_var(key, data)
311}
312
313// ===== RSA 加解密 =====
314// FutuOpenD 使用 RSA PKCS#1 v1.5 加解密。
315// 客户端和服务端共享同一个 RSA 私钥文件(PEM 格式)。
316// 从私钥中提取公钥用于加密,私钥用于解密。
317
318/// 从 PEM 格式加载 RSA 私钥(支持 PKCS#8 和 PKCS#1 格式)
319fn load_rsa_private_key(
320    pem_private_key: &str,
321) -> Result<rsa::RsaPrivateKey, futu_core::error::FutuError> {
322    use rsa::pkcs8::DecodePrivateKey;
323
324    rsa::RsaPrivateKey::from_pkcs8_pem(pem_private_key)
325        .or_else(|_| {
326            use rsa::pkcs1::DecodeRsaPrivateKey;
327            rsa::RsaPrivateKey::from_pkcs1_pem(pem_private_key)
328        })
329        .map_err(|e| {
330            futu_core::error::FutuError::Encryption(format!("invalid RSA private key: {e}"))
331        })
332}
333
334/// 使用 RSA 公钥加密(从私钥中提取公钥)
335///
336/// `pem_private_key`: PEM 格式的 RSA 私钥
337pub fn rsa_public_encrypt(
338    pem_private_key: &str,
339    data: &[u8],
340) -> Result<Vec<u8>, futu_core::error::FutuError> {
341    use rsa::Pkcs1v15Encrypt;
342
343    let private_key = load_rsa_private_key(pem_private_key)?;
344    let public_key = rsa::RsaPublicKey::from(&private_key);
345    let mut rng = rand::thread_rng();
346
347    public_key
348        .encrypt(&mut rng, Pkcs1v15Encrypt, data)
349        .map_err(|e| futu_core::error::FutuError::Encryption(format!("RSA encrypt failed: {e}")))
350}
351
352/// 使用 RSA 私钥解密
353pub fn rsa_private_decrypt(
354    pem_private_key: &str,
355    data: &[u8],
356) -> Result<Vec<u8>, futu_core::error::FutuError> {
357    use rsa::Pkcs1v15Encrypt;
358
359    let private_key = load_rsa_private_key(pem_private_key)?;
360
361    private_key
362        .decrypt(Pkcs1v15Encrypt, data)
363        .map_err(|e| futu_core::error::FutuError::Encryption(format!("RSA decrypt failed: {e}")))
364}
365
366/// 使用 RSA 公钥分块加密(支持任意长度数据)
367///
368/// RSA 1024-bit: 每块最多 117 字节明文 → 128 字节密文
369/// RSA 2048-bit: 每块最多 245 字节明文 → 256 字节密文
370/// 对应 C++ FutuOpenD 的 RSA 分块加密逻辑
371pub fn rsa_public_encrypt_blocks(
372    pem_private_key: &str,
373    data: &[u8],
374) -> Result<Vec<u8>, futu_core::error::FutuError> {
375    use rsa::Pkcs1v15Encrypt;
376    use rsa::traits::PublicKeyParts;
377
378    let private_key = load_rsa_private_key(pem_private_key)?;
379    let public_key = rsa::RsaPublicKey::from(&private_key);
380
381    // PKCS1v15 padding 需要 11 字节开销
382    let key_len = public_key.size();
383    let max_block = key_len - 11;
384
385    let mut result = Vec::with_capacity((data.len() / max_block + 1) * key_len);
386    let mut rng = rand::thread_rng();
387
388    for chunk in data.chunks(max_block) {
389        let encrypted = public_key
390            .encrypt(&mut rng, Pkcs1v15Encrypt, chunk)
391            .map_err(|e| {
392                futu_core::error::FutuError::Encryption(format!("RSA block encrypt failed: {e}"))
393            })?;
394        result.extend_from_slice(&encrypted);
395    }
396
397    Ok(result)
398}
399
400/// 使用 RSA 私钥分块解密(支持任意长度数据)
401///
402/// 密文按 key_size(128 或 256 字节)分块解密
403pub fn rsa_private_decrypt_blocks(
404    pem_private_key: &str,
405    data: &[u8],
406) -> Result<Vec<u8>, futu_core::error::FutuError> {
407    use rsa::Pkcs1v15Encrypt;
408    use rsa::traits::PublicKeyParts;
409
410    let private_key = load_rsa_private_key(pem_private_key)?;
411    let key_len = private_key.size();
412
413    if !data.len().is_multiple_of(key_len) {
414        return Err(futu_core::error::FutuError::Encryption(format!(
415            "RSA ciphertext length {} is not a multiple of key size {}",
416            data.len(),
417            key_len
418        )));
419    }
420
421    let mut result = Vec::with_capacity(data.len());
422
423    for chunk in data.chunks(key_len) {
424        let decrypted = private_key.decrypt(Pkcs1v15Encrypt, chunk).map_err(|e| {
425            futu_core::error::FutuError::Encryption(format!("RSA block decrypt failed: {e}"))
426        })?;
427        result.extend_from_slice(&decrypted);
428    }
429
430    Ok(result)
431}
432
433#[cfg(test)]
434mod tests;