处理用户数据时,哪怕应用只在本地运行,也不能把敏感信息(如密钥、令牌、密码、日志中的个人信息)以明文形式随意存放。这一节我们聚焦三个关键点:本地加密存储、密钥管理和日志脱敏,给出可直接落地的方案。
1. 本地加密存储:不该让密码“裸奔”
永远不要将密码、token直接写入明文文件或SQLite。Tauri的Rust后端可以轻松集成成熟的加密库,推荐使用 aes-gcm 进行认证加密,配合随机生成的 nonce,将加密后的数据存入本地文件。
Rust侧命令示例:加密并保存
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
use rand::Rng;
use std::fs;
#[tauri::command]
fn save_encrypted(file_path: String, plaintext: String, password: &str) -> Result<(), String> {
// 从用户密码派生出32字节密钥(实际项目中建议使用argon2)
let key = Key::from_slice(password.as_bytes()); // 简化示例,务必使用派生函数
let cipher = Aes256Gcm::new(key);
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext.as_bytes())
.map_err(|e| e.to_string())?;
// 将 nonce + ciphertext 拼接后写入文件
let mut combined = nonce_bytes.to_vec();
combined.extend_from_slice(&ciphertext);
fs::write(file_path, &combined).map_err(|e| e.to_string())?;
Ok(())
}
前端只需调用 invoke('save_encrypted', { ... }),用户输入的密码只在内存中短暂存在,不会写入明文。
解密同理:从文件读取首12字节为nonce,随后部分为密文,用相同密码恢复原文。
生产建议:
- 使用
argon2或pbkdf2将口令派生为安全密钥,不要直接当字节用。 - 为每个加密条目随机生成独立 nonce,绝对不要重复使用。
- 优先考虑系统提供的凭据存储API(Windows Credential Manager, macOS Keychain, Linux libsecret),Tauri 有社区插件
tauri-plugin-keyring可以直接调用。
2. 密钥管理:秘密不该散落在代码里
硬编码密钥是灾难。即使你的应用只在内部使用,也需遵循基本管理原则:
- 区分开发与生产密钥:使用环境变量或构建时注入,避免将生产密钥提交到Git。
- 应用内密钥派生:用户主密码 → 派生密钥(argon2) → 加密其他密钥 → 本地存储密文。这样即便设备丢失,不知道主密码也无法解密。
- 临时缓存密钥:用Rust的内存安全特性,可以在结构体上实现
Drop时主动清零敏感数据。例如使用zeroize库:
use zeroize::Zeroize;
struct SensitiveToken(Vec<u8>);
impl Drop for SensitiveToken {
fn drop(&mut self) {
self.0.zeroize();
}
}
确保密钥在离开作用域后不会残留在堆上。
- Tauri前端不要持有密钥:仅将密钥保存在Rust侧,前端通过受控命令请求操作(如“用当前密钥解密某个文件”),而不直接获取密钥本身。
3. 日志脱敏:避免不经意间泄露秘密
日志是排查问题的利器,但也容易成为泄密源头。你必须在输出日志前对敏感字段进行过滤。
实用规则:
- 黑名单字段:自动将“password”“token”“secret”“key”等字段的值替换为
[REDACTED]。 - 格式化前拦截:在Rust中,可以用一个轻量的宏封装日志调用,检查是否包含敏感键名。
macro_rules! log_safe {
($msg:expr, $($key:ident = $val:expr),*) => {{
let mut sanitized = String::new();
$(
if stringify!($key).contains("token") || stringify!($key).contains("password") {
sanitized.push_str(&format!("{}={} ", stringify!($key), "[REDACTED]"));
} else {
sanitized.push_str(&format!("{}={:?} ", stringify!($key), $val));
}
)*
eprintln!("[LOG] {} {}", $msg, sanitized);
}};
}
// 使用
log_safe!("User login", username = "alice", password = "s3cret");
// 输出: [LOG] User login username="alice" password=[REDACTED]
- 结构化日志脱敏:若使用
tracing或log,可自定义Layer对fields做脱敏处理。 - 避免完整打印请求/响应体:如果必须记录,只记录长度或哈希值,例如
sha256(token),方便问题定位又不暴露原文。
一句话总结
用 aes-gcm 加密本地数据,用 argon2 派生密钥且绝不硬编码,日志中对 token/password 无条件脱敏——这三板斧能让你的桌面应用在安全审计时从容过关,也真正保护了用户的隐私。