Files
yaoayanui/src-tauri/src/api_crypto.rs
fengchuanhn@gmail.com c70995fbc9 11
2026-05-17 22:05:34 +08:00

93 lines
2.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! AES-256-GCM与 pythonbackend `app/core/aes_crypto.py` 一致。
use rand::RngCore;
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
use base64::Engine;
use serde_json::Value;
pub const ENCRYPTED_HEADER: &str = "X-AICLIENT-Encrypted";
const ENVELOPE_VERSION: i64 = 1;
pub fn parse_aes_key(raw: &str) -> Option<[u8; 32]> {
let s = raw.trim();
if s.is_empty() {
return None;
}
let bytes = if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
hex::decode(s).ok()?
} else {
base64::engine::general_purpose::STANDARD
.decode(s)
.ok()?
};
if bytes.len() != 32 {
return None;
}
let mut key = [0u8; 32];
key.copy_from_slice(&bytes);
Some(key)
}
pub fn aes_key_from_env() -> Option<[u8; 32]> {
let s="98cde5e64ee0da6ed0c5842f43a950aa0d8ae5087c43c8d8ee5d10bd8d571bb4";
parse_aes_key(&s)
}
pub fn encrypt_bytes(plaintext: &[u8], key: &[u8; 32]) -> Result<String, String> {
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
let mut nonce_bytes = [0u8; 12];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext)
.map_err(|e| format!("加密失败: {e}"))?;
let mut combined = nonce_bytes.to_vec();
combined.extend(ciphertext);
Ok(base64::engine::general_purpose::STANDARD.encode(&combined))
}
pub fn decrypt_payload(b64_payload: &str, key: &[u8; 32]) -> Result<Vec<u8>, String> {
let raw = base64::engine::general_purpose::STANDARD
.decode(b64_payload.trim())
.map_err(|e| format!("Base64 解码失败: {e}"))?;
if raw.len() < 13 {
return Err("密文过短".into());
}
let (nonce_bytes, ciphertext) = raw.split_at(12);
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
let nonce = Nonce::from_slice(nonce_bytes);
cipher
.decrypt(nonce, ciphertext)
.map_err(|e| format!("解密失败: {e}"))
}
pub fn wrap_encrypted(plaintext: &[u8], key: &[u8; 32]) -> Result<String, String> {
let payload = encrypt_bytes(plaintext, key)?;
let envelope = serde_json::json!({
"v": ENVELOPE_VERSION,
"payload": payload,
});
serde_json::to_string(&envelope).map_err(|e| e.to_string())
}
pub fn unwrap_encrypted(body: &str, key: &[u8; 32]) -> Result<Vec<u8>, String> {
let v: Value = serde_json::from_str(body).map_err(|e| format!("信封 JSON 无效: {e}"))?;
let version = v.get("v").and_then(|x| x.as_i64());
let payload = v
.get("payload")
.and_then(|x| x.as_str())
.ok_or_else(|| "缺少 payload".to_string())?;
if version != Some(ENVELOPE_VERSION) {
return Err("不支持的加密版本".into());
}
decrypt_payload(payload, key)
}
pub fn crypto_enabled() -> bool {
aes_key_from_env().is_some()
}