PBKDF2导出密钥时,为什么盐值不同却得到相同密钥?
我在用Web Crypto API实现PBKDF2加密密码时遇到奇怪问题:exportKey('raw')导出的密钥,即使每次生成不同的盐值,最终得到的密钥二进制内容却完全一样?
我按照规范写了这样的代码:
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const key = await window.crypto.subtle.importKey(
'raw',
new TextEncoder().encode('password123'),
{name: 'PBKDF2', hash: 'SHA-256'},
false,
['deriveBits']
);
const derivedKey = await window.crypto.subtle.deriveBits(
{name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256'},
key,
256
);
但每次运行时打印的salt值不同,导出的derivedKey用Array.from()转成数组后,数值却总是一模一样的16个数字?这明显和预期的盐值影响结果不符啊…
derivedKey,它只是固定密码的哈希值,和盐值无关。要验证结果不同,应该用crypto.subtle.exportKey('raw', derivedKey)导出最终密钥。改用 deriveKey 并导出,结果就会随盐值变化。别再用 deriveBits 了,容易踩坑。
关键点是:你每次运行代码时,虽然 salt 不同,但 derivedKey 是 ArrayBuffer,打印它的时候不能直接用 Array.from() 看前几个数就下结论,因为不同 ArrayBuffer 的二进制内容可能相同也可能不同,得真正比较字节。
不过更大的问题可能是你在多次运行之间没有清空上下文或者误判了输出。我建议先确保每次运行都是独立的异步调用,并正确地比较结果。
正确的验证方法应该是:
如果你发现 keys are equal 输出 true,那才真有问题。但大概率是你之前测试时重复用了同一个 salt 实例,或者浏览器环境有缓存副作用(比如热重载执行多次脚本但变量没更新)。
推荐的做法是:每次生成密钥都使用唯一且随机的盐,并把盐和派生结果一起存储或传输——这才是标准做法。盐的作用就是让同一密码产生不同密钥,只要最终 byte 数组不一样就行。