zus_common/
encryption.rs

1//! DES Encryption support for ZUS RPC protocol
2//!
3//! This module provides DES-ECB encryption/decryption compatible with
4//! the C++ and Java ZUS implementations.
5//!
6//! ## Encryption Details
7//!
8//! - Algorithm: DES (Data Encryption Standard)
9//! - Mode: ECB (Electronic Codebook)
10//! - Padding: Zero-padding to 8-byte blocks
11//! - Key size: 8 bytes (64 bits, with 56 effective bits)
12//!
13//! ## Cross-Language Compatibility
14//!
15//! This implementation is compatible with:
16//! - C++: `zus::DesEncrypt` class in `des.cpp`
17//! - Java: `CryptUtil.desEncrypt()` / `CryptUtil.desDecrypt()` in `CryptUtil.java`
18//!
19//! ## Example
20//!
21//! ```rust
22//! use zus_common::encryption::{DesEncryptor, encrypt_des, decrypt_des};
23//!
24//! // Using the simple functions
25//! let key = b"12345678";
26//! let plaintext = b"Hello, World!";
27//! let encrypted = encrypt_des(plaintext, key).unwrap();
28//! let decrypted = decrypt_des(&encrypted, key).unwrap();
29//! assert_eq!(&decrypted[..plaintext.len()], plaintext);
30//!
31//! // Using the DesEncryptor for multiple operations
32//! let encryptor = DesEncryptor::new(key);
33//! let encrypted = encryptor.encrypt(plaintext).unwrap();
34//! let decrypted = encryptor.decrypt(&encrypted).unwrap();
35//! ```
36
37use des::{
38  Des,
39  cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
40};
41
42use crate::{Result, ZusError};
43
44/// DES block size in bytes
45pub const DES_BLOCK_SIZE: usize = 8;
46
47/// DES key size in bytes
48pub const DES_KEY_SIZE: usize = 8;
49
50/// DES Encryptor for encrypting and decrypting data using DES-ECB mode.
51///
52/// This is compatible with the C++ `zus::DesEncrypt` and Java `CryptUtil` implementations.
53#[derive(Clone)]
54pub struct DesEncryptor {
55  cipher: Des,
56}
57
58impl DesEncryptor {
59  /// Create a new DES encryptor with the given key.
60  ///
61  /// # Arguments
62  /// * `key` - 8-byte DES key
63  ///
64  /// # Panics
65  /// Panics if the key is not exactly 8 bytes.
66  pub fn new(key: &[u8]) -> Self {
67    assert_eq!(key.len(), DES_KEY_SIZE, "DES key must be exactly 8 bytes");
68    let cipher = Des::new_from_slice(key).expect("Invalid key length");
69    Self { cipher }
70  }
71
72  /// Create a new DES encryptor, returning an error if the key is invalid.
73  ///
74  /// # Arguments
75  /// * `key` - 8-byte DES key
76  ///
77  /// # Returns
78  /// * `Ok(DesEncryptor)` if the key is valid
79  /// * `Err(ZusError)` if the key is not 8 bytes
80  pub fn try_new(key: &[u8]) -> Result<Self> {
81    if key.len() != DES_KEY_SIZE {
82      return Err(ZusError::Encryption(format!(
83        "DES key must be exactly 8 bytes, got {}",
84        key.len()
85      )));
86    }
87    let cipher = Des::new_from_slice(key).map_err(|e| ZusError::Encryption(e.to_string()))?;
88    Ok(Self { cipher })
89  }
90
91  /// Encrypt data using DES-ECB mode with zero-padding.
92  ///
93  /// The data is padded with zeros to the next 8-byte boundary if necessary.
94  ///
95  /// # Arguments
96  /// * `data` - Data to encrypt (any length)
97  ///
98  /// # Returns
99  /// * Encrypted data (length is multiple of 8)
100  pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
101    // Calculate padded length (round up to next 8-byte boundary)
102    let padded_len = if data.is_empty() {
103      DES_BLOCK_SIZE
104    } else {
105      data.len().div_ceil(DES_BLOCK_SIZE) * DES_BLOCK_SIZE
106    };
107
108    // Create padded buffer with zeros
109    let mut buffer = vec![0u8; padded_len];
110    buffer[..data.len()].copy_from_slice(data);
111
112    // Encrypt each 8-byte block
113    for chunk in buffer.chunks_exact_mut(DES_BLOCK_SIZE) {
114      let block: &mut [u8; 8] = chunk.try_into().unwrap();
115      self.cipher.encrypt_block(block.into());
116    }
117
118    Ok(buffer)
119  }
120
121  /// Decrypt data using DES-ECB mode.
122  ///
123  /// Note: The decrypted data may contain trailing zeros from padding.
124  /// The caller should know the original data length to trim padding.
125  ///
126  /// # Arguments
127  /// * `data` - Encrypted data (length must be multiple of 8)
128  ///
129  /// # Returns
130  /// * Decrypted data (same length as input, may contain padding zeros)
131  pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
132    if !data.len().is_multiple_of(DES_BLOCK_SIZE) {
133      return Err(ZusError::Encryption(format!(
134        "Encrypted data length must be multiple of 8, got {}",
135        data.len()
136      )));
137    }
138
139    let mut buffer = data.to_vec();
140
141    // Decrypt each 8-byte block
142    for chunk in buffer.chunks_exact_mut(DES_BLOCK_SIZE) {
143      let block: &mut [u8; 8] = chunk.try_into().unwrap();
144      self.cipher.decrypt_block(block.into());
145    }
146
147    Ok(buffer)
148  }
149
150  /// Encrypt data in-place starting from the given offset.
151  ///
152  /// This matches the C++ `DesEncrypt::Encrypt(std::string& data, offset)` method.
153  ///
154  /// # Arguments
155  /// * `data` - Mutable data buffer (will be extended if necessary)
156  /// * `offset` - Starting offset for encryption
157  pub fn encrypt_in_place(&self, data: &mut Vec<u8>, offset: usize) -> Result<()> {
158    if offset >= data.len() {
159      return Ok(());
160    }
161
162    let len = data.len() - offset;
163    let padded_len = len.div_ceil(DES_BLOCK_SIZE) * DES_BLOCK_SIZE;
164
165    // Extend buffer if needed for padding
166    if offset + padded_len > data.len() {
167      data.resize(offset + padded_len, 0);
168    }
169
170    // Encrypt each 8-byte block
171    let slice = &mut data[offset..];
172    for chunk in slice.chunks_exact_mut(DES_BLOCK_SIZE) {
173      let block: &mut [u8; 8] = chunk.try_into().unwrap();
174      self.cipher.encrypt_block(block.into());
175    }
176
177    Ok(())
178  }
179
180  /// Decrypt data in-place starting from the given offset.
181  ///
182  /// This matches the C++ `DesEncrypt::Decrypt(std::string& data, offset)` method.
183  ///
184  /// # Arguments
185  /// * `data` - Mutable data buffer
186  /// * `offset` - Starting offset for decryption
187  pub fn decrypt_in_place(&self, data: &mut [u8], offset: usize) -> Result<()> {
188    if offset >= data.len() {
189      return Ok(());
190    }
191
192    let slice = &mut data[offset..];
193    if !slice.len().is_multiple_of(DES_BLOCK_SIZE) {
194      return Err(ZusError::Encryption(format!(
195        "Data length from offset must be multiple of 8, got {}",
196        slice.len()
197      )));
198    }
199
200    // Decrypt each 8-byte block
201    for chunk in slice.chunks_exact_mut(DES_BLOCK_SIZE) {
202      let block: &mut [u8; 8] = chunk.try_into().unwrap();
203      self.cipher.decrypt_block(block.into());
204    }
205
206    Ok(())
207  }
208}
209
210impl std::fmt::Debug for DesEncryptor {
211  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212    f.debug_struct("DesEncryptor").field("cipher", &"[...]").finish()
213  }
214}
215
216/// Encrypt data using DES-ECB mode with zero-padding.
217///
218/// # Arguments
219/// * `data` - Data to encrypt
220/// * `key` - 8-byte DES key
221///
222/// # Returns
223/// * Encrypted data
224pub fn encrypt_des(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
225  let encryptor = DesEncryptor::try_new(key)?;
226  encryptor.encrypt(data)
227}
228
229/// Decrypt data using DES-ECB mode.
230///
231/// # Arguments
232/// * `data` - Encrypted data (length must be multiple of 8)
233/// * `key` - 8-byte DES key
234///
235/// # Returns
236/// * Decrypted data (may contain padding zeros)
237pub fn decrypt_des(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
238  let encryptor = DesEncryptor::try_new(key)?;
239  encryptor.decrypt(data)
240}
241
242#[cfg(test)]
243mod tests {
244  use super::*;
245
246  #[test]
247  fn test_encrypt_decrypt_roundtrip() {
248    let key = b"12345678";
249    let plaintext = b"Hello, World!";
250
251    let encrypted = encrypt_des(plaintext, key).unwrap();
252    assert_eq!(encrypted.len(), 16); // 13 bytes padded to 16
253
254    let decrypted = decrypt_des(&encrypted, key).unwrap();
255    assert_eq!(&decrypted[..plaintext.len()], plaintext);
256  }
257
258  #[test]
259  fn test_encrypt_exact_block_size() {
260    let key = b"12345678";
261    let plaintext = b"12345678"; // Exactly 8 bytes
262
263    let encrypted = encrypt_des(plaintext, key).unwrap();
264    assert_eq!(encrypted.len(), 8);
265
266    let decrypted = decrypt_des(&encrypted, key).unwrap();
267    assert_eq!(&decrypted[..], plaintext);
268  }
269
270  #[test]
271  fn test_encrypt_multiple_blocks() {
272    let key = b"testkey!";
273    let plaintext = b"This is a longer message that spans multiple DES blocks.";
274
275    let encrypted = encrypt_des(plaintext, key).unwrap();
276    assert_eq!(encrypted.len() % 8, 0);
277
278    let decrypted = decrypt_des(&encrypted, key).unwrap();
279    assert_eq!(&decrypted[..plaintext.len()], plaintext);
280  }
281
282  #[test]
283  fn test_encrypt_empty() {
284    let key = b"12345678";
285    let plaintext = b"";
286
287    let encrypted = encrypt_des(plaintext, key).unwrap();
288    assert_eq!(encrypted.len(), 8); // Empty is padded to one block
289
290    let decrypted = decrypt_des(&encrypted, key).unwrap();
291    // All zeros after decryption
292    assert!(decrypted.iter().all(|&b| b == 0));
293  }
294
295  #[test]
296  fn test_invalid_key_length() {
297    let key = b"short";
298    let result = encrypt_des(b"test", key);
299    assert!(result.is_err());
300  }
301
302  #[test]
303  fn test_invalid_decrypt_length() {
304    let key = b"12345678";
305    let invalid_data = vec![1, 2, 3, 4, 5]; // Not multiple of 8
306    let result = decrypt_des(&invalid_data, key);
307    assert!(result.is_err());
308  }
309
310  #[test]
311  fn test_encryptor_reuse() {
312    let key = b"mykey123";
313    let encryptor = DesEncryptor::new(key);
314
315    let data1 = b"First message";
316    let data2 = b"Second message";
317
318    let enc1 = encryptor.encrypt(data1).unwrap();
319    let enc2 = encryptor.encrypt(data2).unwrap();
320
321    let dec1 = encryptor.decrypt(&enc1).unwrap();
322    let dec2 = encryptor.decrypt(&enc2).unwrap();
323
324    assert_eq!(&dec1[..data1.len()], data1);
325    assert_eq!(&dec2[..data2.len()], data2);
326  }
327
328  #[test]
329  fn test_in_place_encryption() {
330    let key = b"12345678";
331    let encryptor = DesEncryptor::new(key);
332
333    let mut data = b"Hello, World!".to_vec();
334    let original_len = data.len();
335
336    encryptor.encrypt_in_place(&mut data, 0).unwrap();
337    assert_eq!(data.len() % 8, 0);
338
339    encryptor.decrypt_in_place(&mut data, 0).unwrap();
340    assert_eq!(&data[..original_len], b"Hello, World!");
341  }
342
343  #[test]
344  fn test_in_place_with_offset() {
345    let key = b"12345678";
346    let encryptor = DesEncryptor::new(key);
347
348    // Simulate a message with header + body
349    let mut data = vec![0x01, 0x02, 0x03, 0x04]; // 4-byte header
350    data.extend_from_slice(b"Body data here"); // Body
351
352    let header = data[..4].to_vec();
353
354    encryptor.encrypt_in_place(&mut data, 4).unwrap();
355    assert_eq!(&data[..4], &header[..]); // Header unchanged
356
357    encryptor.decrypt_in_place(&mut data, 4).unwrap();
358    assert_eq!(&data[4..4 + 14], b"Body data here");
359  }
360}