关于密码哈希的一点点
PBKDF2 是一个基于密码的密钥派生函数,类似于 bcrypt、scrypt 和 Argon2,其中 Argon2 是 2015 年密码哈希竞赛的获胜者(尽可能使用 Argon2id)。他们过去常常从密码中获取密钥。那么问题来了
为什么我们需要一个特殊的函数来从密码中获取密钥,而不是仅仅使用hash(password)?
答案取决于历史的进步。首先,记住攻击总是会变得更好,它们永远不会变得更糟。 Philippe Oechslin 介绍的彩虹桌
实际上是 Merkle-Hellman 工作的延伸,并应用于实际案例。
- 1980 - Hellman, M. “密码分析时间记忆权衡
彩虹桌
Rainbow 表仅用于反转任何函数,例如加密和散列函数。在谈论哈希函数的反向时,必须小心,在这种情况下,它只是为给定的哈希值y找到一个原像x,这样y=hash(x)。如今,人们可以在网络上找到许多预先构建的彩虹表。
彩虹桌的防御措施是什么?每个密码使用不同的盐。在这种情况下,彩虹表是无用的。它们需要按盐建造,这太昂贵了。因此,我们可以说使用盐会杀死 Rainbows 表。
海量 GPU/ASIC/FPGA 搜索
虽然人们也可以使用 CPU 进行大规模并行化以进行密码搜索,但使用 GPU、ASIC/FPGA(其余部分将仅使用 GPU 一词)更有效,因为 CPU 是通用处理单元,而不是专用处理单元.
攻击者可以同时使用多个 GPU 来搜索密码。一个很好的例子是 hashcat。攻击者可以并行使用多个实例来搜索密码。这只是一个并行的蛮力机器。
有什么对策?
-
迭代次数:可以增加迭代次数。假设您使用 SHA 进行密码散列,然后 x-iteration 调用 SHA x 次 SHA(SHA(...(SHA(x)...))。这会使你减慢x 倍,但是,它们也会减慢攻击者x 倍。
PBKDF2 将迭代作为参数来控制这一点。必须注意迭代计数,您可能不希望您的用户等待登录。自定义调整此值以使用户等待不超过一秒钟。我们今天可以看到的 100K、250K、500K、1M 次迭代。考虑到即使您使用 100K 迭代,您也会将密码搜索速度减慢 100K 次。
-
Memory-Hard:在这种情况下,密码散列算法使用的是可调整的内存,不能与时间内存或类似技术收缩,因此它们也消除了大量搜索。 GPU 没有太多内存来提供所有线程,因此它们严重瘫痪。
-
并行度:Argon2 也使用并行化参数。这也减少了攻击者在 CPU 上的并行运行。
回到你的编码
myPassword = user.uid+"QWERTYUIOPLKJHGASDFZXCVMNBqwertyuiopasdfghjklzxcvbnm1234567890!@#$)(*&^%-+=><,.:;{}[]";
encrypted = CryptoJS.AES.encrypt(document.getElementById("id").value, myPassword);
当您以这种方式使用 CryptoJS 加密时,它使用非标准化的 OpenSSL KDF 进行密钥派生 (EvpKDF),并使用 MD5 作为散列算法和 1 次迭代。
虽然 MD5 仍然可以防止前映像,但它对于密码散列的速度很快。因此你需要改变它。您可以使用 PBKDF2 加密as
function encrypt (msg, pass) {
var salt = CryptoJS.lib.WordArray.random(128/8);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var iv = CryptoJS.lib.WordArray.random(128/8);
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
// salt, iv will be hex 32 in length
// append them to the ciphertext for use in decryption
var transitmessage = salt.toString()+ iv.toString() + encrypted.toString();
var encodeB4 = CryptoJS.enc.Base64.stringify(transitmessage);
return encodeB4;
}
function decrypt (transitmessage, pass) {
var decoded = const decoded = CryptoJS.enc.Utf8.stringify(transitmessage);
var salt = CryptoJS.enc.Hex.parse(decoded.substr(0, 32));
var iv = CryptoJS.enc.Hex.parse(decoded.substr(32, 32))
var encrypted = decoded.substring(64);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return decrypted;
}