写在前面
从普遍理性而论,我们能找到的成熟类库通常不会有大的问题,如果发现计算结果不符预期,应优先分析是否是自己的使用方式有问题。(当然也确实存在类库的实现有问题的情况,不能完全排除)
结论先行
关于文章 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编码问题,有如下结论:
QString::toUtf8()
对应编码格式为UTF-8
。QString::toLocal8Bit()
对应本地编码格式。(根据文中给出的方式查询chcp
,确实为936
,但调用QTextCodec::codecForLocale()->name()
查询返回的结果为GBK
,因此文中描述可能是不准确的,936
表示GB2312
的超集 ——GBK
,甚至GBK
的超集GB18030
的可能性更大)QString::toLatin1()
对应编码格式为ISO 8859-1
。(在 qt中的toUtf8, toLatin1, Local8bit, toUcs4 中有指出,其为ASCII
的超集,编码范围为0x00
到0xFF
,很明显的单字节编码)
简单编写 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 C4
,QString::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 值。
总结
QCryptographicHash
类支持含中文的字符串的 MD5 计算。- 在使用
QCryptographicHash
类计算字符串的 MD5 值时,应当明确使用哪个编码转换函数进行转换(通常使用QString::toUtf8()
函数即可)。 - 在使用
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 值,即可得到正确的结果。