我对密码散列有什么误解?

What am I misunderstanding about password hashing?

据我了解,哈希函数在输入相同数据时总是 return 相同的结果。但我一直在使用 libsodium(通过 node-sodium),但事实并非如此。

我的架构中有这个:

UserSchema.pre('save', function(next) {
    // declare my variables       
    let user = this,
        buf = Buffer.alloc(sodium.crypto_pwhash_STRBYTES, 'ascii'),
        passwordBuf = Buffer.from(user.password, 'ascii'),
        saltedPassBuf,
        hash;
    // only hash the password if it has been modified (or is new)
    if (!user.isModified('password')) return next();
    // generate a salt
    sodium.randombytes_buf(buf, sodium.crypto_pwhash_STRBYTES, 'ascii');
    // add salt to the password
    saltedPassBuf = Buffer.concat([passwordBuf, buf], 128);
    // hash it separately multiple times
    // note, i'm not hashing the hash,
    // I'm hashing the original buffer to see what happens
    // this has no application in production
    hash = sodium.crypto_pwhash_str(saltedPassBuf, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);
    hash2 = sodium.crypto_pwhash_str(saltedPassBuf, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);
    hash3 = sodium.crypto_pwhash_str(saltedPassBuf, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);
    // log it to see what I got -- not for production
    console.log(hash.toString());
    console.log(hash2.toString());
    console.log(hash3.toString());
    // save the salt and the buffer for authentication
    user.salt = buf;
    user.password = hash;
    next();
});

我用该代码记录了三个不同的字符串。例如

$argon2i$v=19$m=32768,t=4,p=1$ayPVQ1X+xNhWmD9S5AUuawmWusk59AebhzOHhl+j5JpvmRI27Pq57XG5zcAB5R4U
$argon2i$v=19$m=32768,t=4,p=1$PjTYKpfhh1bZh+MV84Y9kA+U33nf6efuugsrz15cEKDa5+rAHgYVA5Kqo4F1G3DE
$argon2i$v=19$m=32768,t=4,p=1$Ii8AErmAFc0na9Yi2OgCkw$ySU80Fv9OiOmeT9EV/BWon1Jjck2Lx23nOeCk0wkMPU

现在每一个的第一部分都是相同的,这让我觉得提交的密码部分是相同的(因为它是正在被散列的缓冲区的第一部分)。所以也许这是我不明白的缓冲区。

但如果 buf 保持不变,为什么 saltedPassBuff 的其余部分会发生变化?

edit: 不小心提交的时候还没写完, edit 写完题目

顾名思义,输出是加盐的。这意味着在散列之前将随机字符串添加到密码中,并单独包含在输出值中。

这样做的目的是打败dictionary attacks。通过在散列之前向每个密码添加一个随机字符串,您可以确保相同的密码将以不同的方式散列,迫使攻击者分别破解每个密码。

除了您的盐之外,pwhash 函数(文档很少)很可能还添加了它自己的随机盐,它也包含在结果中,以便稍后使用 crypto_pwhash_str_verify.[=12 进行比较=]

还有一个"CPU intensive"方面,大概是迭代。仅仅使用带有盐的散列函数对提高安全性没有多大帮助。需要添加 CPU 密集组件,例如迭代。

重点是让攻击者花费大量时间通过暴力破解密码。