Qt5与mkpasswd生成的MD5哈希不一致,如何生成匹配的哈希?
嘿,我来帮你搞清楚这个问题~
你现在遇到的核心问题是:Qt里用QCryptographicHash::Md5生成的是纯MD5哈希,而mkpasswd -s -5生成的是MD5crypt格式的加盐哈希,这俩根本不是同一种东西,所以结果自然不一样。
先说说两者的区别
- 你用Qt写的代码,是直接对字符串
"test"做MD5运算,得到的是原始MD5的十六进制结果,没有加盐,也没有遵循特定的哈希格式。 - 而
mkpasswd -5生成的是Unix系统常用的MD5crypt密码哈希,格式是$1$<salt>$<hashed_value>:$1$是MD5crypt的标识<salt>是随机生成的8位盐(你的例子里是xCs.somY)<hashed_value>是密码加盐后经过MD5变种算法运算得到的结果,还使用了自定义的Base64编码格式
怎么在Qt里生成和mkpasswd一致的结果?
Qt没有直接提供MD5crypt的API,所以我们需要自己实现这个算法。下面是完整的实现代码,你可以直接用:
#include <QCryptographicHash> #include <QRandomGenerator> #include <QString> QString md5Crypt(const QString& password, const QString& salt = QString()) { // MD5crypt专用的字符编码集,和标准Base64不同 const QByteArray charSet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const int maxSaltLen = 8; // 处理盐:如果没传就随机生成8位,传了就取前8位 QByteArray usedSalt = salt.toLatin1(); if (usedSalt.isEmpty()) { usedSalt.reserve(maxSaltLen); for (int i = 0; i < maxSaltLen; ++i) { usedSalt.append(charSet.at(QRandomGenerator::global()->bounded(charSet.size()))); } } else { usedSalt = usedSalt.left(maxSaltLen); } QByteArray passBytes = password.toLatin1(); // 第一步:计算 MD5(password + "$" + salt) QCryptographicHash hash1(QCryptographicHash::Md5); hash1.addData(passBytes); hash1.addData("$"); hash1.addData(usedSalt); QByteArray hash1Res = hash1.result(); // 第二步:计算 MD5(password + salt + password) QCryptographicHash hash2(QCryptographicHash::Md5); hash2.addData(passBytes); hash2.addData(usedSalt); hash2.addData(passBytes); QByteArray hash2Res = hash2.result(); // 第三步:生成混合初始数据 QByteArray temp; temp.reserve(passBytes.size()); for (int i = 0; i < passBytes.size(); ++i) { temp.append(hash1Res.at(i % 16)); } while (temp.size() < passBytes.size()) { temp.append('\0'); } // 第四步:循环混合密码和初始数据 QByteArray hash3Input = passBytes; int len = passBytes.size(); while (len > 0) { hash3Input.append((len & 1) ? temp : passBytes); len >>= 1; } QCryptographicHash hash3(QCryptographicHash::Md5); hash3.addData(hash3Input); QByteArray hash3Res = hash3.result(); // 第五步:重复1000次混合运算(MD5crypt的核心步骤) for (int i = 0; i < 1000; ++i) { QCryptographicHash roundHash(QCryptographicHash::Md5); // 根据奇偶次选择输入内容 if (i & 1) { roundHash.addData(passBytes); } else { roundHash.addData(hash3Res); } // 根据模3、模7决定是否加盐或密码 if (i % 3) roundHash.addData(usedSalt); if (i % 7) roundHash.addData(passBytes); // 再次根据奇偶选择输入 if (i & 1) { roundHash.addData(hash3Res); } else { roundHash.addData(passBytes); } hash3Res = roundHash.result(); } // 第六步:将哈希结果转换成MD5crypt的编码格式 QString result = "$1$" + QString(usedSalt) + "$"; // MD5crypt指定的字节提取顺序 const int posOrder[] = {0, 6, 12, 1, 7, 13, 2, 8, 14, 3, 9, 15, 4, 10, 5, 11}; // 每3个字节一组,编码成4个字符 for (int i = 0; i < 16; ) { int val = (hash3Res.at(posOrder[i]) & 0xff) << 16 | (hash3Res.at(posOrder[i+1]) & 0xff) << 8 | (hash3Res.at(posOrder[i+2]) & 0xff); i += 3; for (int j = 0; j < 4; ++j) { result.append(charSet.at(val & 0x3f)); val >>= 6; } } // 处理最后剩下的1个字节 int finalVal = hash3Res.at(posOrder[15]) & 0xff; result.append(charSet.at(finalVal & 0x3f)); result.append(charSet.at((finalVal >> 6) & 0x3f)); // 截断到标准MD5crypt长度($1$ + 盐 + $ + 22个字符) result = result.left(3 + usedSalt.size() + 1 + 22); return result; }
调用示例(匹配你的mkpasswd结果)
要得到和echo test | mkpasswd -s -5完全一致的结果,你需要指定相同的盐xCs.somY:
QString pass = "test"; QString hash = md5Crypt(pass, "xCs.somY"); qDebug() << hash; // 输出:$1$xCs.somY$b/VXeBeO9DZbqCR/31iPT1
如果不指定盐,函数会随机生成一个8位盐,每次生成的哈希盐部分不同,但都是合法的MD5crypt格式。
内容的提问来源于stack exchange,提问作者Šerg
相关产品推荐
相关产品推荐

