Skip to main content

sz_orm_crypto/
lib.rs

1//! # SZ-ORM Crypto — 加密工具
2//!
3//! 提供常用密码学原语:AES-256-GCM 对称加密、HMAC-SHA256 消息认证码、
4//! PBKDF2 密钥派生与 SHA-256 哈希,所有实现基于 RustCrypto,保证常数时间比较。
5//!
6//! ## 主要函数
7//!
8//! - [`sha256`] / [`sha256_hex`] — SHA-256 哈希
9//! - AES-256-GCM 加解密
10//! - HMAC-SHA256 与 PBKDF2
11
12use std::collections::HashMap;
13
14use aes_gcm::aead::{Aead, KeyInit};
15use aes_gcm::{Aes256Gcm, Key, Nonce};
16use hmac::{Hmac, Mac};
17use pbkdf2::pbkdf2_hmac;
18use rand::rngs::OsRng;
19use rand::RngCore;
20use sha2::{Digest, Sha256};
21use subtle::ConstantTimeEq;
22
23type HmacSha256 = Hmac<Sha256>;
24
25// ============================================================================
26// SHA-256 (基于 RustCrypto sha2 crate, FIPS 180-4)
27// ============================================================================
28
29/// 计算 SHA-256 哈希(基于 RustCrypto sha2)
30pub fn sha256(data: &[u8]) -> [u8; 32] {
31    let mut hasher = Sha256::new();
32    hasher.update(data);
33    let result = hasher.finalize();
34    let mut out = [0u8; 32];
35    out.copy_from_slice(&result);
36    out
37}
38
39/// 计算 SHA-256 并返回十六进制字符串
40pub fn sha256_hex(data: &[u8]) -> String {
41    sha256(data).iter().map(|b| format!("{:02x}", b)).collect()
42}
43
44/// HMAC-SHA256 (RFC 2104, 基于 RustCrypto hmac crate)
45pub fn hmac_sha256(key: &[u8], message: &[u8]) -> [u8; 32] {
46    // HMAC-SHA256 按 RFC 2104 接受任意长度 key,RustCrypto 的 new_from_slice 对 HMAC 永远返回 Ok。
47    // 用 match 处理避免 panic,虽然 Err 分支不可达(RustCrypto 不变量保证)。
48    let mut mac = match <HmacSha256 as Mac>::new_from_slice(key) {
49        Ok(m) => m,
50        Err(_) => {
51            // 不可达分支:HMAC 规范允许任意 key 长度,RustCrypto 内部会先 hash 过长 key。
52            // 为安全起见返回全零(调用方在正常路径下永远不会命中此分支)。
53            return [0u8; 32];
54        }
55    };
56    mac.update(message);
57    let result = mac.finalize().into_bytes();
58    let mut out = [0u8; 32];
59    out.copy_from_slice(&result);
60    out
61}
62
63/// HMAC-SHA256 十六进制字符串
64pub fn hmac_sha256_hex(key: &[u8], message: &[u8]) -> String {
65    hmac_sha256(key, message)
66        .iter()
67        .map(|b| format!("{:02x}", b))
68        .collect()
69}
70
71/// 常量时间比较(基于 subtle crate),避免时序攻击
72fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
73    a.ct_eq(b).into()
74}
75
76// ============================================================================
77// 加密器
78// ============================================================================
79
80pub trait Crypter: Send + Sync {
81    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError>;
82    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError>;
83}
84
85/// AES-256-GCM 加密器(密码学安全)
86///
87/// 使用 AES-256-GCM AEAD 算法,每次加密生成随机 12 字节 nonce。
88/// 密文格式:`nonce(12) || ciphertext || tag(16)`(由 aes-gcm crate 内部处理)。
89pub struct AesGcmCrypter {
90    cipher: Aes256Gcm,
91}
92
93impl AesGcmCrypter {
94    /// 从 32 字节密钥创建
95    pub fn new(key: &[u8; 32]) -> Self {
96        let key = Key::<Aes256Gcm>::from_slice(key);
97        Self {
98            cipher: Aes256Gcm::new(key),
99        }
100    }
101
102    /// 从任意长度密钥字符串创建(SHA-256 派生 32 字节密钥)
103    pub fn from_key_str(key: &str) -> Self {
104        let hash = sha256(key.as_bytes());
105        Self::new(&hash)
106    }
107
108    fn random_nonce() -> [u8; 12] {
109        let mut nonce = [0u8; 12];
110        OsRng.fill_bytes(&mut nonce);
111        nonce
112    }
113}
114
115impl Crypter for AesGcmCrypter {
116    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
117        let nonce_bytes = Self::random_nonce();
118        let nonce = Nonce::from_slice(&nonce_bytes);
119        let ciphertext = self
120            .cipher
121            .encrypt(nonce, plaintext)
122            .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
123        let mut result = Vec::with_capacity(12 + ciphertext.len());
124        result.extend_from_slice(&nonce_bytes);
125        result.extend_from_slice(&ciphertext);
126        Ok(result)
127    }
128
129    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
130        if ciphertext.len() < 12 {
131            return Err(CryptoError::DecryptionFailed(
132                "Ciphertext too short".to_string(),
133            ));
134        }
135        let nonce = Nonce::from_slice(&ciphertext[..12]);
136        let encrypted = &ciphertext[12..];
137        self.cipher
138            .decrypt(nonce, encrypted)
139            .map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
140    }
141}
142
143// ============================================================================
144// 密码哈希
145// ============================================================================
146
147pub trait PasswordHasher: Send + Sync {
148    fn hash(&self, password: &str) -> Result<String, CryptoError>;
149    fn verify(&self, password: &str, hash: &str) -> Result<bool, CryptoError>;
150}
151
152/// PBKDF2-HMAC-SHA256 密码哈希器(基于 RustCrypto pbkdf2 crate)
153///
154/// 使用 PBKDF2-HMAC-SHA256 算法(RFC 8018)。
155/// 哈希格式:`$<iterations>$<salt_hex>$<hash_hex>`
156pub struct Pbkdf2Hasher {
157    iterations: u32,
158}
159
160impl Pbkdf2Hasher {
161    const DEFAULT_ITERATIONS: u32 = 100_000;
162    const SALT_LEN: usize = 16;
163    const HASH_LEN: usize = 32;
164
165    pub fn new() -> Self {
166        Self {
167            iterations: Self::DEFAULT_ITERATIONS,
168        }
169    }
170
171    pub fn with_iterations(iterations: u32) -> Self {
172        Self {
173            iterations: iterations.max(1),
174        }
175    }
176
177    fn compute_hash(password: &str, salt: &[u8], iterations: u32) -> [u8; Self::HASH_LEN] {
178        let mut out = [0u8; Self::HASH_LEN];
179        pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, iterations, &mut out);
180        out
181    }
182}
183
184impl Default for Pbkdf2Hasher {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190impl PasswordHasher for Pbkdf2Hasher {
191    fn hash(&self, password: &str) -> Result<String, CryptoError> {
192        if password.is_empty() {
193            return Err(CryptoError::InvalidHash(
194                "Password cannot be empty".to_string(),
195            ));
196        }
197        let salt = random_bytes(Self::SALT_LEN);
198        let hash = Self::compute_hash(password, &salt, self.iterations);
199        Ok(format!(
200            "${}${}${}",
201            self.iterations,
202            hex_encode(&salt),
203            hex_encode(&hash)
204        ))
205    }
206
207    fn verify(&self, password: &str, hash: &str) -> Result<bool, CryptoError> {
208        if !hash.starts_with('$') {
209            return Err(CryptoError::InvalidHash("Invalid hash format".to_string()));
210        }
211        let parts: Vec<&str> = hash[1..].splitn(3, '$').collect();
212        if parts.len() != 3 {
213            return Err(CryptoError::InvalidHash("Invalid hash format".to_string()));
214        }
215        let iterations: u32 = parts[0]
216            .parse()
217            .map_err(|_| CryptoError::InvalidHash("Invalid iterations".to_string()))?;
218        let salt = hex_decode(parts[1])
219            .map_err(|_| CryptoError::InvalidHash("Invalid salt hex".to_string()))?;
220        let expected_hash = hex_decode(parts[2])
221            .map_err(|_| CryptoError::InvalidHash("Invalid hash hex".to_string()))?;
222        let computed = Self::compute_hash(password, &salt, iterations);
223        Ok(constant_time_eq(&computed, &expected_hash))
224    }
225}
226
227// ============================================================================
228// API 签名
229// ============================================================================
230
231pub trait ApiSigner: Send + Sync {
232    fn sign(&self, params: &HashMap<String, String>, secret: &str) -> String;
233    fn verify(&self, params: &HashMap<String, String>, secret: &str, signature: &str) -> bool;
234}
235
236/// HMAC-SHA256 API 签名器
237///
238/// 对参数按字典序排序后拼接成 query string,再用 HMAC-SHA256 签名。
239pub struct HmacSigner;
240
241impl HmacSigner {
242    pub fn new() -> Self {
243        Self
244    }
245
246    fn compute_signature(params: &HashMap<String, String>, secret: &str) -> String {
247        let mut sorted: Vec<_> = params.iter().collect();
248        sorted.sort_by(|a, b| a.0.cmp(b.0));
249
250        let query_string: String = sorted
251            .iter()
252            .map(|(k, v)| format!("{}={}", k, v))
253            .collect::<Vec<_>>()
254            .join("&");
255
256        hmac_sha256_hex(secret.as_bytes(), query_string.as_bytes())
257    }
258}
259
260impl Default for HmacSigner {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265
266impl ApiSigner for HmacSigner {
267    fn sign(&self, params: &HashMap<String, String>, secret: &str) -> String {
268        Self::compute_signature(params, secret)
269    }
270
271    fn verify(&self, params: &HashMap<String, String>, secret: &str, signature: &str) -> bool {
272        let computed = Self::compute_signature(params, secret);
273        constant_time_eq(computed.as_bytes(), signature.as_bytes())
274    }
275}
276
277// ============================================================================
278// 辅助函数
279// ============================================================================
280
281fn hex_encode(bytes: &[u8]) -> String {
282    bytes.iter().map(|b| format!("{:02x}", b)).collect()
283}
284
285fn hex_decode(hex: &str) -> Result<Vec<u8>, ()> {
286    if !hex.len().is_multiple_of(2) {
287        return Err(());
288    }
289    (0..hex.len())
290        .step_by(2)
291        .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(|_| ()))
292        .collect()
293}
294
295fn random_bytes(len: usize) -> Vec<u8> {
296    let mut result = vec![0u8; len];
297    OsRng.fill_bytes(&mut result);
298    result
299}
300
301// ============================================================================
302// 错误类型
303// ============================================================================
304
305#[derive(Debug)]
306pub enum CryptoError {
307    EncryptionFailed(String),
308    DecryptionFailed(String),
309    InvalidKey(String),
310    InvalidNonce(String),
311    InvalidHash(String),
312    SigningFailed(String),
313}
314
315impl std::fmt::Display for CryptoError {
316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317        match self {
318            CryptoError::EncryptionFailed(msg) => write!(f, "Encryption failed: {}", msg),
319            CryptoError::DecryptionFailed(msg) => write!(f, "Decryption failed: {}", msg),
320            CryptoError::InvalidKey(msg) => write!(f, "Invalid key: {}", msg),
321            CryptoError::InvalidNonce(msg) => write!(f, "Invalid nonce: {}", msg),
322            CryptoError::InvalidHash(msg) => write!(f, "Invalid hash: {}", msg),
323            CryptoError::SigningFailed(msg) => write!(f, "Signing failed: {}", msg),
324        }
325    }
326}
327
328impl std::error::Error for CryptoError {}
329
330impl serde::Serialize for CryptoError {
331    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
332    where
333        S: serde::Serializer,
334    {
335        serializer.serialize_str(&self.to_string())
336    }
337}
338
339// ============================================================================
340// 测试
341// ============================================================================
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    // --- SHA-256 标准测试向量 (FIPS 180-2 / NIST) ---
348
349    #[test]
350    fn test_sha256_empty() {
351        assert_eq!(
352            sha256_hex(b""),
353            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
354        );
355    }
356
357    #[test]
358    fn test_sha256_abc() {
359        assert_eq!(
360            sha256_hex(b"abc"),
361            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
362        );
363    }
364
365    #[test]
366    fn test_sha256_hello() {
367        assert_eq!(
368            sha256_hex(b"hello"),
369            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
370        );
371    }
372
373    #[test]
374    fn test_sha256_long_message() {
375        assert_eq!(
376            sha256_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
377            "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
378        );
379    }
380
381    #[test]
382    fn test_sha256_deterministic() {
383        assert_eq!(sha256_hex(b"test"), sha256_hex(b"test"));
384        assert_ne!(sha256_hex(b"test"), sha256_hex(b"Test"));
385    }
386
387    // --- HMAC-SHA256 测试向量 (RFC 4231) ---
388
389    #[test]
390    fn test_hmac_sha256_rfc4231_case1() {
391        let key = vec![0x0bu8; 20];
392        let result = hmac_sha256_hex(&key, b"Hi There");
393        assert_eq!(
394            result,
395            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
396        );
397    }
398
399    #[test]
400    fn test_hmac_sha256_rfc4231_case2() {
401        let result = hmac_sha256_hex(b"Jefe", b"what do ya want for nothing?");
402        assert_eq!(
403            result,
404            "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
405        );
406    }
407
408    #[test]
409    fn test_hmac_sha256_long_key() {
410        let key = vec![0xaau8; 130];
411        let result = hmac_sha256_hex(&key, b"test message");
412        assert_eq!(result.len(), 64);
413        let short_key = vec![0xaau8; 32];
414        let result_short = hmac_sha256_hex(&short_key, b"test message");
415        assert_ne!(result, result_short);
416    }
417
418    #[test]
419    fn test_hmac_sha256_different_messages() {
420        let key = b"secret";
421        assert_ne!(hmac_sha256_hex(key, b"msg1"), hmac_sha256_hex(key, b"msg2"));
422    }
423
424    // --- AesGcmCrypter 测试 ---
425
426    #[test]
427    fn test_aes_gcm_roundtrip() {
428        let key = [0x42u8; 32];
429        let crypter = AesGcmCrypter::new(&key);
430        let plaintext = b"Hello, World!";
431        let encrypted = crypter.encrypt(plaintext).unwrap();
432        let decrypted = crypter.decrypt(&encrypted).unwrap();
433        assert_eq!(decrypted, plaintext);
434    }
435
436    #[test]
437    fn test_aes_gcm_random_nonce_per_encryption() {
438        let key = [0x42u8; 32];
439        let crypter = AesGcmCrypter::new(&key);
440        let plaintext = b"same plaintext";
441        let encrypted1 = crypter.encrypt(plaintext).unwrap();
442        let encrypted2 = crypter.encrypt(plaintext).unwrap();
443        assert_ne!(encrypted1, encrypted2, "随机 nonce 应使密文不同");
444        assert_eq!(crypter.decrypt(&encrypted1).unwrap(), plaintext);
445        assert_eq!(crypter.decrypt(&encrypted2).unwrap(), plaintext);
446    }
447
448    #[test]
449    fn test_aes_gcm_from_key_str() {
450        let crypter = AesGcmCrypter::from_key_str("my-secret-key");
451        let plaintext = b"data to encrypt";
452        let encrypted = crypter.encrypt(plaintext).unwrap();
453        let decrypted = crypter.decrypt(&encrypted).unwrap();
454        assert_eq!(decrypted, plaintext);
455    }
456
457    #[test]
458    fn test_aes_gcm_short_ciphertext() {
459        let key = [0x42u8; 32];
460        let crypter = AesGcmCrypter::new(&key);
461        assert!(crypter.decrypt(&[0u8; 8]).is_err());
462    }
463
464    #[test]
465    fn test_aes_gcm_empty_plaintext() {
466        let key = [0x42u8; 32];
467        let crypter = AesGcmCrypter::new(&key);
468        let encrypted = crypter.encrypt(b"").unwrap();
469        // nonce(12) + tag(16) = 28
470        assert_eq!(encrypted.len(), 28);
471        let decrypted = crypter.decrypt(&encrypted).unwrap();
472        assert_eq!(decrypted, b"");
473    }
474
475    #[test]
476    fn test_aes_gcm_tampered_ciphertext() {
477        let key = [0x42u8; 32];
478        let crypter = AesGcmCrypter::new(&key);
479        let encrypted = crypter.encrypt(b"sensitive data").unwrap();
480        let mut tampered = encrypted.clone();
481        tampered[15] ^= 0x01;
482        assert!(crypter.decrypt(&tampered).is_err());
483    }
484
485    // --- Pbkdf2Hasher 测试 ---
486
487    #[test]
488    fn test_pbkdf2_hasher_hash_format() {
489        let hasher = Pbkdf2Hasher::new();
490        let hash = hasher.hash("password123").unwrap();
491        assert!(hash.starts_with('$'));
492        let parts: Vec<&str> = hash[1..].splitn(3, '$').collect();
493        assert_eq!(parts.len(), 3);
494        assert_eq!(parts[0].parse::<u32>().unwrap(), 100_000);
495        // salt 32 hex chars (16 bytes)
496        assert_eq!(parts[1].len(), 32);
497        // hash 64 hex chars (32 bytes)
498        assert_eq!(parts[2].len(), 64);
499    }
500
501    #[test]
502    fn test_pbkdf2_hasher_verify_correct() {
503        let hasher = Pbkdf2Hasher::new();
504        let hash = hasher.hash("password123").unwrap();
505        assert!(hasher.verify("password123", &hash).unwrap());
506    }
507
508    #[test]
509    fn test_pbkdf2_hasher_verify_wrong() {
510        let hasher = Pbkdf2Hasher::new();
511        let hash = hasher.hash("password123").unwrap();
512        assert!(!hasher.verify("wrongpassword", &hash).unwrap());
513    }
514
515    #[test]
516    fn test_pbkdf2_hasher_different_passwords_different_hashes() {
517        let hasher = Pbkdf2Hasher::new();
518        let h1 = hasher.hash("pass1").unwrap();
519        let h2 = hasher.hash("pass2").unwrap();
520        assert_ne!(h1, h2);
521    }
522
523    #[test]
524    fn test_pbkdf2_hasher_same_password_different_salts() {
525        let hasher = Pbkdf2Hasher::new();
526        let h1 = hasher.hash("same").unwrap();
527        let h2 = hasher.hash("same").unwrap();
528        assert_ne!(h1, h2);
529        assert!(hasher.verify("same", &h1).unwrap());
530        assert!(hasher.verify("same", &h2).unwrap());
531    }
532
533    #[test]
534    fn test_pbkdf2_hasher_invalid_format() {
535        let hasher = Pbkdf2Hasher::new();
536        assert!(hasher.verify("password", "invalid-hash").is_err());
537        assert!(hasher.verify("password", "$abc").is_err());
538        assert!(hasher.verify("password", "$abc$def").is_err());
539    }
540
541    #[test]
542    fn test_pbkdf2_hasher_with_iterations() {
543        let hasher = Pbkdf2Hasher::with_iterations(1000);
544        let hash = hasher.hash("secret").unwrap();
545        let parts: Vec<&str> = hash[1..].splitn(3, '$').collect();
546        assert_eq!(parts[0], "1000");
547        assert!(hasher.verify("secret", &hash).unwrap());
548    }
549
550    #[test]
551    fn test_pbkdf2_hasher_empty_password() {
552        let hasher = Pbkdf2Hasher::new();
553        assert!(hasher.hash("").is_err());
554    }
555
556    // --- HmacSigner 测试 ---
557
558    #[test]
559    fn test_hmac_signer_sign_not_empty() {
560        let signer = HmacSigner::new();
561        let mut params = HashMap::new();
562        params.insert("name".to_string(), "test".to_string());
563        let signature = signer.sign(&params, "secret123");
564        assert_eq!(signature.len(), 64);
565    }
566
567    #[test]
568    fn test_hmac_signer_verify_correct() {
569        let signer = HmacSigner::new();
570        let mut params = HashMap::new();
571        params.insert("name".to_string(), "test".to_string());
572        params.insert("age".to_string(), "25".to_string());
573
574        let signature = signer.sign(&params, "mysecret");
575        assert!(signer.verify(&params, "mysecret", &signature));
576    }
577
578    #[test]
579    fn test_hmac_signer_verify_wrong_secret() {
580        let signer = HmacSigner::new();
581        let mut params = HashMap::new();
582        params.insert("name".to_string(), "test".to_string());
583        let signature = signer.sign(&params, "correctsecret");
584        assert!(!signer.verify(&params, "wrongsecret", &signature));
585    }
586
587    #[test]
588    fn test_hmac_signer_verify_wrong_signature() {
589        let signer = HmacSigner::new();
590        let mut params = HashMap::new();
591        params.insert("name".to_string(), "test".to_string());
592        let valid_sig = signer.sign(&params, "secret");
593        let tampered = if let Some(stripped) = valid_sig.strip_prefix('0') {
594            format!("1{}", stripped)
595        } else {
596            format!("0{}", &valid_sig[1..])
597        };
598        assert!(!signer.verify(&params, "secret", &tampered));
599    }
600
601    #[test]
602    fn test_hmac_signer_different_params_different_signatures() {
603        let signer = HmacSigner::new();
604        let mut params1 = HashMap::new();
605        params1.insert("a".to_string(), "1".to_string());
606
607        let mut params2 = HashMap::new();
608        params2.insert("b".to_string(), "2".to_string());
609
610        let sig1 = signer.sign(&params1, "secret");
611        let sig2 = signer.sign(&params2, "secret");
612        assert_ne!(sig1, sig2);
613    }
614
615    #[test]
616    fn test_hmac_signer_param_order_independent() {
617        let signer = HmacSigner::new();
618        let mut params1 = HashMap::new();
619        params1.insert("b".to_string(), "2".to_string());
620        params1.insert("a".to_string(), "1".to_string());
621
622        let mut params2 = HashMap::new();
623        params2.insert("a".to_string(), "1".to_string());
624        params2.insert("b".to_string(), "2".to_string());
625
626        let sig1 = signer.sign(&params1, "secret");
627        let sig2 = signer.sign(&params2, "secret");
628        assert_eq!(sig1, sig2);
629    }
630
631    #[test]
632    fn test_hmac_signer_empty_params() {
633        let signer = HmacSigner::new();
634        let params = HashMap::new();
635        let sig = signer.sign(&params, "secret");
636        assert_eq!(sig.len(), 64);
637        assert!(signer.verify(&params, "secret", &sig));
638    }
639
640    // --- 辅助函数测试 ---
641
642    #[test]
643    fn test_random_bytes_length() {
644        assert_eq!(random_bytes(0).len(), 0);
645        assert_eq!(random_bytes(16).len(), 16);
646        assert_eq!(random_bytes(100).len(), 100);
647    }
648
649    #[test]
650    fn test_random_bytes_random() {
651        let a = random_bytes(32);
652        let b = random_bytes(32);
653        assert_ne!(a, b, "随机字节序列应不同");
654    }
655
656    #[test]
657    fn test_constant_time_eq() {
658        assert!(constant_time_eq(b"abc", b"abc"));
659        assert!(!constant_time_eq(b"abc", b"abd"));
660        assert!(!constant_time_eq(b"abc", b"ab"));
661        assert!(!constant_time_eq(b"abc", b"abcd"));
662        assert!(constant_time_eq(b"", b""));
663    }
664
665    #[test]
666    fn test_hex_encode_decode_roundtrip() {
667        let original = vec![0x00, 0xff, 0xab, 0x42];
668        let encoded = hex_encode(&original);
669        let decoded = hex_decode(&encoded).unwrap();
670        assert_eq!(decoded, original);
671    }
672
673    #[test]
674    fn test_hex_decode_invalid() {
675        assert!(hex_decode("abc").is_err());
676        assert!(hex_decode("xy").is_err());
677    }
678}