如何安全地比较两个有限长度的字符串,以保证每次比较都花费同样的时间?哈希具有时序攻击漏洞,无法使用。
有没有一种不使用哈希算法的方式来比较两个字符串,同时又不容易受到时序攻击的威胁呢?
TL;DR: 使用汇编语言。
常数时间代码真的很难实现。要真正实现常数时间,你需要:
什么是“常数时间算法”?
字符串比较的例子很好。大多数情况下,你希望比较所需的时间尽可能短,这意味着在发现第一个不同之处时立即退出:
fn simple_compare(a: &str, b: &str) -> bool {
if a.len() != b.len() { return false; }
for (a, b) in a.bytes().zip(b.bytes()) {
if a != b { return false; }
}
true
}
然而,常数时间版本的算法应该无论输入如何都具有恒定的时间:
Lukas提供的算法几乎正确:
/// Prerequisite: a.len() == b.len()
fn ct_compare(a: &str, b: &str) -> bool {
debug_assert!(a.len() == b.len());
a.bytes().zip(b.bytes())
.fold(0, |acc, (a, b)| acc | (a ^ b) ) == 0
}
"常数时间实现"是什么意思?
即使算法是常数时间的,实现也可能不是。
如果没有使用完全相同的CPU指令序列,则在某些体系结构上,其中一个指令可能更快,而另一个指令可能更慢,那么该实现将失败。
如果算法使用表格查找,则可能会出现更多或更少的缓存未命中。
你能在Rust中编写字符串比较的常数时间实现吗?
不能。
Rust语言可能适用于此任务,但是其工具链却不适合:
简而言之,今天,从Rust访问常数时间实现的唯一方法是在汇编中编写该实现。
fn ct_compare(a: &str, b: &str) -> bool {
if a.len() != b.len() {
return false;
}
a.bytes().zip(b.bytes())
.fold(0, |acc, (a, b)| acc | (a ^ b) ) == 0
}
当然,这个算法可以轻松地推广到所有实现了AsRef<[u8]>
的内容。这留给读者作为练习;-)
看起来已经有一个crate提供了这种类型的比较:consistenttime
。我没有测试过,但文档看起来非常好。