1use digest::{Digest, KeyInit, Mac, OutputSizeUser, core_api::BlockSizeUser};
34use zeroize::{Zeroize, ZeroizeOnDrop};
35
36use super::AuthProtocol;
37
38pub const MIN_PASSWORD_LENGTH: usize = 8;
44
45#[derive(Clone, Zeroize, ZeroizeOnDrop)]
83pub struct MasterKey {
84 key: Vec<u8>,
85 #[zeroize(skip)]
86 protocol: AuthProtocol,
87}
88
89impl MasterKey {
90 pub fn from_password(protocol: AuthProtocol, password: &[u8]) -> Self {
101 if password.len() < MIN_PASSWORD_LENGTH {
102 tracing::warn!(target: "async_snmp::v3", { password_len = password.len(), min_len = MIN_PASSWORD_LENGTH }, "SNMPv3 password is shorter than recommended minimum; \
103 net-snmp rejects passwords shorter than 8 characters");
104 }
105 let key = password_to_key(protocol, password);
106 Self { key, protocol }
107 }
108
109 pub fn from_str_password(protocol: AuthProtocol, password: &str) -> Self {
111 Self::from_password(protocol, password.as_bytes())
112 }
113
114 pub fn from_bytes(protocol: AuthProtocol, key: impl Into<Vec<u8>>) -> Self {
119 Self {
120 key: key.into(),
121 protocol,
122 }
123 }
124
125 pub fn localize(&self, engine_id: &[u8]) -> LocalizedKey {
132 let localized = localize_key(self.protocol, &self.key, engine_id);
133 LocalizedKey {
134 key: localized,
135 protocol: self.protocol,
136 }
137 }
138
139 pub fn protocol(&self) -> AuthProtocol {
141 self.protocol
142 }
143
144 pub fn as_bytes(&self) -> &[u8] {
146 &self.key
147 }
148}
149
150impl std::fmt::Debug for MasterKey {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.debug_struct("MasterKey")
153 .field("protocol", &self.protocol)
154 .field("key", &"[REDACTED]")
155 .finish()
156 }
157}
158
159#[derive(Clone, Zeroize, ZeroizeOnDrop)]
170pub struct LocalizedKey {
171 key: Vec<u8>,
172 #[zeroize(skip)]
173 protocol: AuthProtocol,
174}
175
176impl LocalizedKey {
177 pub fn from_password(protocol: AuthProtocol, password: &[u8], engine_id: &[u8]) -> Self {
200 MasterKey::from_password(protocol, password).localize(engine_id)
201 }
202
203 pub fn from_str_password(protocol: AuthProtocol, password: &str, engine_id: &[u8]) -> Self {
208 Self::from_password(protocol, password.as_bytes(), engine_id)
209 }
210
211 pub fn from_master_key(master: &MasterKey, engine_id: &[u8]) -> Self {
216 master.localize(engine_id)
217 }
218
219 pub fn from_bytes(protocol: AuthProtocol, key: impl Into<Vec<u8>>) -> Self {
223 Self {
224 key: key.into(),
225 protocol,
226 }
227 }
228
229 pub fn protocol(&self) -> AuthProtocol {
231 self.protocol
232 }
233
234 pub fn as_bytes(&self) -> &[u8] {
236 &self.key
237 }
238
239 pub fn mac_len(&self) -> usize {
241 self.protocol.mac_len()
242 }
243
244 pub fn compute_hmac(&self, data: &[u8]) -> Vec<u8> {
249 compute_hmac(self.protocol, &self.key, data)
250 }
251
252 pub fn verify_hmac(&self, data: &[u8], expected: &[u8]) -> bool {
256 let computed = self.compute_hmac(data);
257 if computed.len() != expected.len() {
259 return false;
260 }
261 let mut result = 0u8;
262 for (a, b) in computed.iter().zip(expected.iter()) {
263 result |= a ^ b;
264 }
265 result == 0
266 }
267}
268
269impl std::fmt::Debug for LocalizedKey {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 f.debug_struct("LocalizedKey")
272 .field("protocol", &self.protocol)
273 .field("key", &"[REDACTED]")
274 .finish()
275 }
276}
277
278fn password_to_key(protocol: AuthProtocol, password: &[u8]) -> Vec<u8> {
282 const EXPANSION_SIZE: usize = 1_048_576; match protocol {
285 AuthProtocol::Md5 => password_to_key_impl::<md5::Md5>(password, EXPANSION_SIZE),
286 AuthProtocol::Sha1 => password_to_key_impl::<sha1::Sha1>(password, EXPANSION_SIZE),
287 AuthProtocol::Sha224 => password_to_key_impl::<sha2::Sha224>(password, EXPANSION_SIZE),
288 AuthProtocol::Sha256 => password_to_key_impl::<sha2::Sha256>(password, EXPANSION_SIZE),
289 AuthProtocol::Sha384 => password_to_key_impl::<sha2::Sha384>(password, EXPANSION_SIZE),
290 AuthProtocol::Sha512 => password_to_key_impl::<sha2::Sha512>(password, EXPANSION_SIZE),
291 }
292}
293
294fn password_to_key_impl<D>(password: &[u8], expansion_size: usize) -> Vec<u8>
295where
296 D: Digest + Default,
297{
298 if password.is_empty() {
299 return vec![0u8; <D as OutputSizeUser>::output_size()];
301 }
302
303 let mut hasher = D::new();
304
305 let mut buf = [0u8; 64];
308 let password_len = password.len();
309 let mut password_index = 0;
310 let mut count = 0;
311
312 while count < expansion_size {
313 for byte in &mut buf {
315 *byte = password[password_index];
316 password_index = (password_index + 1) % password_len;
317 }
318 hasher.update(buf);
319 count += 64;
320 }
321
322 hasher.finalize().to_vec()
323}
324
325fn localize_key(protocol: AuthProtocol, master_key: &[u8], engine_id: &[u8]) -> Vec<u8> {
330 match protocol {
331 AuthProtocol::Md5 => localize_key_impl::<md5::Md5>(master_key, engine_id),
332 AuthProtocol::Sha1 => localize_key_impl::<sha1::Sha1>(master_key, engine_id),
333 AuthProtocol::Sha224 => localize_key_impl::<sha2::Sha224>(master_key, engine_id),
334 AuthProtocol::Sha256 => localize_key_impl::<sha2::Sha256>(master_key, engine_id),
335 AuthProtocol::Sha384 => localize_key_impl::<sha2::Sha384>(master_key, engine_id),
336 AuthProtocol::Sha512 => localize_key_impl::<sha2::Sha512>(master_key, engine_id),
337 }
338}
339
340fn localize_key_impl<D>(master_key: &[u8], engine_id: &[u8]) -> Vec<u8>
341where
342 D: Digest + Default,
343{
344 let mut hasher = D::new();
345 hasher.update(master_key);
346 hasher.update(engine_id);
347 hasher.update(master_key);
348 hasher.finalize().to_vec()
349}
350
351fn compute_hmac(protocol: AuthProtocol, key: &[u8], data: &[u8]) -> Vec<u8> {
353 match protocol {
354 AuthProtocol::Md5 => compute_hmac_impl::<md5::Md5>(key, data, 12),
355 AuthProtocol::Sha1 => compute_hmac_impl::<sha1::Sha1>(key, data, 12),
356 AuthProtocol::Sha224 => compute_hmac_impl::<sha2::Sha224>(key, data, 16),
357 AuthProtocol::Sha256 => compute_hmac_impl::<sha2::Sha256>(key, data, 24),
358 AuthProtocol::Sha384 => compute_hmac_impl::<sha2::Sha384>(key, data, 32),
359 AuthProtocol::Sha512 => compute_hmac_impl::<sha2::Sha512>(key, data, 48),
360 }
361}
362
363fn compute_hmac_impl<D>(key: &[u8], data: &[u8], truncate_len: usize) -> Vec<u8>
365where
366 D: Digest + BlockSizeUser + Clone,
367{
368 use hmac::SimpleHmac;
369
370 let mut mac =
371 <SimpleHmac<D> as KeyInit>::new_from_slice(key).expect("HMAC can take key of any size");
372 Mac::update(&mut mac, data);
373 let result = mac.finalize().into_bytes();
374 result[..truncate_len].to_vec()
375}
376
377pub fn authenticate_message(
383 key: &LocalizedKey,
384 message: &mut [u8],
385 auth_offset: usize,
386 auth_len: usize,
387) {
388 let mac = key.compute_hmac(message);
390
391 message[auth_offset..auth_offset + auth_len].copy_from_slice(&mac);
393}
394
395pub fn verify_message(
399 key: &LocalizedKey,
400 message: &[u8],
401 auth_offset: usize,
402 auth_len: usize,
403) -> bool {
404 let received_mac = &message[auth_offset..auth_offset + auth_len];
406
407 let mut msg_copy = message.to_vec();
409 msg_copy[auth_offset..auth_offset + auth_len].fill(0);
410
411 key.verify_hmac(&msg_copy, received_mac)
413}
414
415#[derive(Clone, Zeroize, ZeroizeOnDrop)]
435pub struct MasterKeys {
436 auth_master: MasterKey,
438 #[zeroize(skip)]
441 priv_protocol: Option<super::PrivProtocol>,
442 priv_master: Option<MasterKey>,
443}
444
445impl MasterKeys {
446 pub fn new(auth_protocol: AuthProtocol, auth_password: &[u8]) -> Self {
456 Self {
457 auth_master: MasterKey::from_password(auth_protocol, auth_password),
458 priv_protocol: None,
459 priv_master: None,
460 }
461 }
462
463 pub fn with_privacy_same_password(mut self, priv_protocol: super::PrivProtocol) -> Self {
468 self.priv_protocol = Some(priv_protocol);
469 self
471 }
472
473 pub fn with_privacy(
478 mut self,
479 priv_protocol: super::PrivProtocol,
480 priv_password: &[u8],
481 ) -> Self {
482 self.priv_protocol = Some(priv_protocol);
483 self.priv_master = Some(MasterKey::from_password(
485 self.auth_master.protocol(),
486 priv_password,
487 ));
488 self
489 }
490
491 pub fn auth_master(&self) -> &MasterKey {
493 &self.auth_master
494 }
495
496 pub fn priv_master(&self) -> Option<&MasterKey> {
501 if self.priv_protocol.is_some() {
502 Some(self.priv_master.as_ref().unwrap_or(&self.auth_master))
503 } else {
504 None
505 }
506 }
507
508 pub fn priv_protocol(&self) -> Option<super::PrivProtocol> {
510 self.priv_protocol
511 }
512
513 pub fn auth_protocol(&self) -> AuthProtocol {
515 self.auth_master.protocol()
516 }
517
518 pub fn localize(&self, engine_id: &[u8]) -> (LocalizedKey, Option<crate::v3::PrivKey>) {
544 let auth_key = self.auth_master.localize(engine_id);
545
546 let priv_key = self.priv_protocol.map(|priv_protocol| {
547 let master = self.priv_master.as_ref().unwrap_or(&self.auth_master);
548 crate::v3::PrivKey::from_master_key(master, priv_protocol, engine_id)
549 });
550
551 (auth_key, priv_key)
552 }
553}
554
555impl std::fmt::Debug for MasterKeys {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 f.debug_struct("MasterKeys")
558 .field("auth_protocol", &self.auth_master.protocol())
559 .field("priv_protocol", &self.priv_protocol)
560 .field("has_separate_priv_password", &self.priv_master.is_some())
561 .finish()
562 }
563}
564
565pub(crate) fn extend_key(protocol: AuthProtocol, key: &[u8], target_len: usize) -> Vec<u8> {
578 if key.len() >= target_len {
580 return key[..target_len].to_vec();
581 }
582
583 match protocol {
584 AuthProtocol::Md5 => extend_key_impl::<md5::Md5>(key, target_len),
585 AuthProtocol::Sha1 => extend_key_impl::<sha1::Sha1>(key, target_len),
586 AuthProtocol::Sha224 => extend_key_impl::<sha2::Sha224>(key, target_len),
587 AuthProtocol::Sha256 => extend_key_impl::<sha2::Sha256>(key, target_len),
588 AuthProtocol::Sha384 => extend_key_impl::<sha2::Sha384>(key, target_len),
589 AuthProtocol::Sha512 => extend_key_impl::<sha2::Sha512>(key, target_len),
590 }
591}
592
593fn extend_key_impl<D>(key: &[u8], target_len: usize) -> Vec<u8>
597where
598 D: Digest + Default,
599{
600 let mut result = key.to_vec();
601
602 while result.len() < target_len {
604 let mut hasher = D::new();
605 hasher.update(&result);
606 let hash = hasher.finalize();
607 result.extend_from_slice(&hash);
608 }
609
610 result.truncate(target_len);
612 result
613}
614
615pub(crate) fn extend_key_reeder(
635 protocol: AuthProtocol,
636 key: &[u8],
637 engine_id: &[u8],
638 target_len: usize,
639) -> Vec<u8> {
640 if key.len() >= target_len {
642 return key[..target_len].to_vec();
643 }
644
645 let mut result = key.to_vec();
646 let mut current_kul = key.to_vec();
647
648 while result.len() < target_len {
650 let ku = password_to_key(protocol, ¤t_kul);
653
654 let new_kul = localize_key(protocol, &ku, engine_id);
656
657 let bytes_needed = target_len - result.len();
659 let bytes_to_copy = bytes_needed.min(new_kul.len());
660 result.extend_from_slice(&new_kul[..bytes_to_copy]);
661
662 current_kul = new_kul;
664 }
665
666 result
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672 use crate::format::hex::{decode as decode_hex, encode as encode_hex};
673
674 #[test]
675 fn test_password_to_key_md5() {
676 let password = b"maplesyrup";
680 let key = password_to_key(AuthProtocol::Md5, password);
681
682 assert_eq!(key.len(), 16);
683 assert_eq!(encode_hex(&key), "9faf3283884e92834ebc9847d8edd963");
684 }
685
686 #[test]
687 fn test_password_to_key_sha1() {
688 let password = b"maplesyrup";
692 let key = password_to_key(AuthProtocol::Sha1, password);
693
694 assert_eq!(key.len(), 20);
695 assert_eq!(encode_hex(&key), "9fb5cc0381497b3793528939ff788d5d79145211");
696 }
697
698 #[test]
699 fn test_localize_key_md5() {
700 let password = b"maplesyrup";
705 let engine_id = decode_hex("000000000000000000000002").unwrap();
706
707 let key = LocalizedKey::from_password(AuthProtocol::Md5, password, &engine_id);
708
709 assert_eq!(key.as_bytes().len(), 16);
710 assert_eq!(
711 encode_hex(key.as_bytes()),
712 "526f5eed9fcce26f8964c2930787d82b"
713 );
714 }
715
716 #[test]
717 fn test_localize_key_sha1() {
718 let password = b"maplesyrup";
722 let engine_id = decode_hex("000000000000000000000002").unwrap();
723
724 let key = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id);
725
726 assert_eq!(key.as_bytes().len(), 20);
727 assert_eq!(
728 encode_hex(key.as_bytes()),
729 "6695febc9288e36282235fc7151f128497b38f3f"
730 );
731 }
732
733 #[test]
734 fn test_hmac_computation() {
735 let key = LocalizedKey::from_bytes(
736 AuthProtocol::Md5,
737 vec![
738 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
739 0x0f, 0x10,
740 ],
741 );
742
743 let data = b"test message";
744 let mac = key.compute_hmac(data);
745
746 assert_eq!(mac.len(), 12);
748
749 assert!(key.verify_hmac(data, &mac));
751
752 let mut wrong_mac = mac.clone();
754 wrong_mac[0] ^= 0xFF;
755 assert!(!key.verify_hmac(data, &wrong_mac));
756 }
757
758 #[test]
759 fn test_empty_password() {
760 let key = password_to_key(AuthProtocol::Md5, b"");
761 assert_eq!(key.len(), 16);
762 assert!(key.iter().all(|&b| b == 0));
763 }
764
765 #[test]
766 fn test_from_str_password() {
767 let engine_id = decode_hex("000000000000000000000002").unwrap();
769
770 let key_from_bytes =
771 LocalizedKey::from_password(AuthProtocol::Sha1, b"maplesyrup", &engine_id);
772 let key_from_str =
773 LocalizedKey::from_str_password(AuthProtocol::Sha1, "maplesyrup", &engine_id);
774
775 assert_eq!(key_from_bytes.as_bytes(), key_from_str.as_bytes());
776 assert_eq!(key_from_bytes.protocol(), key_from_str.protocol());
777 }
778
779 #[test]
780 fn test_master_key_localize_md5() {
781 let password = b"maplesyrup";
783 let engine_id = decode_hex("000000000000000000000002").unwrap();
784
785 let master = MasterKey::from_password(AuthProtocol::Md5, password);
786 let localized_via_master = master.localize(&engine_id);
787 let localized_direct = LocalizedKey::from_password(AuthProtocol::Md5, password, &engine_id);
788
789 assert_eq!(localized_via_master.as_bytes(), localized_direct.as_bytes());
790 assert_eq!(localized_via_master.protocol(), localized_direct.protocol());
791
792 assert_eq!(
794 encode_hex(master.as_bytes()),
795 "9faf3283884e92834ebc9847d8edd963"
796 );
797 }
798
799 #[test]
800 fn test_master_key_localize_sha1() {
801 let password = b"maplesyrup";
802 let engine_id = decode_hex("000000000000000000000002").unwrap();
803
804 let master = MasterKey::from_password(AuthProtocol::Sha1, password);
805 let localized_via_master = master.localize(&engine_id);
806 let localized_direct =
807 LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id);
808
809 assert_eq!(localized_via_master.as_bytes(), localized_direct.as_bytes());
810
811 assert_eq!(
813 encode_hex(master.as_bytes()),
814 "9fb5cc0381497b3793528939ff788d5d79145211"
815 );
816 }
817
818 #[test]
819 fn test_master_key_reuse_for_multiple_engines() {
820 let password = b"maplesyrup";
822 let engine_id_1 = decode_hex("000000000000000000000001").unwrap();
823 let engine_id_2 = decode_hex("000000000000000000000002").unwrap();
824
825 let master = MasterKey::from_password(AuthProtocol::Sha256, password);
826
827 let key1 = master.localize(&engine_id_1);
828 let key2 = master.localize(&engine_id_2);
829
830 assert_ne!(key1.as_bytes(), key2.as_bytes());
832
833 let direct1 = LocalizedKey::from_password(AuthProtocol::Sha256, password, &engine_id_1);
835 let direct2 = LocalizedKey::from_password(AuthProtocol::Sha256, password, &engine_id_2);
836
837 assert_eq!(key1.as_bytes(), direct1.as_bytes());
838 assert_eq!(key2.as_bytes(), direct2.as_bytes());
839 }
840
841 #[test]
842 fn test_from_master_key() {
843 let password = b"maplesyrup";
844 let engine_id = decode_hex("000000000000000000000002").unwrap();
845
846 let master = MasterKey::from_password(AuthProtocol::Sha256, password);
847 let key_via_localize = master.localize(&engine_id);
848 let key_via_from_master = LocalizedKey::from_master_key(&master, &engine_id);
849
850 assert_eq!(key_via_localize.as_bytes(), key_via_from_master.as_bytes());
851 }
852
853 #[test]
854 fn test_master_keys_auth_only() {
855 let engine_id = decode_hex("000000000000000000000002").unwrap();
856 let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword");
857
858 assert_eq!(master_keys.auth_protocol(), AuthProtocol::Sha256);
859 assert!(master_keys.priv_protocol().is_none());
860 assert!(master_keys.priv_master().is_none());
861
862 let (auth_key, priv_key) = master_keys.localize(&engine_id);
863 assert!(priv_key.is_none());
864 assert_eq!(auth_key.protocol(), AuthProtocol::Sha256);
865 }
866
867 #[test]
868 fn test_master_keys_with_privacy_same_password() {
869 use crate::v3::PrivProtocol;
870
871 let engine_id = decode_hex("000000000000000000000002").unwrap();
872 let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"sharedpassword")
873 .with_privacy_same_password(PrivProtocol::Aes128);
874
875 assert_eq!(master_keys.auth_protocol(), AuthProtocol::Sha256);
876 assert_eq!(master_keys.priv_protocol(), Some(PrivProtocol::Aes128));
877
878 let (auth_key, priv_key) = master_keys.localize(&engine_id);
879 assert!(priv_key.is_some());
880 assert_eq!(auth_key.protocol(), AuthProtocol::Sha256);
881 }
882
883 #[test]
884 fn test_master_keys_with_privacy_different_password() {
885 use crate::v3::PrivProtocol;
886
887 let engine_id = decode_hex("000000000000000000000002").unwrap();
888 let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword")
889 .with_privacy(PrivProtocol::Aes128, b"privpassword");
890
891 let (_auth_key, priv_key) = master_keys.localize(&engine_id);
892 assert!(priv_key.is_some());
893
894 let same_password_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword")
896 .with_privacy_same_password(PrivProtocol::Aes128);
897 let (_, priv_key_same) = same_password_keys.localize(&engine_id);
898
899 assert_ne!(
902 priv_key.as_ref().unwrap().encryption_key(),
903 priv_key_same.as_ref().unwrap().encryption_key()
904 );
905 }
906
907 #[test]
911 fn test_reeder_extend_key_md5_kat() {
912 let password = b"maplesyrup";
919 let engine_id = decode_hex("000000000000000000000002").unwrap();
920
921 let k1 = LocalizedKey::from_password(AuthProtocol::Md5, password, &engine_id);
923 assert_eq!(
924 encode_hex(k1.as_bytes()),
925 "526f5eed9fcce26f8964c2930787d82b"
926 );
927
928 let extended = extend_key_reeder(AuthProtocol::Md5, k1.as_bytes(), &engine_id, 32);
930 assert_eq!(extended.len(), 32);
931 assert_eq!(
932 encode_hex(&extended),
933 "526f5eed9fcce26f8964c2930787d82b79eff44a90650ee0a3a40abfac5acc12"
934 );
935 }
936
937 #[test]
938 fn test_reeder_extend_key_sha1_kat() {
939 let password = b"maplesyrup";
946 let engine_id = decode_hex("000000000000000000000002").unwrap();
947
948 let k1 = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id);
950 assert_eq!(
951 encode_hex(k1.as_bytes()),
952 "6695febc9288e36282235fc7151f128497b38f3f"
953 );
954
955 let extended = extend_key_reeder(AuthProtocol::Sha1, k1.as_bytes(), &engine_id, 40);
957 assert_eq!(extended.len(), 40);
958 assert_eq!(
959 encode_hex(&extended),
960 "6695febc9288e36282235fc7151f128497b38f3f9b8b6d78936ba6e7d19dfd9cd2d5065547743fb5"
961 );
962 }
963
964 #[test]
965 fn test_reeder_extend_key_sha1_to_32_bytes() {
966 let password = b"maplesyrup";
969 let engine_id = decode_hex("000000000000000000000002").unwrap();
970
971 let k1 = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id);
972 let extended = extend_key_reeder(AuthProtocol::Sha1, k1.as_bytes(), &engine_id, 32);
973
974 assert_eq!(extended.len(), 32);
975 assert_eq!(
977 encode_hex(&extended),
978 "6695febc9288e36282235fc7151f128497b38f3f9b8b6d78936ba6e7d19dfd9c"
979 );
980 }
981
982 #[test]
983 fn test_reeder_extend_key_truncation() {
984 let long_key = vec![0xAAu8; 64];
986 let engine_id = decode_hex("000000000000000000000002").unwrap();
987
988 let extended = extend_key_reeder(AuthProtocol::Sha256, &long_key, &engine_id, 32);
989 assert_eq!(extended.len(), 32);
990 assert_eq!(extended, vec![0xAAu8; 32]);
991 }
992
993 #[test]
994 fn test_reeder_vs_blumenthal_differ() {
995 let password = b"maplesyrup";
997 let engine_id = decode_hex("000000000000000000000002").unwrap();
998
999 let k1 = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id);
1000
1001 let reeder = extend_key_reeder(AuthProtocol::Sha1, k1.as_bytes(), &engine_id, 32);
1002 let blumenthal = extend_key(AuthProtocol::Sha1, k1.as_bytes(), 32);
1003
1004 assert_eq!(reeder.len(), 32);
1005 assert_eq!(blumenthal.len(), 32);
1006
1007 assert_eq!(&reeder[..20], &blumenthal[..20]);
1009 assert_ne!(&reeder[20..], &blumenthal[20..]);
1011 }
1012}