人人都会AI编程

21.5 敏感数据安全:本地加密、密钥管理、日志脱敏

更新时间:2026-07-11

处理用户数据时,哪怕应用只在本地运行,也不能把敏感信息(如密钥、令牌、密码、日志中的个人信息)以明文形式随意存放。这一节我们聚焦三个关键点:本地加密存储密钥管理日志脱敏,给出可直接落地的方案。


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,随后部分为密文,用相同密码恢复原文。

生产建议

  • 使用 argon2pbkdf2 将口令派生为安全密钥,不要直接当字节用。
  • 为每个加密条目随机生成独立 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]
  
  • 结构化日志脱敏:若使用 tracinglog,可自定义 Layerfields 做脱敏处理。
  • 避免完整打印请求/响应体:如果必须记录,只记录长度或哈希值,例如 sha256(token),方便问题定位又不暴露原文。

一句话总结

aes-gcm 加密本地数据,用 argon2 派生密钥且绝不硬编码,日志中对 token/password 无条件脱敏——这三板斧能让你的桌面应用在安全审计时从容过关,也真正保护了用户的隐私。