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};
26use crate::error::{CryptoErrorKind, Error, Result};
27
28/// Privacy key for encryption/decryption operations.
29///
30/// Derives encryption keys from a password and engine ID using the same
31/// process as authentication keys, then uses the appropriate portion
32/// based on the privacy protocol.
33///
34/// # Security
35///
36/// Key material is automatically zeroed from memory when the key is dropped,
37/// using the `zeroize` crate. This provides defense-in-depth against memory
38/// scraping attacks.
39#[derive(Clone, Zeroize, ZeroizeOnDrop)]
40pub struct PrivKey {
41    /// The localized key bytes
42    key: Vec<u8>,
43    /// Privacy protocol
44    #[zeroize(skip)]
45    protocol: PrivProtocol,
46    /// Salt counter for generating unique IVs
47    /// For thread safety, each PrivKey instance gets its own counter
48    #[zeroize(skip)]
49    salt_counter: u64,
50}
51
52/// Thread-safe salt counter for shared use across multiple encryptions.
53pub struct SaltCounter(AtomicU64);
54
55impl SaltCounter {
56    /// Create a new salt counter initialized to a pseudo-random value.
57    pub fn new() -> Self {
58        // Initialize with current time for pseudo-randomness
59        let init = std::time::SystemTime::now()
60            .duration_since(std::time::UNIX_EPOCH)
61            .map(|d| d.as_nanos() as u64)
62            .unwrap_or(0);
63        Self(AtomicU64::new(init))
64    }
65
66    /// Get the next salt value and increment the counter.
67    pub fn next(&self) -> u64 {
68        self.0.fetch_add(1, Ordering::SeqCst)
69    }
70}
71
72impl Default for SaltCounter {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl PrivKey {
79    /// Derive a privacy key from a password and engine ID.
80    ///
81    /// The key derivation uses the same algorithm as authentication keys
82    /// (RFC 3414 A.2), but the resulting key is used differently:
83    /// - DES: first 8 bytes = key, last 8 bytes = pre-IV
84    /// - AES: first 16/24/32 bytes = key (depending on AES variant)
85    ///
86    /// # Performance Note
87    ///
88    /// This method performs the full key derivation (~850μs for SHA-256). When
89    /// polling many engines with shared credentials, use [`MasterKey`](super::MasterKey)
90    /// and call [`PrivKey::from_master_key`] for each engine.
91    ///
92    /// # Auth/Priv Protocol Compatibility
93    ///
94    /// The authentication protocol must produce sufficient key material for
95    /// the privacy protocol. If not, a warning is logged and the key will
96    /// be shorter than required, leading to runtime panics during encryption.
97    ///
98    /// Use [`AuthProtocol::is_compatible_with`] to check compatibility:
99    ///
100    /// | Privacy Protocol | Required Auth Protocols |
101    /// |------------------|-------------------------|
102    /// | DES, AES-128     | Any (MD5+)             |
103    /// | AES-192          | SHA-224+               |
104    /// | AES-256          | SHA-256+               |
105    ///
106    /// # Panics
107    ///
108    /// Panics during encryption if the privacy protocol requires a longer key
109    /// than the authentication protocol provides.
110    pub fn from_password(
111        auth_protocol: AuthProtocol,
112        priv_protocol: PrivProtocol,
113        password: &[u8],
114        engine_id: &[u8],
115    ) -> Self {
116        use super::MasterKey;
117
118        let master = MasterKey::from_password(auth_protocol, password);
119        Self::from_master_key(&master, priv_protocol, engine_id)
120    }
121
122    /// Derive a privacy key from a master key and engine ID.
123    ///
124    /// This is the efficient path when you have a cached [`MasterKey`](super::MasterKey).
125    /// The master key's auth protocol must be compatible with the privacy protocol.
126    ///
127    /// # Auth/Priv Protocol Compatibility
128    ///
129    /// The authentication protocol used for the master key must produce sufficient
130    /// key material for the privacy protocol. See [`AuthProtocol::is_compatible_with`].
131    ///
132    /// # Panics
133    ///
134    /// Panics during encryption if the privacy protocol requires a longer key
135    /// than the authentication protocol provides.
136    pub fn from_master_key(
137        master: &super::MasterKey,
138        priv_protocol: PrivProtocol,
139        engine_id: &[u8],
140    ) -> Self {
141        let auth_protocol = master.protocol();
142
143        // Check auth/priv protocol compatibility
144        if !auth_protocol.is_compatible_with(priv_protocol) {
145            tracing::warn!(
146                auth_protocol = ?auth_protocol,
147                priv_protocol = ?priv_protocol,
148                auth_key_len = auth_protocol.digest_len(),
149                required_key_len = priv_protocol.key_len(),
150                "authentication protocol produces insufficient key material for privacy protocol; \
151                 use SHA-224+ for AES-192, SHA-256+ for AES-256"
152            );
153        }
154
155        // Localize the master key (per RFC 3826 Section 1.2)
156        let localized = master.localize(engine_id);
157        let key = localized.as_bytes().to_vec();
158
159        Self {
160            key,
161            protocol: priv_protocol,
162            salt_counter: Self::init_salt(),
163        }
164    }
165
166    /// Create a privacy key from raw localized key bytes.
167    pub fn from_bytes(protocol: PrivProtocol, key: impl Into<Vec<u8>>) -> Self {
168        Self {
169            key: key.into(),
170            protocol,
171            salt_counter: Self::init_salt(),
172        }
173    }
174
175    /// Initialize salt with pseudo-random value.
176    fn init_salt() -> u64 {
177        std::time::SystemTime::now()
178            .duration_since(std::time::UNIX_EPOCH)
179            .map(|d| d.as_nanos() as u64)
180            .unwrap_or(0)
181    }
182
183    /// Get the privacy protocol.
184    pub fn protocol(&self) -> PrivProtocol {
185        self.protocol
186    }
187
188    /// Get the encryption key portion.
189    pub fn encryption_key(&self) -> &[u8] {
190        match self.protocol {
191            PrivProtocol::Des => &self.key[..8],
192            PrivProtocol::Aes128 => &self.key[..16],
193            PrivProtocol::Aes192 => &self.key[..24],
194            PrivProtocol::Aes256 => &self.key[..32],
195        }
196    }
197
198    /// Encrypt data and return (ciphertext, privParameters).
199    ///
200    /// # Arguments
201    /// * `plaintext` - The data to encrypt (typically the serialized ScopedPDU)
202    /// * `engine_boots` - The authoritative engine's boot count
203    /// * `engine_time` - The authoritative engine's time
204    /// * `salt_counter` - Optional shared salt counter; if None, uses internal counter
205    ///
206    /// # Returns
207    /// * `Ok((ciphertext, priv_params))` on success
208    /// * `Err` on encryption failure
209    pub fn encrypt(
210        &mut self,
211        plaintext: &[u8],
212        engine_boots: u32,
213        engine_time: u32,
214        salt_counter: Option<&SaltCounter>,
215    ) -> Result<(Bytes, Bytes)> {
216        let salt = salt_counter.map(|c| c.next()).unwrap_or_else(|| {
217            let s = self.salt_counter;
218            self.salt_counter = self.salt_counter.wrapping_add(1);
219            s
220        });
221
222        match self.protocol {
223            PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
224            PrivProtocol::Aes128 => {
225                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
226            }
227            PrivProtocol::Aes192 => {
228                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
229            }
230            PrivProtocol::Aes256 => {
231                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
232            }
233        }
234    }
235
236    /// Decrypt data using the privParameters from the message.
237    ///
238    /// # Arguments
239    /// * `ciphertext` - The encrypted data
240    /// * `engine_boots` - The authoritative engine's boot count (from message)
241    /// * `engine_time` - The authoritative engine's time (from message)
242    /// * `priv_params` - The privParameters field from the message
243    ///
244    /// # Returns
245    /// * `Ok(plaintext)` on success
246    /// * `Err` on decryption failure
247    pub fn decrypt(
248        &self,
249        ciphertext: &[u8],
250        engine_boots: u32,
251        engine_time: u32,
252        priv_params: &[u8],
253    ) -> Result<Bytes> {
254        if priv_params.len() != 8 {
255            return Err(Error::decrypt(
256                None,
257                CryptoErrorKind::InvalidPrivParamsLength {
258                    expected: 8,
259                    actual: priv_params.len(),
260                },
261            ));
262        }
263
264        match self.protocol {
265            PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
266            PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
267                self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
268            }
269        }
270    }
271
272    /// DES-CBC encryption (RFC 3414 Section 8.1.1).
273    fn encrypt_des(
274        &self,
275        plaintext: &[u8],
276        engine_boots: u32,
277        salt_int: u64,
278    ) -> Result<(Bytes, Bytes)> {
279        use cbc::cipher::{BlockEncryptMut, KeyIvInit};
280        type DesCbc = cbc::Encryptor<des::Des>;
281
282        // DES key is first 8 bytes
283        let key = &self.key[..8];
284        // Pre-IV is last 8 bytes of 16-byte privKey
285        let pre_iv = &self.key[8..16];
286
287        // Salt = engineBoots (4 bytes MSB) || counter (4 bytes MSB)
288        // We use the lower 32 bits of salt_int as the counter
289        let mut salt = [0u8; 8];
290        salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
291        salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
292
293        // IV = pre-IV XOR salt
294        let mut iv = [0u8; 8];
295        for i in 0..8 {
296            iv[i] = pre_iv[i] ^ salt[i];
297        }
298
299        // Pad plaintext to multiple of 8 bytes
300        let pad_len = (8 - (plaintext.len() % 8)) % 8;
301        let padded_len = plaintext.len() + if pad_len == 0 { 0 } else { pad_len };
302        // DES requires at least some padding if not aligned
303        let padded_len = if padded_len == plaintext.len() && !plaintext.len().is_multiple_of(8) {
304            plaintext.len() + (8 - plaintext.len() % 8)
305        } else {
306            padded_len
307        };
308
309        let mut buffer = vec![
310            0u8;
311            if padded_len > plaintext.len() {
312                padded_len
313            } else {
314                plaintext.len() + 8 - (plaintext.len() % 8)
315            }
316        ];
317        let padded_len = buffer.len();
318        buffer[..plaintext.len()].copy_from_slice(plaintext);
319
320        // Encrypt in-place
321        let cipher = DesCbc::new_from_slices(key, &iv)
322            .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
323
324        let ciphertext = cipher
325            .encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, padded_len)
326            .map_err(|_| Error::encrypt(None, CryptoErrorKind::CipherError))?;
327
328        Ok((
329            Bytes::copy_from_slice(ciphertext),
330            Bytes::copy_from_slice(&salt),
331        ))
332    }
333
334    /// DES-CBC decryption (RFC 3414 Section 8.1.1).
335    fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> Result<Bytes> {
336        use cbc::cipher::{BlockDecryptMut, KeyIvInit};
337        type DesCbc = cbc::Decryptor<des::Des>;
338
339        if !ciphertext.len().is_multiple_of(8) {
340            return Err(Error::decrypt(
341                None,
342                CryptoErrorKind::InvalidCiphertextLength {
343                    length: ciphertext.len(),
344                    block_size: 8,
345                },
346            ));
347        }
348
349        // DES key is first 8 bytes
350        let key = &self.key[..8];
351        // Pre-IV is last 8 bytes of 16-byte privKey
352        let pre_iv = &self.key[8..16];
353
354        // Salt is the privParameters
355        let salt = priv_params;
356
357        // IV = pre-IV XOR salt
358        let mut iv = [0u8; 8];
359        for i in 0..8 {
360            iv[i] = pre_iv[i] ^ salt[i];
361        }
362
363        // Decrypt
364        let cipher = DesCbc::new_from_slices(key, &iv)
365            .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
366
367        let mut buffer = ciphertext.to_vec();
368        let plaintext = cipher
369            .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
370            .map_err(|_| Error::decrypt(None, CryptoErrorKind::CipherError))?;
371
372        Ok(Bytes::copy_from_slice(plaintext))
373    }
374
375    /// AES-CFB encryption (RFC 3826 Section 3.1).
376    fn encrypt_aes(
377        &self,
378        plaintext: &[u8],
379        engine_boots: u32,
380        engine_time: u32,
381        salt: u64,
382        key_len: usize,
383    ) -> Result<(Bytes, Bytes)> {
384        use aes::{Aes128, Aes192, Aes256};
385        use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
386
387        // AES key is first key_len bytes
388        let key = &self.key[..key_len];
389
390        // Salt as 8 bytes (big-endian)
391        let salt_bytes = salt.to_be_bytes();
392
393        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
394        // This is CONCATENATION, not XOR (unlike DES)
395        let mut iv = [0u8; 16];
396        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
397        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
398        iv[8..].copy_from_slice(&salt_bytes);
399
400        let mut buffer = plaintext.to_vec();
401
402        match key_len {
403            16 => {
404                type Aes128Cfb = cfb_mode::Encryptor<Aes128>;
405                let cipher = Aes128Cfb::new_from_slices(key, &iv)
406                    .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
407                cipher.encrypt(&mut buffer);
408            }
409            24 => {
410                type Aes192Cfb = cfb_mode::Encryptor<Aes192>;
411                let cipher = Aes192Cfb::new_from_slices(key, &iv)
412                    .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
413                cipher.encrypt(&mut buffer);
414            }
415            32 => {
416                type Aes256Cfb = cfb_mode::Encryptor<Aes256>;
417                let cipher = Aes256Cfb::new_from_slices(key, &iv)
418                    .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
419                cipher.encrypt(&mut buffer);
420            }
421            _ => {
422                return Err(Error::encrypt(None, CryptoErrorKind::UnsupportedProtocol));
423            }
424        }
425
426        Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
427    }
428
429    /// AES-CFB decryption (RFC 3826 Section 3.1.4).
430    fn decrypt_aes(
431        &self,
432        ciphertext: &[u8],
433        engine_boots: u32,
434        engine_time: u32,
435        priv_params: &[u8],
436    ) -> Result<Bytes> {
437        use aes::{Aes128, Aes192, Aes256};
438        use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
439
440        let key_len = match self.protocol {
441            PrivProtocol::Aes128 => 16,
442            PrivProtocol::Aes192 => 24,
443            PrivProtocol::Aes256 => 32,
444            _ => unreachable!(),
445        };
446
447        // AES key is first key_len bytes
448        let key = &self.key[..key_len];
449
450        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
451        let mut iv = [0u8; 16];
452        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
453        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
454        iv[8..].copy_from_slice(priv_params);
455
456        let mut buffer = ciphertext.to_vec();
457
458        match key_len {
459            16 => {
460                type Aes128Cfb = cfb_mode::Decryptor<Aes128>;
461                let cipher = Aes128Cfb::new_from_slices(key, &iv)
462                    .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
463                cipher.decrypt(&mut buffer);
464            }
465            24 => {
466                type Aes192Cfb = cfb_mode::Decryptor<Aes192>;
467                let cipher = Aes192Cfb::new_from_slices(key, &iv)
468                    .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
469                cipher.decrypt(&mut buffer);
470            }
471            32 => {
472                type Aes256Cfb = cfb_mode::Decryptor<Aes256>;
473                let cipher = Aes256Cfb::new_from_slices(key, &iv)
474                    .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
475                cipher.decrypt(&mut buffer);
476            }
477            _ => {
478                return Err(Error::decrypt(None, CryptoErrorKind::UnsupportedProtocol));
479            }
480        }
481
482        Ok(Bytes::from(buffer))
483    }
484}
485
486impl std::fmt::Debug for PrivKey {
487    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488        f.debug_struct("PrivKey")
489            .field("protocol", &self.protocol)
490            .field("key", &"[REDACTED]")
491            .finish()
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498    use crate::format::hex::decode as decode_hex;
499
500    #[test]
501    fn test_des_encrypt_decrypt_roundtrip() {
502        // Create a 16-byte key (8 for DES, 8 for pre-IV)
503        let key = vec![
504            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DES key
505            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // pre-IV
506        ];
507        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
508
509        let plaintext = b"Hello, SNMPv3 World!";
510        let engine_boots = 100u32;
511        let engine_time = 12345u32;
512
513        let (ciphertext, priv_params) = priv_key
514            .encrypt(plaintext, engine_boots, engine_time, None)
515            .expect("encryption failed");
516
517        // Verify ciphertext is different from plaintext
518        assert_ne!(ciphertext.as_ref(), plaintext);
519        // Verify priv_params is 8 bytes
520        assert_eq!(priv_params.len(), 8);
521
522        // Decrypt
523        let decrypted = priv_key
524            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
525            .expect("decryption failed");
526
527        // DES pads to 8-byte boundary, so decrypted may be longer
528        assert!(decrypted.len() >= plaintext.len());
529        assert_eq!(&decrypted[..plaintext.len()], plaintext);
530    }
531
532    #[test]
533    fn test_aes128_encrypt_decrypt_roundtrip() {
534        // Create a 16-byte key for AES-128
535        let key = vec![
536            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
537            0x0f, 0x10,
538        ];
539        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
540
541        let plaintext = b"Hello, SNMPv3 AES World!";
542        let engine_boots = 200u32;
543        let engine_time = 54321u32;
544
545        let (ciphertext, priv_params) = priv_key
546            .encrypt(plaintext, engine_boots, engine_time, None)
547            .expect("encryption failed");
548
549        // Verify ciphertext is different from plaintext
550        assert_ne!(ciphertext.as_ref(), plaintext);
551        // Verify priv_params is 8 bytes (salt)
552        assert_eq!(priv_params.len(), 8);
553
554        // Decrypt
555        let decrypted = priv_key
556            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
557            .expect("decryption failed");
558
559        // AES-CFB doesn't require padding, so lengths should match
560        assert_eq!(decrypted.len(), plaintext.len());
561        assert_eq!(decrypted.as_ref(), plaintext);
562    }
563
564    #[test]
565    fn test_des_invalid_ciphertext_length() {
566        let key = vec![0u8; 16];
567        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
568
569        // Ciphertext not multiple of 8
570        let ciphertext = [0u8; 13];
571        let priv_params = [0u8; 8];
572
573        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
574        assert!(result.is_err());
575    }
576
577    #[test]
578    fn test_invalid_priv_params_length() {
579        let key = vec![0u8; 16];
580        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
581
582        // priv_params should be 8 bytes
583        let ciphertext = [0u8; 16];
584        let priv_params = [0u8; 4]; // Wrong length
585
586        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
587        assert!(result.is_err());
588    }
589
590    #[test]
591    fn test_salt_counter() {
592        let counter = SaltCounter::new();
593        let s1 = counter.next();
594        let s2 = counter.next();
595        let s3 = counter.next();
596
597        // Each call should increment
598        assert_eq!(s2, s1.wrapping_add(1));
599        assert_eq!(s3, s2.wrapping_add(1));
600    }
601
602    #[test]
603    fn test_multiple_encryptions_different_salt() {
604        let key = vec![0u8; 16];
605        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
606
607        let plaintext = b"test data";
608
609        let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
610        let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
611
612        // Salts should be different for each encryption
613        assert_ne!(salt1, salt2);
614    }
615
616    #[test]
617    fn test_from_password() {
618        // Test that we can derive a privacy key from a password
619        let password = b"maplesyrup";
620        let engine_id = decode_hex("000000000000000000000002").unwrap();
621
622        let mut priv_key = PrivKey::from_password(
623            AuthProtocol::Sha1,
624            PrivProtocol::Aes128,
625            password,
626            &engine_id,
627        );
628
629        // Just verify we can encrypt/decrypt with the derived key
630        let plaintext = b"test message";
631        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
632        let decrypted = priv_key
633            .decrypt(&ciphertext, 100, 200, &priv_params)
634            .unwrap();
635
636        assert_eq!(decrypted.as_ref(), plaintext);
637    }
638
639    #[test]
640    fn test_aes192_encrypt_decrypt_roundtrip() {
641        // Create a 24-byte key for AES-192
642        let key = vec![
643            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
644            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
645        ];
646        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key);
647
648        let plaintext = b"Hello, SNMPv3 AES-192 World!";
649        let engine_boots = 300u32;
650        let engine_time = 67890u32;
651
652        let (ciphertext, priv_params) = priv_key
653            .encrypt(plaintext, engine_boots, engine_time, None)
654            .expect("AES-192 encryption failed");
655
656        // Verify ciphertext is different from plaintext
657        assert_ne!(ciphertext.as_ref(), plaintext);
658        // Verify priv_params is 8 bytes (salt)
659        assert_eq!(priv_params.len(), 8);
660
661        // Decrypt
662        let decrypted = priv_key
663            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
664            .expect("AES-192 decryption failed");
665
666        // AES-CFB doesn't require padding, so lengths should match
667        assert_eq!(decrypted.len(), plaintext.len());
668        assert_eq!(decrypted.as_ref(), plaintext);
669    }
670
671    #[test]
672    fn test_aes256_encrypt_decrypt_roundtrip() {
673        // Create a 32-byte key for AES-256
674        let key = vec![
675            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
676            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
677            0x1d, 0x1e, 0x1f, 0x20,
678        ];
679        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key);
680
681        let plaintext = b"Hello, SNMPv3 AES-256 World!";
682        let engine_boots = 400u32;
683        let engine_time = 11111u32;
684
685        let (ciphertext, priv_params) = priv_key
686            .encrypt(plaintext, engine_boots, engine_time, None)
687            .expect("AES-256 encryption failed");
688
689        // Verify ciphertext is different from plaintext
690        assert_ne!(ciphertext.as_ref(), plaintext);
691        // Verify priv_params is 8 bytes (salt)
692        assert_eq!(priv_params.len(), 8);
693
694        // Decrypt
695        let decrypted = priv_key
696            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
697            .expect("AES-256 decryption failed");
698
699        // AES-CFB doesn't require padding, so lengths should match
700        assert_eq!(decrypted.len(), plaintext.len());
701        assert_eq!(decrypted.as_ref(), plaintext);
702    }
703
704    #[test]
705    fn test_aes192_from_password() {
706        // For AES-192 (24-byte key), we need SHA-224 or higher auth protocol
707        let password = b"longpassword123";
708        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
709
710        let mut priv_key = PrivKey::from_password(
711            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, enough for AES-192
712            PrivProtocol::Aes192,
713            password,
714            &engine_id,
715        );
716
717        let plaintext = b"test message for AES-192";
718        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
719        let decrypted = priv_key
720            .decrypt(&ciphertext, 100, 200, &priv_params)
721            .unwrap();
722
723        assert_eq!(decrypted.as_ref(), plaintext);
724    }
725
726    #[test]
727    fn test_aes256_from_password() {
728        // For AES-256 (32-byte key), we need SHA-256 or higher auth protocol
729        let password = b"anotherlongpassword456";
730        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
731
732        let mut priv_key = PrivKey::from_password(
733            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, exactly enough for AES-256
734            PrivProtocol::Aes256,
735            password,
736            &engine_id,
737        );
738
739        let plaintext = b"test message for AES-256";
740        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
741        let decrypted = priv_key
742            .decrypt(&ciphertext, 100, 200, &priv_params)
743            .unwrap();
744
745        assert_eq!(decrypted.as_ref(), plaintext);
746    }
747
748    // ========================================================================
749    // Wrong Key Decryption Tests
750    //
751    // These tests verify that decryption with the wrong key produces garbage,
752    // not the original plaintext. Note: Stream ciphers like AES-CFB don't return
753    // errors on wrong-key decryption - they produce garbage. The authentication
754    // layer (HMAC) is what detects tampering/wrong keys in practice (RFC 3414).
755    // ========================================================================
756
757    #[test]
758    fn test_des_wrong_key_produces_garbage() {
759        // Correct 16-byte key
760        let correct_key = vec![
761            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
762            0x17, 0x18,
763        ];
764        // Wrong key (different from correct key)
765        let wrong_key = vec![
766            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
767            0xE1, 0xE0,
768        ];
769
770        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key);
771        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key);
772
773        let plaintext = b"Secret SNMPv3 message data!";
774        let engine_boots = 100u32;
775        let engine_time = 12345u32;
776
777        // Encrypt with correct key
778        let (ciphertext, priv_params) = correct_priv_key
779            .encrypt(plaintext, engine_boots, engine_time, None)
780            .expect("encryption failed");
781
782        // Decrypt with wrong key - this will "succeed" but produce garbage
783        let wrong_decrypted = wrong_priv_key
784            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
785            .expect("decryption should succeed cryptographically");
786
787        // Verify wrong key produces different output (not the original plaintext)
788        assert_ne!(
789            &wrong_decrypted[..plaintext.len()],
790            plaintext,
791            "wrong key should NOT produce the original plaintext"
792        );
793
794        // Verify correct key still works
795        let correct_decrypted = correct_priv_key
796            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
797            .expect("correct key decryption failed");
798        assert_eq!(
799            &correct_decrypted[..plaintext.len()],
800            plaintext,
801            "correct key should produce the original plaintext"
802        );
803    }
804
805    #[test]
806    fn test_aes128_wrong_key_produces_garbage() {
807        let correct_key = vec![
808            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
809            0x0f, 0x10,
810        ];
811        let wrong_key = vec![
812            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
813            0xF1, 0xF0,
814        ];
815
816        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key);
817        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key);
818
819        let plaintext = b"Secret AES-128 message data!";
820        let engine_boots = 200u32;
821        let engine_time = 54321u32;
822
823        // Encrypt with correct key
824        let (ciphertext, priv_params) = correct_priv_key
825            .encrypt(plaintext, engine_boots, engine_time, None)
826            .expect("encryption failed");
827
828        // Decrypt with wrong key
829        let wrong_decrypted = wrong_priv_key
830            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
831            .expect("decryption should succeed cryptographically");
832
833        // Wrong key should produce garbage (not the original plaintext)
834        assert_ne!(
835            wrong_decrypted.as_ref(),
836            plaintext,
837            "wrong key should NOT produce the original plaintext"
838        );
839
840        // Correct key should work
841        let correct_decrypted = correct_priv_key
842            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
843            .expect("correct key decryption failed");
844        assert_eq!(correct_decrypted.as_ref(), plaintext);
845    }
846
847    #[test]
848    fn test_aes192_wrong_key_produces_garbage() {
849        let correct_key = vec![
850            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
851            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
852        ];
853        let wrong_key = vec![
854            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
855            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
856        ];
857
858        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key);
859        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key);
860
861        let plaintext = b"Secret AES-192 message data!";
862        let engine_boots = 300u32;
863        let engine_time = 67890u32;
864
865        let (ciphertext, priv_params) = correct_priv_key
866            .encrypt(plaintext, engine_boots, engine_time, None)
867            .expect("encryption failed");
868
869        let wrong_decrypted = wrong_priv_key
870            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
871            .expect("decryption should succeed cryptographically");
872
873        assert_ne!(
874            wrong_decrypted.as_ref(),
875            plaintext,
876            "wrong key should NOT produce the original plaintext"
877        );
878    }
879
880    #[test]
881    fn test_aes256_wrong_key_produces_garbage() {
882        let correct_key = vec![
883            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
884            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
885            0x1d, 0x1e, 0x1f, 0x20,
886        ];
887        let wrong_key = vec![
888            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
889            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
890            0xE3, 0xE2, 0xE1, 0xE0,
891        ];
892
893        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key);
894        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key);
895
896        let plaintext = b"Secret AES-256 message data!";
897        let engine_boots = 400u32;
898        let engine_time = 11111u32;
899
900        let (ciphertext, priv_params) = correct_priv_key
901            .encrypt(plaintext, engine_boots, engine_time, None)
902            .expect("encryption failed");
903
904        let wrong_decrypted = wrong_priv_key
905            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
906            .expect("decryption should succeed cryptographically");
907
908        assert_ne!(
909            wrong_decrypted.as_ref(),
910            plaintext,
911            "wrong key should NOT produce the original plaintext"
912        );
913    }
914
915    #[test]
916    fn test_des_wrong_priv_params_produces_garbage() {
917        // Verify that even with the correct key, wrong priv_params (salt/IV)
918        // produces garbage. This tests the IV derivation logic.
919        let key = vec![
920            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
921            0x17, 0x18,
922        ];
923
924        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
925
926        let plaintext = b"DES test message";
927        let engine_boots = 100u32;
928        let engine_time = 12345u32;
929
930        let (ciphertext, correct_priv_params) = priv_key
931            .encrypt(plaintext, engine_boots, engine_time, None)
932            .expect("encryption failed");
933
934        // Use wrong priv_params (different salt)
935        let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
936
937        let wrong_decrypted = priv_key
938            .decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
939            .expect("decryption should succeed cryptographically");
940
941        // Wrong IV should produce garbage
942        assert_ne!(
943            &wrong_decrypted[..plaintext.len()],
944            plaintext,
945            "wrong priv_params should NOT produce the original plaintext"
946        );
947
948        // Correct priv_params should work
949        let correct_decrypted = priv_key
950            .decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
951            .expect("correct decryption failed");
952        assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
953    }
954
955    #[test]
956    fn test_aes_wrong_engine_time_produces_garbage() {
957        // For AES, the IV includes engine_boots and engine_time.
958        // Wrong values should produce garbage.
959        let key = vec![
960            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
961            0x0f, 0x10,
962        ];
963
964        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
965
966        let plaintext = b"AES test message";
967        let engine_boots = 200u32;
968        let engine_time = 54321u32;
969
970        let (ciphertext, priv_params) = priv_key
971            .encrypt(plaintext, engine_boots, engine_time, None)
972            .expect("encryption failed");
973
974        // Decrypt with wrong engine_time (IV mismatch)
975        let wrong_decrypted = priv_key
976            .decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
977            .expect("decryption should succeed cryptographically");
978
979        assert_ne!(
980            wrong_decrypted.as_ref(),
981            plaintext,
982            "wrong engine_time should NOT produce the original plaintext"
983        );
984
985        // Decrypt with wrong engine_boots (IV mismatch)
986        let wrong_decrypted2 = priv_key
987            .decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
988            .expect("decryption should succeed cryptographically");
989
990        assert_ne!(
991            wrong_decrypted2.as_ref(),
992            plaintext,
993            "wrong engine_boots should NOT produce the original plaintext"
994        );
995    }
996}