File size: 5,086 Bytes
a21c316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use aes_gcm::{
    aead::{Aead, KeyInit},
    Aes256Gcm, Nonce,
};
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Deserializer, Serializer};
use sha2::Digest;

const FIXED_NONCE: &[u8; 12] = b"antigravsalt";
const ENCRYPTED_PREFIX: &str = "ag_enc_";

/// 生成加密密钥 (基于设备 ID)
fn get_encryption_key() -> [u8; 32] {
    // 使用设备唯一标识生成密钥
    let device_id = machine_uid::get().unwrap_or_else(|_| "default".to_string());
    let mut key = [0u8; 32];
    let hash = sha2::Sha256::digest(device_id.as_bytes());
    key.copy_from_slice(&hash);
    key
}

pub fn serialize_password<S>(password: &str, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    // [FIX #1738] 防止双重加密:检查是否已包含魔术前缀
    if password.starts_with(ENCRYPTED_PREFIX) {
        return serializer.serialize_str(password);
    }

    let encrypted = encrypt_string(password).map_err(serde::ser::Error::custom)?;
    serializer.serialize_str(&encrypted)
}

pub fn deserialize_password<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let raw = String::deserialize(deserializer)?;
    if raw.is_empty() {
        return Ok(raw);
    }

    // [FIX #1738] 检查魔术前缀
    if raw.starts_with(ENCRYPTED_PREFIX) {
        // 新版格式:去前缀后解密
        let ciphertext = &raw[ENCRYPTED_PREFIX.len()..];
        match decrypt_string_internal(ciphertext) {
            Ok(plaintext) => Ok(plaintext),
            Err(_) => {
                // 解密失败(如密钥变更),返回原始密文以防止数据丢失
                Ok(raw)
            }
        }
    } else {
        // 兼容旧版:尝试直接解密
        match decrypt_string_internal(&raw) {
            Ok(plaintext) => {
                // 只有当解密出有效的 UTF-8 且看起来像合理个字符串时才认为是旧版密文
                // 这里 decrypt_string_internal 已经保证了 UTF-8,
                // 如果是用户输入的明文,通常解密会失败(Base64 错误或 Tag 校验错误)。
                Ok(plaintext)
            }
            Err(_) => {
                // 解密失败,认为是普通明文(用户输入的无前缀密码)
                Ok(raw)
            }
        }
    }
}

pub fn encrypt_string(password: &str) -> Result<String, String> {
    let key = get_encryption_key();
    let cipher = Aes256Gcm::new(&key.into());
    // In production, we should use a random nonce and prepend it to the ciphertext
    // For simplicity in this demo, we use a fixed nonce (NOT SECURE for repeats)
    // improving security: use random nonce
    let nonce = Nonce::from_slice(FIXED_NONCE);

    let ciphertext = cipher
        .encrypt(nonce, password.as_bytes())
        .map_err(|e| format!("Encryption failed: {}", e))?;

    let base64_ciphertext = general_purpose::STANDARD.encode(ciphertext);
    // [FIX #1738] 添加魔术前缀
    Ok(format!("{}{}", ENCRYPTED_PREFIX, base64_ciphertext))
}

/// 内部解密函数 (输入必须是纯 Base64 密文,不含前缀)
fn decrypt_string_internal(encrypted_base64: &str) -> Result<String, String> {
    let key = get_encryption_key();
    let cipher = Aes256Gcm::new(&key.into());
    let nonce = Nonce::from_slice(FIXED_NONCE);

    let ciphertext = general_purpose::STANDARD
        .decode(encrypted_base64)
        .map_err(|e| format!("Base64 decode failed: {}", e))?;

    let plaintext = cipher
        .decrypt(nonce, ciphertext.as_ref())
        .map_err(|e| format!("Decryption failed: {}", e))?;

    String::from_utf8(plaintext).map_err(|e| format!("UTF-8 conversion failed: {}", e))
}

pub fn decrypt_string(encrypted: &str) -> Result<String, String> {
    if encrypted.starts_with(ENCRYPTED_PREFIX) {
        decrypt_string_internal(&encrypted[ENCRYPTED_PREFIX.len()..])
    } else {
        decrypt_string_internal(encrypted)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encrypt_decrypt_cycle() {
        let password = "my_secret_password";
        let encrypted = encrypt_string(password).unwrap();
        
        assert!(encrypted.starts_with(ENCRYPTED_PREFIX));
        assert_ne!(password, encrypted);

        let decrypted = decrypt_string(&encrypted).unwrap();
        assert_eq!(password, decrypted);
    }

    #[test]
    fn test_legacy_compatibility() {
        // 模拟旧版加密(手动调用内部逻辑生成无前缀密文)
        let password = "legacy_password";
        let key = get_encryption_key();
        let cipher = Aes256Gcm::new(&key.into());
        let nonce = Nonce::from_slice(FIXED_NONCE);
        let ciphertext = cipher.encrypt(nonce, password.as_bytes()).unwrap();
        let legacy_encrypted = general_purpose::STANDARD.encode(ciphertext);

        assert!(!legacy_encrypted.starts_with(ENCRYPTED_PREFIX));

        // 使用新版解密逻辑
        let decrypted = decrypt_string(&legacy_encrypted).unwrap();
        assert_eq!(password, decrypted);
    }
}