Skip to main content

alun_utils/
crypto.rs

1//! 加密解密工具
2
3use aes_gcm::{Aes256Gcm, Nonce, KeyInit};
4use aes_gcm::aead::Aead;
5use sha2::{Sha256, Digest};
6use hmac::{Hmac, Mac};
7use rand::Rng;
8
9type HmacSha256 = Hmac<Sha256>;
10
11/// 加密解密工具集
12///
13/// 提供 SHA-256、HMAC、AES-256-GCM、Argon2 密码哈希、Base64 URL 编解码、
14/// 随机密钥/Token 生成等常用密码学操作。
15pub struct Crypto;
16
17impl Crypto {
18    /// SHA-256 哈希(返回 hex 编码,64 字符)
19    pub fn sha256(data: &str) -> String {
20        let mut hasher = Sha256::new();
21        hasher.update(data.as_bytes());
22        hex::encode(hasher.finalize())
23    }
24
25    /// HMAC-SHA256 签名(返回 hex 编码)
26    pub fn hmac_sha256(key: &[u8], data: &str) -> String {
27        let mut mac = <HmacSha256 as hmac::digest::KeyInit>::new_from_slice(key)
28            .expect("HMAC key size error");
29        mac.update(data.as_bytes());
30        hex::encode(mac.finalize().into_bytes())
31    }
32
33    /// AES-256-GCM 加密
34    ///
35    /// - `key`: 32 字节密钥,若非 32 字节返回 `None`
36    /// - 返回 `(Base64 密文, Base64 nonce)` 元组
37    pub fn aes_encrypt(key: &[u8], plaintext: &str) -> Option<(String, String)> {
38        if key.len() != 32 { return None; }
39        let cipher = Aes256Gcm::new_from_slice(key).ok()?;
40        let mut nonce_bytes = [0u8; 12];
41        rand::thread_rng().fill(&mut nonce_bytes);
42        let nonce = Nonce::from_slice(&nonce_bytes);
43        let ciphertext = cipher.encrypt(nonce, plaintext.as_bytes()).ok()?;
44        Some((
45            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &ciphertext),
46            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &nonce_bytes),
47        ))
48    }
49
50    /// AES-256-GCM 解密(与 `aes_encrypt` 配对使用)
51    pub fn aes_decrypt(key: &[u8], ciphertext_b64: &str, nonce_b64: &str) -> Option<String> {
52        if key.len() != 32 { return None; }
53        let cipher = Aes256Gcm::new_from_slice(key).ok()?;
54        let ciphertext = base64::Engine::decode(
55            &base64::engine::general_purpose::STANDARD, ciphertext_b64
56        ).ok()?;
57        let nonce_vec = base64::Engine::decode(
58            &base64::engine::general_purpose::STANDARD, nonce_b64
59        ).ok()?;
60        let nonce = Nonce::from_slice(&nonce_vec);
61        let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).ok()?;
62        String::from_utf8(plaintext).ok()
63    }
64
65    /// Argon2 密码哈希(自动生成随机盐)
66    pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
67        use argon2::{Argon2, PasswordHasher};
68        let salt = argon2::password_hash::SaltString::generate(&mut rand::thread_rng());
69        let argon2 = Argon2::default();
70        let hash = argon2.hash_password(password.as_bytes(), &salt)?;
71        Ok(hash.to_string())
72    }
73
74    /// 验证密码是否匹配 Argon2 哈希
75    pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
76        use argon2::{Argon2, PasswordVerifier, PasswordHash};
77        let parsed = PasswordHash::new(hash)?;
78        let argon2 = Argon2::default();
79        Ok(argon2.verify_password(password.as_bytes(), &parsed).is_ok())
80    }
81
82    /// Base64 URL 安全编码(无填充,适合放在 URL/文件名中)
83    pub fn base64_url_encode(data: &[u8]) -> String {
84        base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, data)
85    }
86
87    /// Base64 URL 安全解码
88    pub fn base64_url_decode(s: &str) -> Option<Vec<u8>> {
89        base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, s).ok()
90    }
91
92    /// 生成 32 字节随机密钥(用于 AES-256-GCM)
93    pub fn random_key() -> Vec<u8> {
94        let mut key = vec![0u8; 32];
95        rand::thread_rng().fill(&mut key[..]);
96        key
97    }
98
99    /// 生成随机 hex Token
100    pub fn random_token(len: usize) -> String {
101        let mut bytes = vec![0u8; len];
102        rand::thread_rng().fill(&mut bytes[..]);
103        hex::encode(bytes)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    #[test]
111    fn test_sha256() { assert_eq!(Crypto::sha256("hello").len(), 64); }
112    #[test]
113    fn test_encrypt_decrypt() {
114        let key = vec![0u8; 32];
115        let (ct, nonce64) = Crypto::aes_encrypt(&key, "test-data").unwrap();
116        let pt = Crypto::aes_decrypt(&key, &ct, &nonce64).unwrap();
117        assert_eq!(pt, "test-data");
118    }
119    #[test]
120    fn test_password() {
121        let hash = Crypto::hash_password("Alun@2024").unwrap();
122        assert!(Crypto::verify_password("Alun@2024", &hash).unwrap());
123    }
124    #[test]
125    fn test_random_key() { assert_eq!(Crypto::random_key().len(), 32); }
126}