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
11/// AES-128 ECB 加密
12///
13/// key 必须是 16 字节。data 会被 PKCS7 填充到 16 字节的倍数。
14pub fn aes_ecb_encrypt(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
15    use std::iter;
16
17    // PKCS7 padding
18    let block_size = 16;
19    let padding_len = block_size - (data.len() % block_size);
20    let mut padded: Vec<u8> = data.to_vec();
21    padded.extend(iter::repeat_n(padding_len as u8, padding_len));
22
23    // AES-128 ECB: 逐块加密
24    let mut output = vec![0u8; padded.len()];
25    for (i, chunk) in padded.chunks(block_size).enumerate() {
26        let block = aes_encrypt_block(key, chunk);
27        output[i * block_size..(i + 1) * block_size].copy_from_slice(&block);
28    }
29
30    output
31}
32
33/// AES-128 ECB 加密(自定义 size block 格式,用于 TCP 登录 ReqEncryptData)
34///
35/// 输出格式: [encrypted_blocks] + [unencrypted size_block(16B, last byte=remainder)]
36/// 对应成功项目的 aes_128_ecb_encrypt
37pub fn aes_ecb_encrypt_with_tail(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
38    let block_size = 16;
39    let remainder = (data.len() % block_size) as u8;
40    let full_blocks = data.len() / block_size;
41
42    let aligned_len = if remainder == 0 {
43        data.len()
44    } else {
45        data.len() - remainder as usize + block_size
46    };
47
48    let mut output = Vec::with_capacity(aligned_len + block_size);
49
50    // 加密完整块
51    for i in 0..full_blocks {
52        let block = aes_encrypt_block(key, &data[i * block_size..(i + 1) * block_size]);
53        output.extend_from_slice(&block);
54    }
55
56    // 不完整的最后块(补零后加密)
57    if remainder != 0 {
58        let mut last_block = [0u8; 16];
59        last_block[..remainder as usize].copy_from_slice(&data[full_blocks * block_size..]);
60        let encrypted = aes_encrypt_block(key, &last_block);
61        output.extend_from_slice(&encrypted);
62    }
63
64    // 未加密的 size block:[0,0,...,0, remainder]
65    let mut tail = [0u8; 16];
66    tail[15] = remainder;
67    output.extend_from_slice(&tail);
68
69    output
70}
71
72/// AES-128 ECB 解密(自定义 size block 格式)
73pub fn aes_ecb_decrypt_with_tail(
74    key: &[u8; 16],
75    data: &[u8],
76) -> Result<Vec<u8>, futu_core::error::FutuError> {
77    if data.len() < 32 || !data.len().is_multiple_of(16) {
78        return Err(futu_core::error::FutuError::Encryption(
79            "ecb_tail: invalid length".into(),
80        ));
81    }
82
83    // 最后 16 字节是未加密的 size block
84    let tail = &data[data.len() - 16..];
85    let remainder = tail[15] as usize;
86    let encrypted_data = &data[..data.len() - 16];
87
88    let block_size = 16;
89    let mut output = Vec::with_capacity(encrypted_data.len());
90
91    for chunk in encrypted_data.chunks(block_size) {
92        let decrypted = aes_decrypt_block(key, chunk);
93        output.extend_from_slice(&decrypted);
94    }
95
96    // 去掉最后块的零填充
97    if remainder != 0 && !output.is_empty() {
98        let final_len = output.len() - block_size + remainder;
99        output.truncate(final_len);
100    }
101
102    Ok(output)
103}
104
105/// AES-128 ECB 解密(PKCS7 padding)
106///
107/// key 必须是 16 字节。返回去除 PKCS7 padding 后的明文。
108pub fn aes_ecb_decrypt(
109    key: &[u8; 16],
110    data: &[u8],
111) -> Result<Vec<u8>, futu_core::error::FutuError> {
112    if data.is_empty() || !data.len().is_multiple_of(16) {
113        return Err(futu_core::error::FutuError::Encryption(
114            "ciphertext length must be a multiple of 16".into(),
115        ));
116    }
117
118    let block_size = 16;
119    let mut output = vec![0u8; data.len()];
120    for (i, chunk) in data.chunks(block_size).enumerate() {
121        let block = aes_decrypt_block(key, chunk);
122        output[i * block_size..(i + 1) * block_size].copy_from_slice(&block);
123    }
124
125    // Remove PKCS7 padding
126    let padding_len = *output.last().unwrap() as usize;
127    if padding_len == 0 || padding_len > block_size {
128        return Err(futu_core::error::FutuError::Encryption(
129            "invalid PKCS7 padding".into(),
130        ));
131    }
132    if output[output.len() - padding_len..]
133        .iter()
134        .any(|&b| b as usize != padding_len)
135    {
136        return Err(futu_core::error::FutuError::Encryption(
137            "invalid PKCS7 padding bytes".into(),
138        ));
139    }
140    output.truncate(output.len() - padding_len);
141
142    Ok(output)
143}
144
145// ===== AES-128 核心实现 =====
146// 使用 aes crate(硬件加速)
147
148/// 加密单个 16 字节块(使用 aes crate)
149fn aes_encrypt_block(key: &[u8; 16], block: &[u8]) -> [u8; 16] {
150    use aes::cipher::{BlockEncrypt, KeyInit};
151    let encryptor = aes::Aes128::new(key.into());
152    let mut out = [0u8; 16];
153    out.copy_from_slice(block);
154    let ga = aes::cipher::generic_array::GenericArray::from_mut_slice(&mut out);
155    encryptor.encrypt_block(ga);
156    out
157}
158
159/// 解密单个 16 字节块(使用 aes crate)
160fn aes_decrypt_block(key: &[u8; 16], block: &[u8]) -> [u8; 16] {
161    use aes::cipher::{BlockDecrypt, KeyInit};
162    let decryptor = aes::Aes128::new(key.into());
163    let mut out = [0u8; 16];
164    out.copy_from_slice(block);
165    let ga = aes::cipher::generic_array::GenericArray::from_mut_slice(&mut out);
166    decryptor.decrypt_block(ga);
167    out
168}
169
170// ===== AES-128 CBC + MD5 校验 (自定义格式) =====
171// 用于 tgtgt 加密和内部通信的 OMCrypt_AES_Encrypt/Decrypt
172// 格式: [CBC加密的数据块] + [padding块(标记最后块大小)] + [MD5(明文所有16字节块)]
173
174/// AES-128 CBC + MD5 加密 (对应 C++ om_aes_encrypt_cbc_md5)
175pub fn aes_cbc_md5_encrypt(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
176    let input_len = data.len();
177    let modv = input_len % 16;
178    let last_block_size = if modv == 0 { 0u8 } else { modv as u8 };
179
180    // 计算输出大小
181    let data_blocks = if modv == 0 {
182        input_len
183    } else {
184        input_len - modv + 16
185    };
186    let total_size = data_blocks + 16 + 16; // +padding块 +MD5
187    let mut output = vec![0u8; total_size];
188
189    // MD5 计算(对所有16字节块)
190    let mut md5_ctx = md5::Context::new();
191
192    let mut encrypt_pos = 0usize;
193    let aligned_end = input_len - modv;
194
195    // 加密完整块
196    while encrypt_pos < aligned_end {
197        let block = &data[encrypt_pos..encrypt_pos + 16];
198        md5_ctx.consume(block);
199
200        if encrypt_pos == 0 {
201            let encrypted = aes_encrypt_block(key, block);
202            output[..16].copy_from_slice(&encrypted);
203        } else {
204            let mut xor_block = [0u8; 16];
205            for i in 0..16 {
206                xor_block[i] = output[encrypt_pos - 16 + i] ^ block[i];
207            }
208            let encrypted = aes_encrypt_block(key, &xor_block);
209            output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&encrypted);
210        }
211        encrypt_pos += 16;
212    }
213
214    // 处理不完整的最后块
215    if modv != 0 {
216        let mut tmp = [0u8; 16];
217        tmp[..last_block_size as usize].copy_from_slice(&data[encrypt_pos..encrypt_pos + modv]);
218        md5_ctx.consume(tmp);
219
220        if encrypt_pos > 0 {
221            for i in 0..16 {
222                tmp[i] ^= output[encrypt_pos - 16 + i];
223            }
224        }
225        let encrypted = aes_encrypt_block(key, &tmp);
226        output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&encrypted);
227        encrypt_pos += 16;
228    }
229
230    // Padding 块:tmp[15] = last_block_size
231    let mut padding = [0u8; 16];
232    padding[15] = last_block_size;
233    let encrypted_padding = aes_encrypt_block(key, &padding);
234    output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&encrypted_padding);
235    encrypt_pos += 16;
236
237    // MD5 校验和
238    let md5_hash = md5_ctx.compute();
239    output[encrypt_pos..encrypt_pos + 16].copy_from_slice(&md5_hash.0);
240
241    output
242}
243
244/// AES-128 CBC + MD5 解密 (对应 C++ om_aes_decrypt_cbc_md5)
245pub fn aes_cbc_md5_decrypt(
246    key: &[u8; 16],
247    data: &[u8],
248) -> std::result::Result<Vec<u8>, futu_core::error::FutuError> {
249    let input_len = data.len();
250    if input_len < 32 || !input_len.is_multiple_of(16) {
251        return Err(futu_core::error::FutuError::Encryption(format!(
252            "cbc_md5: invalid ciphertext length {input_len} (need >= 32 and multiple of 16)"
253        )));
254    }
255
256    // 解密倒数第二个块获取 last_block_size
257    let padding_block = aes_decrypt_block(key, &data[input_len - 32..input_len - 16]);
258    let last_block_size = padding_block[15] as usize;
259    if last_block_size > 15 {
260        return Err(futu_core::error::FutuError::Encryption(
261            "cbc_md5: invalid last_block_size".into(),
262        ));
263    }
264
265    let encrypt_len = input_len - 32; // 去掉 padding块 + MD5
266    let mut output = vec![0u8; encrypt_len];
267    let mut md5_ctx = md5::Context::new();
268
269    let mut decrypt_pos = 0usize;
270    while encrypt_len >= 16 && decrypt_pos < encrypt_len - 16 {
271        let decrypted = aes_decrypt_block(key, &data[decrypt_pos..decrypt_pos + 16]);
272        if decrypt_pos > 0 {
273            for i in 0..16 {
274                output[decrypt_pos + i] = data[decrypt_pos - 16 + i] ^ decrypted[i];
275            }
276        } else {
277            output[..16].copy_from_slice(&decrypted);
278        }
279        md5_ctx.consume(&output[decrypt_pos..decrypt_pos + 16]);
280        decrypt_pos += 16;
281    }
282
283    // 最后一块
284    if last_block_size != 0 {
285        let decrypted = aes_decrypt_block(key, &data[decrypt_pos..decrypt_pos + 16]);
286        let mut tmp = [0u8; 16];
287        if decrypt_pos > 0 {
288            for i in 0..16 {
289                tmp[i] = data[decrypt_pos - 16 + i] ^ decrypted[i];
290            }
291        } else {
292            tmp = decrypted;
293        }
294        output.truncate(decrypt_pos + last_block_size);
295        output[decrypt_pos..decrypt_pos + last_block_size].copy_from_slice(&tmp[..last_block_size]);
296        md5_ctx.consume(tmp);
297    } else {
298        let decrypted = aes_decrypt_block(key, &data[decrypt_pos..decrypt_pos + 16]);
299        if decrypt_pos > 0 {
300            for i in 0..16 {
301                output[decrypt_pos + i] = data[decrypt_pos - 16 + i] ^ decrypted[i];
302            }
303        } else {
304            output[decrypt_pos..decrypt_pos + 16].copy_from_slice(&decrypted);
305        }
306        md5_ctx.consume(&output[decrypt_pos..decrypt_pos + 16]);
307    }
308
309    // 验证 MD5
310    let computed_md5 = md5_ctx.compute();
311    if computed_md5.0 != data[input_len - 16..] {
312        return Err(futu_core::error::FutuError::Encryption(
313            "cbc_md5: MD5 checksum mismatch".into(),
314        ));
315    }
316
317    Ok(output)
318}
319
320// ===== RSA 加解密 =====
321// FutuOpenD 使用 RSA PKCS#1 v1.5 加解密。
322// 客户端和服务端共享同一个 RSA 私钥文件(PEM 格式)。
323// 从私钥中提取公钥用于加密,私钥用于解密。
324
325/// 从 PEM 格式加载 RSA 私钥(支持 PKCS#8 和 PKCS#1 格式)
326fn load_rsa_private_key(
327    pem_private_key: &str,
328) -> Result<rsa::RsaPrivateKey, futu_core::error::FutuError> {
329    use rsa::pkcs8::DecodePrivateKey;
330
331    rsa::RsaPrivateKey::from_pkcs8_pem(pem_private_key)
332        .or_else(|_| {
333            use rsa::pkcs1::DecodeRsaPrivateKey;
334            rsa::RsaPrivateKey::from_pkcs1_pem(pem_private_key)
335        })
336        .map_err(|e| {
337            futu_core::error::FutuError::Encryption(format!("invalid RSA private key: {e}"))
338        })
339}
340
341/// 使用 RSA 公钥加密(从私钥中提取公钥)
342///
343/// `pem_private_key`: PEM 格式的 RSA 私钥
344pub fn rsa_public_encrypt(
345    pem_private_key: &str,
346    data: &[u8],
347) -> Result<Vec<u8>, futu_core::error::FutuError> {
348    use rsa::Pkcs1v15Encrypt;
349
350    let private_key = load_rsa_private_key(pem_private_key)?;
351    let public_key = rsa::RsaPublicKey::from(&private_key);
352    let mut rng = rand::thread_rng();
353
354    public_key
355        .encrypt(&mut rng, Pkcs1v15Encrypt, data)
356        .map_err(|e| futu_core::error::FutuError::Encryption(format!("RSA encrypt failed: {e}")))
357}
358
359/// 使用 RSA 私钥解密
360pub fn rsa_private_decrypt(
361    pem_private_key: &str,
362    data: &[u8],
363) -> Result<Vec<u8>, futu_core::error::FutuError> {
364    use rsa::Pkcs1v15Encrypt;
365
366    let private_key = load_rsa_private_key(pem_private_key)?;
367
368    private_key
369        .decrypt(Pkcs1v15Encrypt, data)
370        .map_err(|e| futu_core::error::FutuError::Encryption(format!("RSA decrypt failed: {e}")))
371}
372
373/// 使用 RSA 公钥分块加密(支持任意长度数据)
374///
375/// RSA 1024-bit: 每块最多 117 字节明文 → 128 字节密文
376/// RSA 2048-bit: 每块最多 245 字节明文 → 256 字节密文
377/// 对应 C++ FutuOpenD 的 RSA 分块加密逻辑
378pub fn rsa_public_encrypt_blocks(
379    pem_private_key: &str,
380    data: &[u8],
381) -> Result<Vec<u8>, futu_core::error::FutuError> {
382    use rsa::traits::PublicKeyParts;
383    use rsa::Pkcs1v15Encrypt;
384
385    let private_key = load_rsa_private_key(pem_private_key)?;
386    let public_key = rsa::RsaPublicKey::from(&private_key);
387
388    // PKCS1v15 padding 需要 11 字节开销
389    let key_len = public_key.size();
390    let max_block = key_len - 11;
391
392    let mut result = Vec::with_capacity((data.len() / max_block + 1) * key_len);
393    let mut rng = rand::thread_rng();
394
395    for chunk in data.chunks(max_block) {
396        let encrypted = public_key
397            .encrypt(&mut rng, Pkcs1v15Encrypt, chunk)
398            .map_err(|e| {
399                futu_core::error::FutuError::Encryption(format!("RSA block encrypt failed: {e}"))
400            })?;
401        result.extend_from_slice(&encrypted);
402    }
403
404    Ok(result)
405}
406
407/// 使用 RSA 私钥分块解密(支持任意长度数据)
408///
409/// 密文按 key_size(128 或 256 字节)分块解密
410pub fn rsa_private_decrypt_blocks(
411    pem_private_key: &str,
412    data: &[u8],
413) -> Result<Vec<u8>, futu_core::error::FutuError> {
414    use rsa::traits::PublicKeyParts;
415    use rsa::Pkcs1v15Encrypt;
416
417    let private_key = load_rsa_private_key(pem_private_key)?;
418    let key_len = private_key.size();
419
420    if !data.len().is_multiple_of(key_len) {
421        return Err(futu_core::error::FutuError::Encryption(format!(
422            "RSA ciphertext length {} is not a multiple of key size {}",
423            data.len(),
424            key_len
425        )));
426    }
427
428    let mut result = Vec::with_capacity(data.len());
429
430    for chunk in data.chunks(key_len) {
431        let decrypted = private_key.decrypt(Pkcs1v15Encrypt, chunk).map_err(|e| {
432            futu_core::error::FutuError::Encryption(format!("RSA block decrypt failed: {e}"))
433        })?;
434        result.extend_from_slice(&decrypted);
435    }
436
437    Ok(result)
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_aes_ecb_roundtrip() {
446        let key = [
447            0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
448            0x4f, 0x3c,
449        ];
450        let plaintext = b"hello futu world";
451
452        let ciphertext = aes_ecb_encrypt(&key, plaintext);
453        let decrypted = aes_ecb_decrypt(&key, &ciphertext).unwrap();
454
455        assert_eq!(decrypted, plaintext);
456    }
457
458    #[test]
459    fn test_aes_ecb_roundtrip_multi_block() {
460        let key = [0x01; 16];
461        let plaintext = b"this is a longer message that spans multiple AES blocks for testing";
462
463        let ciphertext = aes_ecb_encrypt(&key, plaintext);
464        let decrypted = aes_ecb_decrypt(&key, &ciphertext).unwrap();
465
466        assert_eq!(decrypted, plaintext.as_slice());
467    }
468
469    #[test]
470    fn test_aes_ecb_roundtrip_exact_block() {
471        let key = [0xAA; 16];
472        // Exactly 16 bytes - will be padded to 32 bytes
473        let plaintext = b"exactly16bytes!!";
474
475        let ciphertext = aes_ecb_encrypt(&key, plaintext);
476        assert_eq!(ciphertext.len(), 32); // 16 + 16 padding
477        let decrypted = aes_ecb_decrypt(&key, &ciphertext).unwrap();
478
479        assert_eq!(decrypted, plaintext.as_slice());
480    }
481
482    #[test]
483    fn test_aes_ecb_empty_ciphertext() {
484        let key = [0x00; 16];
485        assert!(aes_ecb_decrypt(&key, &[]).is_err());
486    }
487
488    #[test]
489    fn test_aes_ecb_invalid_length() {
490        let key = [0x00; 16];
491        assert!(aes_ecb_decrypt(&key, &[0u8; 15]).is_err());
492    }
493
494    #[test]
495    fn test_nist_aes128_vector() {
496        // NIST FIPS 197 Appendix B test vector
497        let key: [u8; 16] = [
498            0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
499            0x4f, 0x3c,
500        ];
501        let input: [u8; 16] = [
502            0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37,
503            0x07, 0x34,
504        ];
505        let expected: [u8; 16] = [
506            0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a,
507            0x0b, 0x32,
508        ];
509
510        let output = aes_encrypt_block(&key, &input);
511        assert_eq!(output, expected);
512
513        let decrypted = aes_decrypt_block(&key, &output);
514        assert_eq!(decrypted, input);
515    }
516
517    #[test]
518    fn test_rsa_roundtrip() {
519        use rsa::pkcs8::EncodePrivateKey;
520
521        // 生成测试用 RSA 密钥
522        let mut rng = rand::thread_rng();
523        let private_key =
524            rsa::RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate RSA key");
525        let pem = private_key
526            .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
527            .expect("failed to encode PEM");
528
529        let plaintext = b"hello futu RSA encryption test";
530
531        let encrypted = rsa_public_encrypt(pem.as_ref(), plaintext).unwrap();
532        assert_ne!(encrypted, plaintext);
533
534        let decrypted = rsa_private_decrypt(pem.as_ref(), &encrypted).unwrap();
535        assert_eq!(decrypted, plaintext);
536    }
537
538    #[test]
539    fn test_rsa_blocks_roundtrip_small() {
540        use rsa::pkcs8::EncodePrivateKey;
541
542        // 1024-bit key: max 117 bytes per block
543        let mut rng = rand::thread_rng();
544        let private_key =
545            rsa::RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate RSA key");
546        let pem = private_key
547            .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
548            .expect("failed to encode PEM");
549
550        let plaintext = b"small data";
551        let encrypted = rsa_public_encrypt_blocks(pem.as_ref(), plaintext).unwrap();
552        let decrypted = rsa_private_decrypt_blocks(pem.as_ref(), &encrypted).unwrap();
553        assert_eq!(decrypted, plaintext);
554    }
555
556    #[test]
557    fn test_rsa_blocks_roundtrip_large() {
558        use rsa::pkcs8::EncodePrivateKey;
559
560        // 1024-bit key, data larger than 117 bytes → needs multiple blocks
561        let mut rng = rand::thread_rng();
562        let private_key =
563            rsa::RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate RSA key");
564        let pem = private_key
565            .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
566            .expect("failed to encode PEM");
567
568        // 300 bytes → 需要 3 个 RSA 块 (117 + 117 + 66)
569        let plaintext: Vec<u8> = (0u8..=255).chain(0u8..44).collect();
570        assert_eq!(plaintext.len(), 300);
571
572        let encrypted = rsa_public_encrypt_blocks(pem.as_ref(), &plaintext).unwrap();
573        // 3 blocks × 128 bytes = 384
574        assert_eq!(encrypted.len(), 384);
575
576        let decrypted = rsa_private_decrypt_blocks(pem.as_ref(), &encrypted).unwrap();
577        assert_eq!(decrypted, plaintext);
578    }
579}