1

我试图为节点的内置加密模块(特别是 scrypt)找到比较或验证功能,因为我使用的大多数密码散列模块都有这样的功能。然后,我发现了为什么这是一项不可能完成的任务:使用这些算法生成的所有哈希使用相同的参数生成相同的字符串(技术上是缓冲区)。crypto许多的散列函数就是这种情况,包括它的pbkdf2实现。

为什么这样安全?密码/消息散列函数的全部(现代)点不是您不能使用相同的输入再次生成相同的密码/消息吗?这就是各种 bcrypt 模块的工作方式,以及 scrypt 的原始版本,内置版本,即我要询问的版本,就是从中派生出来的。

例如:

let scryptHash1;
let scryptHash2;
let scryptHash3;

let pbkdfHash1;
let pbkdfHash2;
let pbkdfHash3;

const key1 = 'my secret key';
const key2 = 'my other secret key';

const salt = 'my salt';

crypto.scrypt(key1, salt, 16, hash => scryptHash1 = hash);
crypto.scrypt(key1, salt, 16, hash => scryptHash2 = hash);
crypto.scrypt(key2, salt, 16, hash => scryptHash3 = hash);

scryptHash1.toString() === scryptHash2.toString(); // true
scryptHash1.toString() === scryptHash3.toString(); // false

crypto.pbkdf2(key1, salt, 16, 16, 'sha256', hash => pbkdfHash1 = hash);
crypto.pbkdf2(key1, salt, 16, 16, 'sha256', hash => pbkdfHash2 = hash);
crypto.pbkdf2(key2, salt, 16, 16, 'sha256', hash => pbkdfHash3 = hash);

pbkdfHash1.toString() === pbkdfHash2.toString(); // true
pbkdfHash1.toString() === pbkdfHash3.toString(); // false

我最初是在 上问这个问题的Cryptography,因为我更关心安全性而不是其他任何事情,因为我想从 移动bcryptscrypt。然而,正如多人指出的那样,正如我所担心的那样,问题更多的是关于 API 设计。话虽如此,任何公认的答案都应该包括为什么这种方法是安全的,或者足够安全以进行切换(承认“足够安全”永远不够安全)。我的专业是安全,但我现在是一名网络开发人员,安全一直在变化,尽管核心概念基本保持不变。

4

1 回答 1

7

您似乎对密码哈希有一些基本的误解。首先,就像任何散列函数一样,密码散列函数也是数学意义上的函数。即,它只是一个映射,将其范围内的固定值分配给其输入域的每个元素。

密码散列与常规散列的区别在于两点:首先,它们被设计为速度慢和/或在评估时使用大量内存。(这与我们在这里的讨论无关。)其次,他们需要第二个输入,盐。

对于密码散列函数 H,您希望对于任何固定密码 m 和任何两个盐 s≠ s' 它不仅保持 H(m,s)≠ H(m,s'),而且还给定散列值和盐您应该无法检测到它们是相同 m 的哈希值。

您似乎对 API 设计的不同选择感到困惑。具体来说,谁可以选择盐。每次对新密码 m 进行散列(例如输入数据库)时,应选择一个新的均匀随机盐 s,然后计算散列值 h:=H(m,s) 并存储 h 和 s在数据库中。每当有人声称是同一用户提交密码 m' 以验证他们自己时,就会检索 (h,s) 并检查是否 h=H(m',s)。

现在的问题是谁选择盐。您熟悉的 API 似乎不信任用户这样做。因此,当您调用哈希密码 m 时,库将选择一个盐 s,计算 h 并输出 h'=(h,s) 作为“哈希值”。要检查密码 m' 是否正确,然后提交 h',m' 并且库将提取盐,重新计算哈希并进行比较。

您现在正在查看的库希望用户选择盐。即,每次您在密码数据库中创建一个新条目时,必须选择一个新的盐,计算 h=H(m,s) 并存储两者 (h,s)。由于在这种情况下库不会尝试向您“隐藏”任何内容,因此您需要注意比较。

于 2018-12-15T18:59:26.123 回答