- Base platform from base.git (ERP base: auth, core, config, message, workflow, plugin) - Created erp-diary module skeleton (lib.rs, dto.rs, error.rs, event.rs, state.rs) - Integrated erp-diary into workspace and erp-server - Added DiaryModule registration in main.rs - Added DiaryState FromRef in state.rs - Diary routes mounted (empty routes, ready for implementation) - Product design spec v1.2 preserved in docs/ - Implementation plan preserved in plans/ Cargo check: OK Cargo test: OK (78+ base tests passing)
49 lines
1.8 KiB
Rust
49 lines
1.8 KiB
Rust
use aes_gcm::aead::Aead;
|
||
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
|
||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||
use rand::RngCore;
|
||
|
||
const CIPHER_VERSION: u8 = 0x01;
|
||
|
||
/// AES-256-GCM 加密。输出格式: Base64(0x01 || nonce[12] || ciphertext + tag)
|
||
pub fn encrypt(key: &[u8; 32], plaintext: &str) -> Result<String, String> {
|
||
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
|
||
let mut nonce_bytes = [0u8; 12];
|
||
rand::thread_rng().fill_bytes(&mut nonce_bytes);
|
||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||
let ciphertext = cipher
|
||
.encrypt(nonce, plaintext.as_bytes())
|
||
.map_err(|e| e.to_string())?;
|
||
let mut combined = vec![CIPHER_VERSION];
|
||
combined.extend_from_slice(&nonce_bytes);
|
||
combined.extend_from_slice(&ciphertext);
|
||
Ok(BASE64.encode(&combined))
|
||
}
|
||
|
||
/// AES-256-GCM 解密。支持 v1 格式: Base64(0x01 || nonce[12] || ciphertext + tag)
|
||
/// 兼容旧格式: Base64(nonce[12] || ciphertext + tag)
|
||
pub fn decrypt(key: &[u8; 32], encoded: &str) -> Result<String, String> {
|
||
let bytes = BASE64.decode(encoded).map_err(|e| e.to_string())?;
|
||
if bytes.len() < 13 {
|
||
return Err("ciphertext too short".into());
|
||
}
|
||
|
||
let (nonce_bytes, ciphertext) = if bytes[0] == CIPHER_VERSION {
|
||
// v1: version(1) + nonce(12) + ciphertext
|
||
if bytes.len() < 14 {
|
||
return Err("v1 ciphertext too short".into());
|
||
}
|
||
(&bytes[1..13], &bytes[13..])
|
||
} else {
|
||
// 旧格式: nonce(12) + ciphertext(向后兼容)
|
||
(&bytes[0..12], &bytes[12..])
|
||
};
|
||
|
||
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
|
||
let nonce = Nonce::from_slice(nonce_bytes);
|
||
let plaintext = cipher
|
||
.decrypt(nonce, ciphertext)
|
||
.map_err(|e| e.to_string())?;
|
||
String::from_utf8(plaintext).map_err(|e| e.to_string())
|
||
}
|