我试图为节点的内置加密模块找到一个比较或验证函数,特别是针对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
上问这个问题,因为我更关心安全性,因为我想从bcrypt
转到scrypt
。然而,正如许多人所指出的,正如我所担心的,问题更多地是关于API设计。话虽如此,任何公认的答案都应该包括为什么这种方法是安全的,或者足够安全可以切换(承认"足够安全"永远不够安全(。我主修安全,但我现在是一名网络开发人员,安全一直在变化,尽管核心概念基本保持不变。
您似乎对密码哈希有一些根本性的误解。首先,就像任何哈希函数一样,密码哈希函数也是数学意义上的函数。也就是说,它只是一个映射,将其范围中的固定值分配给其输入域的每个元素。
将密码哈希与常规哈希区分开来的是两件事:首先,它们在计算时速度较慢和/或使用大量内存。(这与我们在这里的讨论无关。(第二,他们接受了第二个输入,盐。
对于密码哈希函数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时,库将选择salt s,计算h并输出h'=(h,s(作为"哈希值"。要检查密码m'是否正确,请提交h',m',库将提取salt,重新计算哈希并进行比较。
您现在正在查看的库希望用户选择盐。也就是说,每次在密码数据库中创建新条目时,都必须选择一个新的salt,计算h=h(m,s(并存储两者(h,s(。由于本例中的库不会试图向您"隐藏"任何内容,因此您需要注意比较。