Qt 的 QCryptographicHash
属于【通用的加密哈希函数】,而不是【专门的密码哈希算法】。
把它归类到和 MD5、SHA256 一样的类别里是最准确的。下面进行详细解释:
1. 核心定位:通用哈希计算工具
QCryptographicHash
的设计初衷是一个工具类,用于计算任意二进制数据(比如文件、字节流)的加密哈希值,以实现:
- 数据完整性校验:验证下载的文件是否完整、未被篡改。
- 数字签名:作为签名算法的一部分。
- 生成唯一标识符:根据内容生成短且唯一的密钥(例如,用于缓存文件名)。
- 快速哈希计算:在任何需要 MD5、SHA1 等哈希值的通用场景中使用。
2. 为什么它不适合用于密码加密?
正是由于其"通用"和"工具"的属性,它具备了和 MD5 一样的、导致其不适合用于密码的所有缺陷:
- 纯粹且快速的哈希计算 :它只是单纯地执行所选算法(如 MD5, SHA256)的哈希计算,速度极快。这对于校验文件是优点,但对于密码存储却是致命弱点,因为攻击者可以高速进行暴力破解。
- 不自动加盐(Salt) :这个类不会 自动为你生成和管理盐值。如果你需要加盐,必须自己手动 生成一个随机盐,将其与密码组合,然后再调用
QCryptographicHash
进行计算。并且,你还需要自己安全地存储这个盐值(通常和哈希值存在一起)。忘记加盐或错误地加盐都会导致严重的安全问题。 - 不涉及成本因子(迭代次数) :像 bcrypt 这样的密码哈希算法允许你设置一个"工作因子"(或迭代次数),从而可以故意减慢计算速度,以抵御暴力破解。
QCryptographicHash
没有这个功能,它的计算成本是固定的、且非常低。 - 不支持自适应算法:它提供的算法(MD5, SHA1, SHA256等)是固定的。而密码学的最佳实践是在发展变化的,如果未来发现 SHA256 也不安全了,你需要自己重构所有代码来更换算法。而 Qt 的密码哈希功能(后文会讲)或其它现代库会自动选择当前最安全的算法。
一个危险的示例(请不要这样做):
cpp
// ⚠️ 警告:这是一个不安全的示例,仅用于演示问题,切勿在实际项目中使用!
QString password = "myPassword123";
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(password.toUtf8());
QString hashedPassword = hash.result().toHex();
// 存储 hashedPassword
这个例子的问题:
- 没有加盐。
- SHA256 计算速度太快。
- 没有成本因子。
3. 在 Qt 中正确加密密码的方法
从 Qt 5.12 版本开始 ,Qt 提供了一个专门用于密码哈希的类:QPasswordDigestor
。
这个类才是 Qt 世界中用于密码的正确答案,它遵循了现代密码存储的最佳实践。
推荐使用 QPasswordDigestor
:
cpp
#include <QPasswordDigestor>
#include <QRandomGenerator>
// 1. 生成一个随机的盐值(Salt)
QByteArray salt(16, '\0'); // 生成16字节的盐值
QRandomGenerator::system()->fillRange(reinterpret_cast<quint32*>(salt.data()), salt.size() / sizeof(quint32));
// 2. 定义计算参数:算法、迭代次数
// 迭代次数越高越安全,但也越慢。需要根据硬件性能权衡(通常数万到数十万次)
int iterations = 100000;
QPasswordDigestor::DerivationKey derivationKey = QPasswordDigestor::DerivationKey::Pbkdf2;
// 3. 使用 PBKDF2 算法派生密钥(即计算密码哈希)
QByteArray passwordData = "myPassword123".toUtf8();
QByteArray hashedPassword = QPasswordDigestor::deriveKeyPbkdf2(
QCryptographicHash::Sha256, // 内部使用SHA256,但被PBKDF2包装了
passwordData,
salt,
iterations, // 关键!增加了计算成本
32 // 输出哈希值的长度(例如32字节 for SHA256)
);
// 4. 存储时,必须同时存储【哈希值】、【盐值】和【迭代次数】
// 格式可以是:`算法$迭代次数$盐$哈希` 或分开存储在数据库字段中
// 例如: "pbkdf2_sha256$100000$abcdef123456...$789abcdeff..."
关键改进:
- 加盐:强制要求你提供随机盐。
- 慢 :通过高迭代次数故意减慢计算速度,极大增加暴力破解成本。
- 使用标准算法 :实现了 PBKDF2,这是一个基于标准且经过验证的密钥派生函数,常用于密码哈希。
总结对比
特性 | QCryptographicHash |
QPasswordDigestor (推荐用于密码) |
---|---|---|
设计目的 | 通用数据哈希 | 密码哈希 |
速度 | 快(用于校验数据) | 可调节的慢(用于阻止破解) |
盐值 | 不提供,需手动实现 | 需要手动提供,但为必选项 |
成本因子 | 无 | 有(迭代次数) |
适用场景 | 文件校验、生成唯一ID | 用户密码存储 |
结论:
不要使用 QCryptographicHash
来直接加密密码。 它是为完全不同的任务而设计的。
对于 Qt 应用程序,如果你的版本 >= 5.12,请使用 QPasswordDigestor
。如果你的 Qt 版本较老,则应使用其他专门的三方库(例如基于 OpenSSL 的实现)来正确处理密码。