feat: initialize Nuanji (Warm Notes) project
- 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)
This commit is contained in:
48
crates/erp-core/src/crypto/engine.rs
Normal file
48
crates/erp-core/src/crypto/engine.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user