Python中的Hashlib md5对某些Unicode字符返回不正确的摘要?
我最近在玩Python和Java的MD5实现,遇到了一些让我困惑的小问题。
下面这个Python脚本展示了这个问题:
# -*- coding: utf-8 -*-
import hashlib
def md5hash(x):
m = hashlib.md5()
m.update(x)
return m.hexdigest()
print md5hash('\xdb')
print md5hash('Û')
输出结果:
98fd00d788afe2a5fa5e4f8e1666638b
31ecfb09f120720a55d96a2034f5d00b
我原本以为这两个结果应该是一样的,因为Û
应该等于\xdb
。于是我在Java中写了一个类似的实现,想更深入了解一下:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Test {
public static void main(String[] args) throws Exception {
MessageDigest m = MessageDigest.getInstance("MD5");
m.update("\u00db".getBytes());
System.out.println(bytesToHex(m.digest()));
m.update("Û".getBytes());
System.out.println(bytesToHex(m.digest()));
}
final protected static char[] hexArray = "0123456789abcdef".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
输出结果:
31ecfb09f120720a55d96a2034f5d00b
31ecfb09f120720a55d96a2034f5d00b
Java的输出结果是稳定的,正如我预期的那样。这让我开始怀疑md5hash('\xdb')
的结果可能是错误的,但我不太确定我漏掉了什么。你们有什么想法吗?
2 个回答
3
我原以为这两个摘要是一样的,因为
Û
应该等于\xdb
。
在UTF-8编码中,Û
是C3 9B
,看起来你正在使用这种编码(这是你声明的编码方式)。而DB
则是ISO-8859-1编码。
>>> import hashlib
>>> hashlib.md5(b'\xc3\x9b').hexdigest()
'31ecfb09f120720a55d96a2034f5d00b'
好了!
7
你的假设不对。你在Python代码的开头写了:
# -*- coding: utf-8 -*-
Û
在这种情况下并不等于 \xdb
; 它实际上是两个字节:
>>> u'Û'.encode('utf8')
'\xc3\x9b'
在这一点上,Python是完全一致的:
>>> import hashlib
>>> hashlib.md5('\xc3\x9b').hexdigest()
'31ecfb09f120720a55d96a2034f5d00b'
>>> hashlib.md5('\xdb').hexdigest()
'98fd00d788afe2a5fa5e4f8e1666638b'
在Java中,你是从一个Unicode代码点开始的,然后把它转换成UTF-8字节:
"\u00db".getBytes()
在Python中,等效的做法是使用一个unicode
字符串字面量,并使用\uhhhh
或\xhh
转义序列:
>>> u'\u00db'.encode('utf8')
'\xc3\x9b'
>>> u'\xdb'.encode('utf8')
'\xc3\x9b'
注意前面的u
前缀,这样才能生成一个unicode
字符串。没有u
前缀的\xdb
是一个字节字符串,而不是Unicode代码点,只有当你把它解码为Latin 1时,才能得到相同的Unicode字符串:
>>> '\xdb'.decode('latin1')
u'\xdb'
>>> '\xdb'.decode('latin1').encode('utf8')
'\xc3\x9b'
你可能需要多了解一下Python和Unicode;可以看看:
实用Unicode,作者是Ned Batchelder
为了完整性,推荐以下内容:
- 每个软件开发者绝对必须了解的Unicode和字符集的最低限度知识(没有借口!),作者是Joel Spolsky