async_snmp/v3/
privacy.rs

1//! Privacy (encryption) protocols for SNMPv3 (RFC 3414, RFC 3826).
2//!
3//! This module implements:
4//! - DES-CBC privacy (RFC 3414 Section 8)
5//! - AES-128-CFB privacy (RFC 3826)
6//! - AES-192-CFB privacy (RFC 3826)
7//! - AES-256-CFB privacy (RFC 3826)
8//!
9//! # Salt/IV Construction
10//!
11//! ## DES-CBC
12//! - Salt (privParameters): engineBoots (4 bytes) || counter (4 bytes) = 8 bytes
13//! - IV: pre-IV XOR salt (pre-IV is last 8 bytes of 16-byte privKey)
14//!
15//! ## AES-CFB-128
16//! - Salt (privParameters): 64-bit counter = 8 bytes
17//! - IV: engineBoots (4 bytes) || engineTime (4 bytes) || salt (8 bytes) = 16 bytes
18//!   (concatenation, NOT XOR)
19
20use std::sync::atomic::{AtomicU64, Ordering};
21
22use bytes::Bytes;
23use zeroize::{Zeroize, ZeroizeOnDrop};
24
25use super::{AuthProtocol, PrivProtocol};
26
27/// Error type for privacy (encryption/decryption) operations.
28///
29/// These errors indicate cryptographic failures. Callers should convert
30/// these to `Error::Auth` with appropriate target context.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum PrivacyError {
33    /// Invalid privParameters length (expected 8 bytes).
34    InvalidPrivParamsLength { expected: usize, actual: usize },
35    /// Ciphertext length not a multiple of block size.
36    InvalidCiphertextLength { length: usize, block_size: usize },
37    /// Invalid key length for cipher.
38    InvalidKeyLength,
39    /// Cipher operation failed.
40    CipherError,
41    /// Unsupported privacy protocol.
42    UnsupportedProtocol,
43}
44
45impl std::fmt::Display for PrivacyError {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            Self::InvalidPrivParamsLength { expected, actual } => {
49                write!(
50                    f,
51                    "invalid privParameters length: expected {}, got {}",
52                    expected, actual
53                )
54            }
55            Self::InvalidCiphertextLength { length, block_size } => {
56                write!(
57                    f,
58                    "ciphertext length {} not multiple of block size {}",
59                    length, block_size
60                )
61            }
62            Self::InvalidKeyLength => write!(f, "invalid key length"),
63            Self::CipherError => write!(f, "cipher operation failed"),
64            Self::UnsupportedProtocol => write!(f, "unsupported privacy protocol"),
65        }
66    }
67}
68
69impl std::error::Error for PrivacyError {}
70
71/// Result type for privacy operations.
72pub type PrivacyResult<T> = std::result::Result<T, PrivacyError>;
73
74/// Generate a random non-zero u64 for salt initialization.
75///
76/// Uses the OS cryptographic random source via `getrandom`.
77fn random_nonzero_u64() -> u64 {
78    let mut buf = [0u8; 8];
79    loop {
80        getrandom::fill(&mut buf).expect("getrandom failed");
81        let val = u64::from_ne_bytes(buf);
82        if val != 0 {
83            return val;
84        }
85        // Extremely unlikely (1 in 2^64), but loop if we got zero
86    }
87}
88
89/// Privacy key for encryption/decryption operations.
90///
91/// Derives encryption keys from a password and engine ID using the same
92/// process as authentication keys, then uses the appropriate portion
93/// based on the privacy protocol.
94///
95/// # Security
96///
97/// Key material is automatically zeroed from memory when the key is dropped,
98/// using the `zeroize` crate. This provides defense-in-depth against memory
99/// scraping attacks.
100#[derive(Clone, Zeroize, ZeroizeOnDrop)]
101pub struct PrivKey {
102    /// The localized key bytes
103    key: Vec<u8>,
104    /// Privacy protocol
105    #[zeroize(skip)]
106    protocol: PrivProtocol,
107    /// Salt counter for generating unique IVs
108    /// For thread safety, each PrivKey instance gets its own counter
109    #[zeroize(skip)]
110    salt_counter: u64,
111}
112
113/// Thread-safe salt counter for shared use across multiple encryptions.
114pub struct SaltCounter(AtomicU64);
115
116impl SaltCounter {
117    /// Create a new salt counter initialized from cryptographic randomness.
118    pub fn new() -> Self {
119        Self(AtomicU64::new(random_nonzero_u64()))
120    }
121
122    /// Create a salt counter initialized to a specific value.
123    ///
124    /// This is primarily for testing purposes.
125    pub fn from_value(value: u64) -> Self {
126        Self(AtomicU64::new(value))
127    }
128
129    /// Get the next salt value and increment the counter.
130    ///
131    /// This method never returns zero. Per net-snmp behavior, zero is skipped
132    /// on wraparound to avoid potential IV reuse issues.
133    pub fn next(&self) -> u64 {
134        let val = self.0.fetch_add(1, Ordering::SeqCst);
135        // Skip zero on wraparound (matches net-snmp behavior)
136        if val == 0 {
137            self.0.fetch_add(1, Ordering::SeqCst)
138        } else {
139            val
140        }
141    }
142}
143
144impl Default for SaltCounter {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150impl PrivKey {
151    /// Derive a privacy key from a password and engine ID.
152    ///
153    /// The key derivation uses the same algorithm as authentication keys
154    /// (RFC 3414 A.2), but the resulting key is used differently:
155    /// - DES: first 8 bytes = key, last 8 bytes = pre-IV
156    /// - 3DES: first 24 bytes = key, last 8 bytes = pre-IV
157    /// - AES: first 16/24/32 bytes = key (depending on AES variant)
158    ///
159    /// Key extension is automatically applied when needed based on the auth/priv
160    /// protocol combination:
161    ///
162    /// - AES-192/256 with SHA-1 or MD5: Blumenthal extension (draft-blumenthal-aes-usm-04)
163    /// - 3DES with SHA-1 or MD5: Reeder extension (draft-reeder-snmpv3-usm-3desede-00)
164    ///
165    /// # Performance Note
166    ///
167    /// This method performs the full key derivation (~850μs for SHA-256). When
168    /// polling many engines with shared credentials, use [`MasterKey`](super::MasterKey)
169    /// and call [`PrivKey::from_master_key`] for each engine.
170    ///
171    /// # Example
172    ///
173    /// ```rust
174    /// use async_snmp::{AuthProtocol, PrivProtocol, v3::PrivKey};
175    ///
176    /// let engine_id = [0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04];
177    ///
178    /// // SHA-1 only produces 20 bytes, but AES-256 needs 32.
179    /// // Blumenthal extension is automatically applied.
180    /// let priv_key = PrivKey::from_password(
181    ///     AuthProtocol::Sha1,
182    ///     PrivProtocol::Aes256,
183    ///     b"password",
184    ///     &engine_id,
185    /// );
186    /// ```
187    pub fn from_password(
188        auth_protocol: AuthProtocol,
189        priv_protocol: PrivProtocol,
190        password: &[u8],
191        engine_id: &[u8],
192    ) -> Self {
193        use super::MasterKey;
194
195        let master = MasterKey::from_password(auth_protocol, password);
196        Self::from_master_key(&master, priv_protocol, engine_id)
197    }
198
199    /// Derive a privacy key from a master key and engine ID.
200    ///
201    /// This is the efficient path when you have a cached [`MasterKey`](super::MasterKey).
202    /// Key extension is automatically applied when needed based on the auth/priv
203    /// protocol combination:
204    ///
205    /// - AES-192/256 with SHA-1 or MD5: Blumenthal extension (draft-blumenthal-aes-usm-04)
206    /// - 3DES with SHA-1 or MD5: Reeder extension (draft-reeder-snmpv3-usm-3desede-00)
207    ///
208    /// # Example
209    ///
210    /// ```rust
211    /// use async_snmp::{AuthProtocol, MasterKey, PrivProtocol, v3::PrivKey};
212    ///
213    /// let master = MasterKey::from_password(AuthProtocol::Sha1, b"password");
214    /// let engine_id = [0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04];
215    ///
216    /// // SHA-1 only produces 20 bytes, but AES-256 needs 32.
217    /// // Blumenthal extension is automatically applied.
218    /// let priv_key = PrivKey::from_master_key(&master, PrivProtocol::Aes256, &engine_id);
219    /// ```
220    pub fn from_master_key(
221        master: &super::MasterKey,
222        priv_protocol: PrivProtocol,
223        engine_id: &[u8],
224    ) -> Self {
225        use super::{
226            KeyExtension,
227            auth::{extend_key, extend_key_reeder},
228        };
229
230        let auth_protocol = master.protocol();
231        let key_extension = priv_protocol.key_extension_for(auth_protocol);
232
233        // Localize the master key (per RFC 3826 Section 1.2)
234        let localized = master.localize(engine_id);
235        let key_bytes = localized.as_bytes();
236
237        let key = match key_extension {
238            KeyExtension::None => key_bytes.to_vec(),
239            KeyExtension::Blumenthal => {
240                extend_key(auth_protocol, key_bytes, priv_protocol.key_len())
241            }
242            KeyExtension::Reeder => {
243                extend_key_reeder(auth_protocol, key_bytes, engine_id, priv_protocol.key_len())
244            }
245        };
246
247        Self {
248            key,
249            protocol: priv_protocol,
250            salt_counter: Self::init_salt(),
251        }
252    }
253
254    /// Create a privacy key from raw localized key bytes.
255    pub fn from_bytes(protocol: PrivProtocol, key: impl Into<Vec<u8>>) -> Self {
256        Self {
257            key: key.into(),
258            protocol,
259            salt_counter: Self::init_salt(),
260        }
261    }
262
263    /// Initialize salt from cryptographic randomness.
264    ///
265    /// Never returns zero to avoid IV reuse issues on wraparound.
266    fn init_salt() -> u64 {
267        random_nonzero_u64()
268    }
269
270    /// Get the privacy protocol.
271    pub fn protocol(&self) -> PrivProtocol {
272        self.protocol
273    }
274
275    /// Get the encryption key portion.
276    pub fn encryption_key(&self) -> &[u8] {
277        match self.protocol {
278            PrivProtocol::Des => &self.key[..8],
279            PrivProtocol::Des3 => &self.key[..24],
280            PrivProtocol::Aes128 => &self.key[..16],
281            PrivProtocol::Aes192 => &self.key[..24],
282            PrivProtocol::Aes256 => &self.key[..32],
283        }
284    }
285
286    /// Encrypt data and return (ciphertext, privParameters).
287    ///
288    /// # Arguments
289    /// * `plaintext` - The data to encrypt (typically the serialized ScopedPDU)
290    /// * `engine_boots` - The authoritative engine's boot count
291    /// * `engine_time` - The authoritative engine's time
292    /// * `salt_counter` - Optional shared salt counter; if None, uses internal counter
293    ///
294    /// # Returns
295    /// * `Ok((ciphertext, priv_params))` on success
296    /// * `Err` on encryption failure
297    pub fn encrypt(
298        &mut self,
299        plaintext: &[u8],
300        engine_boots: u32,
301        engine_time: u32,
302        salt_counter: Option<&SaltCounter>,
303    ) -> PrivacyResult<(Bytes, Bytes)> {
304        let salt = salt_counter.map(|c| c.next()).unwrap_or_else(|| {
305            let mut s = self.salt_counter;
306            self.salt_counter = self.salt_counter.wrapping_add(1);
307            // Skip zero on wraparound (matches net-snmp behavior)
308            if s == 0 {
309                s = self.salt_counter;
310                self.salt_counter = self.salt_counter.wrapping_add(1);
311            }
312            s
313        });
314
315        match self.protocol {
316            PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
317            PrivProtocol::Des3 => self.encrypt_des3(plaintext, engine_boots, salt),
318            PrivProtocol::Aes128 => {
319                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
320            }
321            PrivProtocol::Aes192 => {
322                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
323            }
324            PrivProtocol::Aes256 => {
325                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
326            }
327        }
328    }
329
330    /// Decrypt data using the privParameters from the message.
331    ///
332    /// # Arguments
333    /// * `ciphertext` - The encrypted data
334    /// * `engine_boots` - The authoritative engine's boot count (from message)
335    /// * `engine_time` - The authoritative engine's time (from message)
336    /// * `priv_params` - The privParameters field from the message
337    ///
338    /// # Returns
339    /// * `Ok(plaintext)` on success
340    /// * `Err` on decryption failure
341    pub fn decrypt(
342        &self,
343        ciphertext: &[u8],
344        engine_boots: u32,
345        engine_time: u32,
346        priv_params: &[u8],
347    ) -> PrivacyResult<Bytes> {
348        if priv_params.len() != 8 {
349            tracing::debug!(target: "async_snmp::crypto", { expected = 8, actual = priv_params.len() }, "invalid privParameters length");
350            return Err(PrivacyError::InvalidPrivParamsLength {
351                expected: 8,
352                actual: priv_params.len(),
353            });
354        }
355
356        match self.protocol {
357            PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
358            PrivProtocol::Des3 => self.decrypt_des3(ciphertext, priv_params),
359            PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
360                self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
361            }
362        }
363    }
364
365    /// DES-CBC encryption (RFC 3414 Section 8.1.1).
366    fn encrypt_des(
367        &self,
368        plaintext: &[u8],
369        engine_boots: u32,
370        salt_int: u64,
371    ) -> PrivacyResult<(Bytes, Bytes)> {
372        use cbc::cipher::{BlockEncryptMut, KeyIvInit};
373        type DesCbc = cbc::Encryptor<des::Des>;
374
375        // DES key is first 8 bytes
376        let key = &self.key[..8];
377        // Pre-IV is last 8 bytes of 16-byte privKey
378        let pre_iv = &self.key[8..16];
379
380        // Salt = engineBoots (4 bytes MSB) || counter (4 bytes MSB)
381        // We use the lower 32 bits of salt_int as the counter
382        let mut salt = [0u8; 8];
383        salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
384        salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
385
386        // IV = pre-IV XOR salt
387        let mut iv = [0u8; 8];
388        for i in 0..8 {
389            iv[i] = pre_iv[i] ^ salt[i];
390        }
391
392        // Pad plaintext to multiple of 8 bytes
393        let pad_len = (8 - (plaintext.len() % 8)) % 8;
394        let padded_len = plaintext.len() + if pad_len == 0 { 0 } else { pad_len };
395        // DES requires at least some padding if not aligned
396        let padded_len = if padded_len == plaintext.len() && !plaintext.len().is_multiple_of(8) {
397            plaintext.len() + (8 - plaintext.len() % 8)
398        } else {
399            padded_len
400        };
401
402        let mut buffer = vec![
403            0u8;
404            if padded_len > plaintext.len() {
405                padded_len
406            } else {
407                plaintext.len() + 8 - (plaintext.len() % 8)
408            }
409        ];
410        let padded_len = buffer.len();
411        buffer[..plaintext.len()].copy_from_slice(plaintext);
412
413        // Encrypt in-place
414        let cipher = DesCbc::new_from_slices(key, &iv).map_err(|_| {
415            tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: invalid key length");
416            PrivacyError::InvalidKeyLength
417        })?;
418
419        let ciphertext = cipher
420            .encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, padded_len)
421            .map_err(|_| {
422                tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: cipher error");
423                PrivacyError::CipherError
424            })?;
425
426        Ok((
427            Bytes::copy_from_slice(ciphertext),
428            Bytes::copy_from_slice(&salt),
429        ))
430    }
431
432    /// DES-CBC decryption (RFC 3414 Section 8.1.1).
433    fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
434        use cbc::cipher::{BlockDecryptMut, KeyIvInit};
435        type DesCbc = cbc::Decryptor<des::Des>;
436
437        if !ciphertext.len().is_multiple_of(8) {
438            tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "DES decryption failed: invalid ciphertext length");
439            return Err(PrivacyError::InvalidCiphertextLength {
440                length: ciphertext.len(),
441                block_size: 8,
442            });
443        }
444
445        // DES key is first 8 bytes
446        let key = &self.key[..8];
447        // Pre-IV is last 8 bytes of 16-byte privKey
448        let pre_iv = &self.key[8..16];
449
450        // Salt is the privParameters
451        let salt = priv_params;
452
453        // IV = pre-IV XOR salt
454        let mut iv = [0u8; 8];
455        for i in 0..8 {
456            iv[i] = pre_iv[i] ^ salt[i];
457        }
458
459        // Decrypt
460        let cipher = DesCbc::new_from_slices(key, &iv).map_err(|_| {
461            tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: invalid key length");
462            PrivacyError::InvalidKeyLength
463        })?;
464
465        let mut buffer = ciphertext.to_vec();
466        let plaintext = cipher
467            .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
468            .map_err(|_| {
469                tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: cipher error");
470                PrivacyError::CipherError
471            })?;
472
473        Ok(Bytes::copy_from_slice(plaintext))
474    }
475
476    /// 3DES-EDE CBC encryption (draft-reeder-snmpv3-usm-3desede-00 Section 5.1.1.2).
477    fn encrypt_des3(
478        &self,
479        plaintext: &[u8],
480        engine_boots: u32,
481        salt_int: u64,
482    ) -> PrivacyResult<(Bytes, Bytes)> {
483        use cbc::cipher::{BlockEncryptMut, KeyIvInit};
484        type Des3Cbc = cbc::Encryptor<des::TdesEde3>;
485
486        // 3DES key is first 24 bytes (K1, K2, K3)
487        let key = &self.key[..24];
488        // Pre-IV is bytes 24-31 of the 32-byte privKey
489        let pre_iv = &self.key[24..32];
490
491        // Salt = engineBoots (4 bytes MSB) || counter (4 bytes MSB)
492        let mut salt = [0u8; 8];
493        salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
494        salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
495
496        // IV = pre-IV XOR salt
497        let mut iv = [0u8; 8];
498        for i in 0..8 {
499            iv[i] = pre_iv[i] ^ salt[i];
500        }
501
502        // Pad plaintext to multiple of 8 bytes
503        let padded_len = plaintext.len().next_multiple_of(8);
504        let mut buffer = vec![0u8; padded_len];
505        buffer[..plaintext.len()].copy_from_slice(plaintext);
506
507        // Encrypt in-place
508        let cipher = Des3Cbc::new_from_slices(key, &iv).map_err(|_| {
509            tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: invalid key length");
510            PrivacyError::InvalidKeyLength
511        })?;
512
513        let ciphertext = cipher
514            .encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, padded_len)
515            .map_err(|_| {
516                tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: cipher error");
517                PrivacyError::CipherError
518            })?;
519
520        Ok((
521            Bytes::copy_from_slice(ciphertext),
522            Bytes::copy_from_slice(&salt),
523        ))
524    }
525
526    /// 3DES-EDE CBC decryption (draft-reeder-snmpv3-usm-3desede-00 Section 5.1.1.3).
527    fn decrypt_des3(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
528        use cbc::cipher::{BlockDecryptMut, KeyIvInit};
529        type Des3Cbc = cbc::Decryptor<des::TdesEde3>;
530
531        if !ciphertext.len().is_multiple_of(8) {
532            tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "3DES decryption failed: invalid ciphertext length");
533            return Err(PrivacyError::InvalidCiphertextLength {
534                length: ciphertext.len(),
535                block_size: 8,
536            });
537        }
538
539        // 3DES key is first 24 bytes (K1, K2, K3)
540        let key = &self.key[..24];
541        // Pre-IV is bytes 24-31 of the 32-byte privKey
542        let pre_iv = &self.key[24..32];
543
544        // Salt is the privParameters
545        let salt = priv_params;
546
547        // IV = pre-IV XOR salt
548        let mut iv = [0u8; 8];
549        for i in 0..8 {
550            iv[i] = pre_iv[i] ^ salt[i];
551        }
552
553        // Decrypt
554        let cipher = Des3Cbc::new_from_slices(key, &iv).map_err(|_| {
555            tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: invalid key length");
556            PrivacyError::InvalidKeyLength
557        })?;
558
559        let mut buffer = ciphertext.to_vec();
560        let plaintext = cipher
561            .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
562            .map_err(|_| {
563                tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: cipher error");
564                PrivacyError::CipherError
565            })?;
566
567        Ok(Bytes::copy_from_slice(plaintext))
568    }
569
570    /// AES-CFB encryption (RFC 3826 Section 3.1).
571    fn encrypt_aes(
572        &self,
573        plaintext: &[u8],
574        engine_boots: u32,
575        engine_time: u32,
576        salt: u64,
577        key_len: usize,
578    ) -> PrivacyResult<(Bytes, Bytes)> {
579        use aes::{Aes128, Aes192, Aes256};
580        use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
581
582        // AES key is first key_len bytes
583        let key = &self.key[..key_len];
584
585        // Salt as 8 bytes (big-endian)
586        let salt_bytes = salt.to_be_bytes();
587
588        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
589        // This is CONCATENATION, not XOR (unlike DES)
590        let mut iv = [0u8; 16];
591        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
592        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
593        iv[8..].copy_from_slice(&salt_bytes);
594
595        let mut buffer = plaintext.to_vec();
596
597        match key_len {
598            16 => {
599                type Aes128Cfb = cfb_mode::Encryptor<Aes128>;
600                let cipher = Aes128Cfb::new_from_slices(key, &iv).map_err(|_| {
601                    tracing::debug!(target: "async_snmp::crypto", "AES-128 encryption failed: invalid key length");
602                    PrivacyError::InvalidKeyLength
603                })?;
604                cipher.encrypt(&mut buffer);
605            }
606            24 => {
607                type Aes192Cfb = cfb_mode::Encryptor<Aes192>;
608                let cipher = Aes192Cfb::new_from_slices(key, &iv).map_err(|_| {
609                    tracing::debug!(target: "async_snmp::crypto", "AES-192 encryption failed: invalid key length");
610                    PrivacyError::InvalidKeyLength
611                })?;
612                cipher.encrypt(&mut buffer);
613            }
614            32 => {
615                type Aes256Cfb = cfb_mode::Encryptor<Aes256>;
616                let cipher = Aes256Cfb::new_from_slices(key, &iv).map_err(|_| {
617                    tracing::debug!(target: "async_snmp::crypto", "AES-256 encryption failed: invalid key length");
618                    PrivacyError::InvalidKeyLength
619                })?;
620                cipher.encrypt(&mut buffer);
621            }
622            _ => {
623                tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES encryption failed: unsupported key length");
624                return Err(PrivacyError::UnsupportedProtocol);
625            }
626        }
627
628        Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
629    }
630
631    /// AES-CFB decryption (RFC 3826 Section 3.1.4).
632    fn decrypt_aes(
633        &self,
634        ciphertext: &[u8],
635        engine_boots: u32,
636        engine_time: u32,
637        priv_params: &[u8],
638    ) -> PrivacyResult<Bytes> {
639        use aes::{Aes128, Aes192, Aes256};
640        use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
641
642        let key_len = match self.protocol {
643            PrivProtocol::Aes128 => 16,
644            PrivProtocol::Aes192 => 24,
645            PrivProtocol::Aes256 => 32,
646            _ => unreachable!(),
647        };
648
649        // AES key is first key_len bytes
650        let key = &self.key[..key_len];
651
652        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
653        let mut iv = [0u8; 16];
654        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
655        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
656        iv[8..].copy_from_slice(priv_params);
657
658        let mut buffer = ciphertext.to_vec();
659
660        match key_len {
661            16 => {
662                type Aes128Cfb = cfb_mode::Decryptor<Aes128>;
663                let cipher = Aes128Cfb::new_from_slices(key, &iv).map_err(|_| {
664                    tracing::debug!(target: "async_snmp::crypto", "AES-128 decryption failed: invalid key length");
665                    PrivacyError::InvalidKeyLength
666                })?;
667                cipher.decrypt(&mut buffer);
668            }
669            24 => {
670                type Aes192Cfb = cfb_mode::Decryptor<Aes192>;
671                let cipher = Aes192Cfb::new_from_slices(key, &iv).map_err(|_| {
672                    tracing::debug!(target: "async_snmp::crypto", "AES-192 decryption failed: invalid key length");
673                    PrivacyError::InvalidKeyLength
674                })?;
675                cipher.decrypt(&mut buffer);
676            }
677            32 => {
678                type Aes256Cfb = cfb_mode::Decryptor<Aes256>;
679                let cipher = Aes256Cfb::new_from_slices(key, &iv).map_err(|_| {
680                    tracing::debug!(target: "async_snmp::crypto", "AES-256 decryption failed: invalid key length");
681                    PrivacyError::InvalidKeyLength
682                })?;
683                cipher.decrypt(&mut buffer);
684            }
685            _ => {
686                tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES decryption failed: unsupported key length");
687                return Err(PrivacyError::UnsupportedProtocol);
688            }
689        }
690
691        Ok(Bytes::from(buffer))
692    }
693}
694
695impl std::fmt::Debug for PrivKey {
696    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
697        f.debug_struct("PrivKey")
698            .field("protocol", &self.protocol)
699            .field("key", &"[REDACTED]")
700            .finish()
701    }
702}
703
704#[cfg(test)]
705mod tests {
706    use super::*;
707    use crate::format::hex::decode as decode_hex;
708
709    #[test]
710    fn test_des_encrypt_decrypt_roundtrip() {
711        // Create a 16-byte key (8 for DES, 8 for pre-IV)
712        let key = vec![
713            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DES key
714            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // pre-IV
715        ];
716        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
717
718        let plaintext = b"Hello, SNMPv3 World!";
719        let engine_boots = 100u32;
720        let engine_time = 12345u32;
721
722        let (ciphertext, priv_params) = priv_key
723            .encrypt(plaintext, engine_boots, engine_time, None)
724            .expect("encryption failed");
725
726        // Verify ciphertext is different from plaintext
727        assert_ne!(ciphertext.as_ref(), plaintext);
728        // Verify priv_params is 8 bytes
729        assert_eq!(priv_params.len(), 8);
730
731        // Decrypt
732        let decrypted = priv_key
733            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
734            .expect("decryption failed");
735
736        // DES pads to 8-byte boundary, so decrypted may be longer
737        assert!(decrypted.len() >= plaintext.len());
738        assert_eq!(&decrypted[..plaintext.len()], plaintext);
739    }
740
741    #[test]
742    fn test_des3_encrypt_decrypt_roundtrip() {
743        // Create a 32-byte key (24 for 3DES, 8 for pre-IV)
744        let key = vec![
745            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // K1
746            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // K2
747            0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, // K3
748            0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, // pre-IV
749        ];
750        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des3, key);
751
752        let plaintext = b"Hello, SNMPv3 World with 3DES!";
753        let engine_boots = 100u32;
754        let engine_time = 12345u32;
755
756        let (ciphertext, priv_params) = priv_key
757            .encrypt(plaintext, engine_boots, engine_time, None)
758            .expect("encryption failed");
759
760        // Verify ciphertext is different from plaintext
761        assert_ne!(ciphertext.as_ref(), plaintext);
762        // Verify priv_params is 8 bytes
763        assert_eq!(priv_params.len(), 8);
764
765        // Decrypt
766        let decrypted = priv_key
767            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
768            .expect("decryption failed");
769
770        // 3DES pads to 8-byte boundary, so decrypted may be longer
771        assert!(decrypted.len() >= plaintext.len());
772        assert_eq!(&decrypted[..plaintext.len()], plaintext);
773    }
774
775    #[test]
776    fn test_aes128_encrypt_decrypt_roundtrip() {
777        // Create a 16-byte key for AES-128
778        let key = vec![
779            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
780            0x0f, 0x10,
781        ];
782        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
783
784        let plaintext = b"Hello, SNMPv3 AES World!";
785        let engine_boots = 200u32;
786        let engine_time = 54321u32;
787
788        let (ciphertext, priv_params) = priv_key
789            .encrypt(plaintext, engine_boots, engine_time, None)
790            .expect("encryption failed");
791
792        // Verify ciphertext is different from plaintext
793        assert_ne!(ciphertext.as_ref(), plaintext);
794        // Verify priv_params is 8 bytes (salt)
795        assert_eq!(priv_params.len(), 8);
796
797        // Decrypt
798        let decrypted = priv_key
799            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
800            .expect("decryption failed");
801
802        // AES-CFB doesn't require padding, so lengths should match
803        assert_eq!(decrypted.len(), plaintext.len());
804        assert_eq!(decrypted.as_ref(), plaintext);
805    }
806
807    #[test]
808    fn test_des_invalid_ciphertext_length() {
809        let key = vec![0u8; 16];
810        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
811
812        // Ciphertext not multiple of 8
813        let ciphertext = [0u8; 13];
814        let priv_params = [0u8; 8];
815
816        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
817        assert!(result.is_err());
818    }
819
820    #[test]
821    fn test_invalid_priv_params_length() {
822        let key = vec![0u8; 16];
823        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
824
825        // priv_params should be 8 bytes
826        let ciphertext = [0u8; 16];
827        let priv_params = [0u8; 4]; // Wrong length
828
829        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
830        assert!(result.is_err());
831    }
832
833    #[test]
834    fn test_salt_counter() {
835        let counter = SaltCounter::new();
836        let s1 = counter.next();
837        let s2 = counter.next();
838        let s3 = counter.next();
839
840        // Each call should increment
841        assert_eq!(s2, s1.wrapping_add(1));
842        assert_eq!(s3, s2.wrapping_add(1));
843    }
844
845    /// Test that SaltCounter never returns zero.
846    ///
847    /// Per net-snmp behavior (snmpusm.c:1319-1320), zero salt values should be
848    /// skipped to avoid potential IV reuse issues on wraparound.
849    #[test]
850    fn test_salt_counter_skips_zero() {
851        // Create a counter initialized to u64::MAX
852        let counter = SaltCounter::from_value(u64::MAX);
853
854        // First call returns u64::MAX
855        let s1 = counter.next();
856        assert_eq!(s1, u64::MAX);
857
858        // Second call would normally return 0 (wraparound), but should skip to 1
859        let s2 = counter.next();
860        assert_ne!(s2, 0, "SaltCounter should never return zero");
861        assert_eq!(s2, 1, "SaltCounter should skip 0 and return 1");
862
863        // Subsequent calls should continue normally
864        let s3 = counter.next();
865        assert_eq!(s3, 2);
866    }
867
868    /// Test that PrivKey's internal salt counter never produces zero.
869    ///
870    /// When using the internal counter (not a shared SaltCounter), the salt
871    /// should also skip zero on wraparound.
872    #[test]
873    fn test_priv_key_internal_salt_skips_zero() {
874        let key = vec![0u8; 16];
875        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
876
877        // Set the internal counter to u64::MAX
878        priv_key.salt_counter = u64::MAX;
879
880        let plaintext = b"test";
881
882        // First encryption uses u64::MAX
883        let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
884        assert_eq!(
885            u64::from_be_bytes(salt1.as_ref().try_into().unwrap()),
886            u64::MAX
887        );
888
889        // Second encryption should skip 0 and use 1
890        let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
891        let salt2_value = u64::from_be_bytes(salt2.as_ref().try_into().unwrap());
892        assert_ne!(salt2_value, 0, "Salt should never be zero");
893        assert_eq!(salt2_value, 1, "Salt should skip 0 and be 1");
894
895        // Third encryption should use 2
896        let (_, salt3) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
897        let salt3_value = u64::from_be_bytes(salt3.as_ref().try_into().unwrap());
898        assert_eq!(salt3_value, 2);
899    }
900
901    #[test]
902    fn test_multiple_encryptions_different_salt() {
903        let key = vec![0u8; 16];
904        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
905
906        let plaintext = b"test data";
907
908        let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
909        let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
910
911        // Salts should be different for each encryption
912        assert_ne!(salt1, salt2);
913    }
914
915    #[test]
916    fn test_from_password() {
917        // Test that we can derive a privacy key from a password
918        let password = b"maplesyrup";
919        let engine_id = decode_hex("000000000000000000000002").unwrap();
920
921        let mut priv_key = PrivKey::from_password(
922            AuthProtocol::Sha1,
923            PrivProtocol::Aes128,
924            password,
925            &engine_id,
926        );
927
928        // Just verify we can encrypt/decrypt with the derived key
929        let plaintext = b"test message";
930        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
931        let decrypted = priv_key
932            .decrypt(&ciphertext, 100, 200, &priv_params)
933            .unwrap();
934
935        assert_eq!(decrypted.as_ref(), plaintext);
936    }
937
938    #[test]
939    fn test_aes192_encrypt_decrypt_roundtrip() {
940        // Create a 24-byte key for AES-192
941        let key = vec![
942            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
943            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
944        ];
945        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key);
946
947        let plaintext = b"Hello, SNMPv3 AES-192 World!";
948        let engine_boots = 300u32;
949        let engine_time = 67890u32;
950
951        let (ciphertext, priv_params) = priv_key
952            .encrypt(plaintext, engine_boots, engine_time, None)
953            .expect("AES-192 encryption failed");
954
955        // Verify ciphertext is different from plaintext
956        assert_ne!(ciphertext.as_ref(), plaintext);
957        // Verify priv_params is 8 bytes (salt)
958        assert_eq!(priv_params.len(), 8);
959
960        // Decrypt
961        let decrypted = priv_key
962            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
963            .expect("AES-192 decryption failed");
964
965        // AES-CFB doesn't require padding, so lengths should match
966        assert_eq!(decrypted.len(), plaintext.len());
967        assert_eq!(decrypted.as_ref(), plaintext);
968    }
969
970    #[test]
971    fn test_aes256_encrypt_decrypt_roundtrip() {
972        // Create a 32-byte key for AES-256
973        let key = vec![
974            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
975            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
976            0x1d, 0x1e, 0x1f, 0x20,
977        ];
978        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key);
979
980        let plaintext = b"Hello, SNMPv3 AES-256 World!";
981        let engine_boots = 400u32;
982        let engine_time = 11111u32;
983
984        let (ciphertext, priv_params) = priv_key
985            .encrypt(plaintext, engine_boots, engine_time, None)
986            .expect("AES-256 encryption failed");
987
988        // Verify ciphertext is different from plaintext
989        assert_ne!(ciphertext.as_ref(), plaintext);
990        // Verify priv_params is 8 bytes (salt)
991        assert_eq!(priv_params.len(), 8);
992
993        // Decrypt
994        let decrypted = priv_key
995            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
996            .expect("AES-256 decryption failed");
997
998        // AES-CFB doesn't require padding, so lengths should match
999        assert_eq!(decrypted.len(), plaintext.len());
1000        assert_eq!(decrypted.as_ref(), plaintext);
1001    }
1002
1003    #[test]
1004    fn test_aes192_from_password() {
1005        // For AES-192 (24-byte key), we need SHA-224 or higher auth protocol
1006        let password = b"longpassword123";
1007        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
1008
1009        let mut priv_key = PrivKey::from_password(
1010            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, enough for AES-192
1011            PrivProtocol::Aes192,
1012            password,
1013            &engine_id,
1014        );
1015
1016        let plaintext = b"test message for AES-192";
1017        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
1018        let decrypted = priv_key
1019            .decrypt(&ciphertext, 100, 200, &priv_params)
1020            .unwrap();
1021
1022        assert_eq!(decrypted.as_ref(), plaintext);
1023    }
1024
1025    #[test]
1026    fn test_aes256_from_password() {
1027        // For AES-256 (32-byte key), we need SHA-256 or higher auth protocol
1028        let password = b"anotherlongpassword456";
1029        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
1030
1031        let mut priv_key = PrivKey::from_password(
1032            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, exactly enough for AES-256
1033            PrivProtocol::Aes256,
1034            password,
1035            &engine_id,
1036        );
1037
1038        let plaintext = b"test message for AES-256";
1039        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
1040        let decrypted = priv_key
1041            .decrypt(&ciphertext, 100, 200, &priv_params)
1042            .unwrap();
1043
1044        assert_eq!(decrypted.as_ref(), plaintext);
1045    }
1046
1047    // ========================================================================
1048    // Wrong Key Decryption Tests
1049    //
1050    // These tests verify that decryption with the wrong key produces garbage,
1051    // not the original plaintext. Note: Stream ciphers like AES-CFB don't return
1052    // errors on wrong-key decryption - they produce garbage. The authentication
1053    // layer (HMAC) is what detects tampering/wrong keys in practice (RFC 3414).
1054    // ========================================================================
1055
1056    #[test]
1057    fn test_des_wrong_key_produces_garbage() {
1058        // Correct 16-byte key
1059        let correct_key = vec![
1060            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1061            0x17, 0x18,
1062        ];
1063        // Wrong key (different from correct key)
1064        let wrong_key = vec![
1065            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
1066            0xE1, 0xE0,
1067        ];
1068
1069        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key);
1070        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key);
1071
1072        let plaintext = b"Secret SNMPv3 message data!";
1073        let engine_boots = 100u32;
1074        let engine_time = 12345u32;
1075
1076        // Encrypt with correct key
1077        let (ciphertext, priv_params) = correct_priv_key
1078            .encrypt(plaintext, engine_boots, engine_time, None)
1079            .expect("encryption failed");
1080
1081        // Decrypt with wrong key - this will "succeed" but produce garbage
1082        let wrong_decrypted = wrong_priv_key
1083            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1084            .expect("decryption should succeed cryptographically");
1085
1086        // Verify wrong key produces different output (not the original plaintext)
1087        assert_ne!(
1088            &wrong_decrypted[..plaintext.len()],
1089            plaintext,
1090            "wrong key should NOT produce the original plaintext"
1091        );
1092
1093        // Verify correct key still works
1094        let correct_decrypted = correct_priv_key
1095            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1096            .expect("correct key decryption failed");
1097        assert_eq!(
1098            &correct_decrypted[..plaintext.len()],
1099            plaintext,
1100            "correct key should produce the original plaintext"
1101        );
1102    }
1103
1104    #[test]
1105    fn test_aes128_wrong_key_produces_garbage() {
1106        let correct_key = vec![
1107            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1108            0x0f, 0x10,
1109        ];
1110        let wrong_key = vec![
1111            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1112            0xF1, 0xF0,
1113        ];
1114
1115        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key);
1116        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key);
1117
1118        let plaintext = b"Secret AES-128 message data!";
1119        let engine_boots = 200u32;
1120        let engine_time = 54321u32;
1121
1122        // Encrypt with correct key
1123        let (ciphertext, priv_params) = correct_priv_key
1124            .encrypt(plaintext, engine_boots, engine_time, None)
1125            .expect("encryption failed");
1126
1127        // Decrypt with wrong key
1128        let wrong_decrypted = wrong_priv_key
1129            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1130            .expect("decryption should succeed cryptographically");
1131
1132        // Wrong key should produce garbage (not the original plaintext)
1133        assert_ne!(
1134            wrong_decrypted.as_ref(),
1135            plaintext,
1136            "wrong key should NOT produce the original plaintext"
1137        );
1138
1139        // Correct key should work
1140        let correct_decrypted = correct_priv_key
1141            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1142            .expect("correct key decryption failed");
1143        assert_eq!(correct_decrypted.as_ref(), plaintext);
1144    }
1145
1146    #[test]
1147    fn test_aes192_wrong_key_produces_garbage() {
1148        let correct_key = vec![
1149            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1150            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
1151        ];
1152        let wrong_key = vec![
1153            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1154            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
1155        ];
1156
1157        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key);
1158        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key);
1159
1160        let plaintext = b"Secret AES-192 message data!";
1161        let engine_boots = 300u32;
1162        let engine_time = 67890u32;
1163
1164        let (ciphertext, priv_params) = correct_priv_key
1165            .encrypt(plaintext, engine_boots, engine_time, None)
1166            .expect("encryption failed");
1167
1168        let wrong_decrypted = wrong_priv_key
1169            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1170            .expect("decryption should succeed cryptographically");
1171
1172        assert_ne!(
1173            wrong_decrypted.as_ref(),
1174            plaintext,
1175            "wrong key should NOT produce the original plaintext"
1176        );
1177    }
1178
1179    #[test]
1180    fn test_aes256_wrong_key_produces_garbage() {
1181        let correct_key = vec![
1182            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1183            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1184            0x1d, 0x1e, 0x1f, 0x20,
1185        ];
1186        let wrong_key = vec![
1187            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1188            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
1189            0xE3, 0xE2, 0xE1, 0xE0,
1190        ];
1191
1192        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key);
1193        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key);
1194
1195        let plaintext = b"Secret AES-256 message data!";
1196        let engine_boots = 400u32;
1197        let engine_time = 11111u32;
1198
1199        let (ciphertext, priv_params) = correct_priv_key
1200            .encrypt(plaintext, engine_boots, engine_time, None)
1201            .expect("encryption failed");
1202
1203        let wrong_decrypted = wrong_priv_key
1204            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1205            .expect("decryption should succeed cryptographically");
1206
1207        assert_ne!(
1208            wrong_decrypted.as_ref(),
1209            plaintext,
1210            "wrong key should NOT produce the original plaintext"
1211        );
1212    }
1213
1214    #[test]
1215    fn test_des_wrong_priv_params_produces_garbage() {
1216        // Verify that even with the correct key, wrong priv_params (salt/IV)
1217        // produces garbage. This tests the IV derivation logic.
1218        let key = vec![
1219            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1220            0x17, 0x18,
1221        ];
1222
1223        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
1224
1225        let plaintext = b"DES test message";
1226        let engine_boots = 100u32;
1227        let engine_time = 12345u32;
1228
1229        let (ciphertext, correct_priv_params) = priv_key
1230            .encrypt(plaintext, engine_boots, engine_time, None)
1231            .expect("encryption failed");
1232
1233        // Use wrong priv_params (different salt)
1234        let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
1235
1236        let wrong_decrypted = priv_key
1237            .decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
1238            .expect("decryption should succeed cryptographically");
1239
1240        // Wrong IV should produce garbage
1241        assert_ne!(
1242            &wrong_decrypted[..plaintext.len()],
1243            plaintext,
1244            "wrong priv_params should NOT produce the original plaintext"
1245        );
1246
1247        // Correct priv_params should work
1248        let correct_decrypted = priv_key
1249            .decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
1250            .expect("correct decryption failed");
1251        assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
1252    }
1253
1254    #[test]
1255    fn test_aes_wrong_engine_time_produces_garbage() {
1256        // For AES, the IV includes engine_boots and engine_time.
1257        // Wrong values should produce garbage.
1258        let key = vec![
1259            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1260            0x0f, 0x10,
1261        ];
1262
1263        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
1264
1265        let plaintext = b"AES test message";
1266        let engine_boots = 200u32;
1267        let engine_time = 54321u32;
1268
1269        let (ciphertext, priv_params) = priv_key
1270            .encrypt(plaintext, engine_boots, engine_time, None)
1271            .expect("encryption failed");
1272
1273        // Decrypt with wrong engine_time (IV mismatch)
1274        let wrong_decrypted = priv_key
1275            .decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
1276            .expect("decryption should succeed cryptographically");
1277
1278        assert_ne!(
1279            wrong_decrypted.as_ref(),
1280            plaintext,
1281            "wrong engine_time should NOT produce the original plaintext"
1282        );
1283
1284        // Decrypt with wrong engine_boots (IV mismatch)
1285        let wrong_decrypted2 = priv_key
1286            .decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
1287            .expect("decryption should succeed cryptographically");
1288
1289        assert_ne!(
1290            wrong_decrypted2.as_ref(),
1291            plaintext,
1292            "wrong engine_boots should NOT produce the original plaintext"
1293        );
1294    }
1295}