← 中文资源

简体中文版本

敏感数据内存擦除

用带 Drop 擦除的包装类型保存密钥,调试输出自动脱敏,并显式清零临时缓冲区。

阅读英文原文

功能说明

密钥读进内存后,即使变量离开作用域,堆上的字节也可能留在未清零的页里,被转储、交换文件或复用内存的进程读到。Zeroize 提供在析构时覆盖内存的机制,ZeroizeOnDrop 派生宏让结构体在 drop 时自动擦除字段,编译器不会把这次写入优化掉。

示例把 API 密钥包进 ApiKey,禁止直接打印明文,只暴露只读访问方法;临时拼接的字符串在拼接完成后可以手动清零,缩短敏感数据的存在时间。

关键代码

结构体派生 Zeroize 与 ZeroizeOnDrop,字段 String 会先被覆写为零再释放。Debug 实现固定输出 REDACTED,避免日志、panic 信息或断言失败时泄露内容。expose 只返回借用,不复制明文,调用方无法长期持有。main 末尾对临时字符串调用 zeroize,再验证长度为零。

使用注意

擦除只能覆盖本进程分配的这块内存,无法清除已经被复制到别处的数据,例如格式化字符串、日志缓冲或操作系统换页。避免派生 Clone,否则明文会有多份副本。不要实现 Display,也不要让密钥进入错误类型。Zeroize 不保证防御所有编译器优化,极端威胁模型下还需配合内存锁定。密钥长度本身也可能敏感。

验证方式

第一行输出 ApiKey([REDACTED]),看不到明文;第二行输出明文长度二十二。临时字符串清零后 is_empty 返回 true,说明缓冲区已被覆盖。可以在作用域结束前打印指针内容做对比,但不要在生产日志中这样做。

代码示例

use std::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};

#[derive(Zeroize, ZeroizeOnDrop)]
struct ApiKey(String);

impl ApiKey {
    fn new(raw: impl Into<String>) -> Self {
        Self(raw.into())
    }

    fn expose(&self) -> &str {
        &self.0
    }
}

impl fmt::Debug for ApiKey {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("ApiKey([REDACTED])")
    }
}

fn main() {
    let key = ApiKey::new("sk-live-example-123456");
    println!("{:?}", key);
    println!("length: {}", key.expose().len());

    let mut temporary = String::from("temporary-secret");
    temporary.zeroize();
    println!("zeroized is empty: {}", temporary.is_empty());
}