关于 QCryptographicHash 对含中文字符串计算 MD5 值,产生所谓「MD5 碰撞」问题的分析

写在前面

从普遍理性而论,我们能找到的成熟类库通常不会有大的问题,如果发现计算结果不符预期,应优先分析是否是自己的使用方式有问题。(当然也确实存在类库的实现有问题的情况,不能完全排除)

结论先行

关于文章 QT之MD5加密——QCryptographicHash类不适用于含中文的字符串 的结论有误,实际为选取的编码转换格式有误而导致所谓的「MD5 碰撞」,并不是 QCryptographicHash 本身无法支持含中文的字符串的 MD5 计算。

只要使用 QString::toUtf8() 函数进行编码转换,就可以得到预期的 MD5 值。

基本用法

QCryptographicHash 类中,QByteArray QCryptographicHash::hash(const QByteArray &data, Algorithm method) 函数的 data 参数是 QByteArray 类型,而 QString 类型的字符串可以通过 QByteArray QString::toUtf8() 函数转换为 QByteArray 类型。

QString str = "中文";
QByteArray data = str.toUtf8();
QByteArray md5 = QCryptographicHash::hash(data, QCryptographicHash::Md5);

不同编码转换函数的差异

在文章 QT之MD5加密——QCryptographicHash类不适用于含中文的字符串 中,作者使用了 QString::toLatin1() 函数进行编码转换,那我们看一下不同编码转换函数的差异。

参考文章 qt中的toUtf8, toLatin1, Local8bit编码问题,有如下结论:

  1. QString::toUtf8() 对应编码格式为 UTF-8
  2. QString::toLocal8Bit() 对应本地编码格式。(根据文中给出的方式查询 chcp,确实为 936,但调用 QTextCodec::codecForLocale()->name() 查询返回的结果为 GBK,因此文中描述可能是不准确的,936 表示 GB2312 的超集 —— GBK,甚至 GBK 的超集 GB18030 的可能性更大)
  3. QString::toLatin1() 对应编码格式为 ISO 8859-1。(在 qt中的toUtf8, toLatin1, Local8bit, toUcs4 中有指出,其为 ASCII 的超集,编码范围为 0x000xFF,很明显的单字节编码)

简单编写 demo,调用 QString::toUtf8()QString::toLocal8Bit()QString::toLatin1() 函数,并将转换后的 QByteArray 类型数据转换为十六进制字符串格式,得到了如下结果:

str: "中文English123."
str to utf-8: "E4 B8 AD E6 96 87 45 6E 67 6C 69 73 68 31 32 33 2E"
str to local8Bit:   "D6 D0 CE C4 45 6E 67 6C 69 73 68 31 32 33 2E"
str to latin1:            "3F 3F 45 6E 67 6C 69 73 68 31 32 33 2E"

str: "中问English123."
str to utf-8: "E4 B8 AD E9 97 AE 45 6E 67 6C 69 73 68 31 32 33 2E"
str to local8Bit: "  D6 D0 CE CA 45 6E 67 6C 69 73 68 31 32 33 2E"
str to latin1:            "3F 3F 45 6E 67 6C 69 73 68 31 32 33 2E"

可以看到,QString::toUtf8() 函数将中文字符转换为了 E4 B8 AD E6 96 87,而 QString::toLocal8Bit() 函数将中文字符转换为了 D6 D0 CE C4QString::toLatin1() 函数将中文字符转换为了 3F 3F。这就是为什么在文章 QT之MD5加密——QCryptographicHash类不适用于含中文的字符串 中,作者使用了 QString::toLatin1() 函数进行编码转换后,得到了「错误的」 MD5 值。

实际上文章中的 MD5 值计算结果并没有任何问题,也不是所谓的「MD5 碰撞」。因为输入的 QByteArray 数据就是一致的,因此得到的 MD5 值也是一致的。只是因为编码转换的问题,导致了输入的 QByteArray 数据和预期不符(中文部分都被转换为了 3F),从而得到了错误的 MD5 值。

通常意义上使用各种在线 MD5 计算工具,都是以 UTF-8 编码进行计算的,因此在使用 QCryptographicHash 类进行 MD5 计算时,应当使用 QString::toUtf8() 函数进行编码转换。

起因

调用如下函数,发现含中文的字符串无法正确计算 MD5 值:

QString GetStringMD5(const QString& str)
{
    QString md5;
    QByteArray ba;
    ba = QCryptographicHash::hash(str.toLatin1(),QCryptographicHash::Md5);
    md5.append(ba.toHex());
    return md5;
}

但调用如下函数计算文件的 MD5 值却没有问题(即使文件内容中含有中文):

QString GetFileMD5(const QString& fileName)
{
    QString md5;
    QFile file(fileName);
    if (file.open(QIODevice::ReadOnly))
    {
        QByteArray ba;
        ba = QCryptographicHash::hash(file.readAll(),QCryptographicHash::Md5);
        md5.append(ba.toHex());
        file.close();
    }
    return md5;
}

对比二者差异,前者是 QString::toLatin1() 函数进行编码转换后输入的 QByteArray 数据,而后者是 QFile::readAll() 函数直接读取文件内容后输入的 QByteArray 数据。既然输入内容都是 QByteArray 类型,那么问题不应该和是否含有中文或其他任何特殊字符有关。最终发现是编码转换的问题,QString::toLatin1() 函数将所有中文字符转换为了 3F,从而导致最终得到了错误的 MD5 值。

总结

  1. QCryptographicHash 类支持含中文的字符串的 MD5 计算。
  2. 在使用 QCryptographicHash 类计算字符串的 MD5 值时,应当明确使用哪个编码转换函数进行转换(通常使用 QString::toUtf8() 函数即可)。
  3. 在使用 QCryptographicHash 类计算文件的 MD5 值时,不需要关心文件内容中是否含有中文或其他任何特殊字符。调用 QFile::readAll() 函数直接读取文件内容得到的 QByteArray 数据即可直接用于计算 MD5 值。

补充

额外记录一个类似的情况:

存在 MD5 计算函数 QString GetFileMD5(ifstream& ifs),需传入参数 ifstream 类型的文件流。(这里不是使用 QCryptographicHash 类计算文件的 MD5 值,而是使用其他方式计算。非重点,此处不赘述。)

实际使用时,发现如下调用:

ifstream ifs(fileName);
QString md5 = GetFileMD5(ifs);

实测发现,GetFileMD5 函数计算的 MD5 值并不正确。
对于上述调用,创建文件流 ifstream 时,需要指定打开方式,如:

ifstream ifs(fileName, ios::binary)

再次测试调用 GetFileMD5 函数计算 MD5 值,即可得到正确的结果。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值