使用 Ring 构建安全的 Rust 应用:现代开发者的内存安全密码学
Source: Dev.to
引言
当你编写处理机密信息——密码、金融数据、私人消息——的代码时,容不得任何失误。哪怕是一个字节放错位置或是一次计时差异,都可能导致全部崩溃。历史上,密码学一直是个令人望而生畏的任务,因为许多库都是用 C 编写的,完全信任程序员本身。缓冲区溢出、内存泄漏以及侧信道攻击都是常见的陷阱。
为什么选择 Rust 和 Ring?
Rust 在代码运行前就阻止了整类错误。Ring crate 基于 Rust 的安全保证,提供以安全为首要前提的密码学原语。其核心使用安全的 Rust 编写,剩余的少量 C 组件经过严格审查并实现为常量时间,从设计上消除了许多漏洞类别。
核心特性
- 哈希函数:SHA‑256、SHA‑512 等。
- 安全随机数生成。
- 数字签名:Ed25519 等。
- 密钥协商:Diffie‑Hellman、X2550。
- 密码哈希:PBKDF2。
生成安全随机数
在密码学中,你需要不可预测的随机性。Ring 提供了一个直接的 API,使用操作系统的安全随机源。
use ring::rand;
fn get_secure_random() -> Result {
let rng = rand::SystemRandom::new();
let mut random_bytes = [0u8; 32]; // 32 random bytes
rng.fill(&mut random_bytes)?;
Ok(random_bytes)
}
SystemRandom 在 Linux 上从 /dev/urandom(或平台特定的源)读取,并保证要么操作成功,要么返回错误——不存在隐藏的失败模式。
使用 PBKDF2 进行密码哈希
永远不要直接存储密码。应存储加盐、故意慢速的哈希,以防止彩虹表攻击。Ring 提供了 PBKDF2 原语。
use ring::{digest, pbkdf2};
use std::num::NonZeroU32;
fn hash_password(password: &str, salt: &[u8]) -> Vec {
let iterations = NonZeroU32::new(100_000).expect("Non-zero iteration count");
let mut derived_key = vec![0u8; digest::SHA256.output_len]; // 32‑byte output
pbkdf2::derive(
pbkdf2::PBKDF2_HMAC_SHA256,
iterations,
salt,
password.as_bytes(),
&mut derived_key,
);
derived_key
}
fn verify_password(password: &str, salt: &[u8], stored_hash: &[u8]) -> bool {
let iterations = NonZeroU32::new(100_000).unwrap();
pbkdf2::verify(
pbkdf2::PBKDF2_HMAC_SHA256,
iterations,
salt,
password.as_bytes(),
stored_hash,
)
.is_ok()
}
NonZeroU32 确保迭代次数永远不为零,从编译时就防止了关键的安全错误。每个密码都应拥有唯一的、随机的盐,并与派生的哈希一起存储。
数字签名
签名用于验证真实性和完整性。Ring 支持现代算法,如 Ed25519。
use ring::{rand, signature};
use ring::signature::KeyPair;
fn sign_a_message() -> Result> {
// 1. Secure RNG
let rng = rand::SystemRandom::new();
// 2. Generate a private/public key pair (PKCS#8)
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng)?;
// 3. Create a key pair object
let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref())?;
// Public key (shareable)
let public_key_bytes = key_pair.public_key().as_ref();
// 4. Sign a message
let message = b"Critical system update v2.1";
let signature = key_pair.sign(message);
// 5. Verification (performed by the receiver)
let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, public_key_bytes);
match peer_public_key.verify(message, signature.as_ref()) {
Ok(()) => println!("Signature is valid. Update is authentic."),
Err(_) => println!("DANGER: Signature verification failed!"),
}
Ok(())
}
私钥(pkcs8_bytes)必须保密,而公钥可以自由分发。API 明确地强制了这种分离。
与 OpenSSL 的对比
OpenSSL 是一个庞大的 C 语言库。其广度带来了复杂性和大量严重漏洞的历史(例如 Heartbleed)。Ring 采用了不同的方式:
- 更小、更精挑细选的 API:仅提供现代、经过审查的算法。
- 内存安全:Rust 代码消除了许多错误类别。
- 常量时间实现:降低侧信道风险。
这种聚焦的设计引导开发者在不受遗留选项负担的情况下,正确且安全地使用密码学功能。
密钥协商(Diffie‑Hellman)
Ring 也提供安全的密钥交换原语(例如 X25519)。虽然完整示例超出本段范围,但典型工作流包括:
- 生成临时密钥对。
- 交换公钥。
- 使用
agreement::agree_ephemeral派生共享密钥。
所有操作都是常量时间,并使用安全的 Rust 抽象实现。