近段时间在分析 QQ、微信的数据库,根据网上一些大佬的解密方法,也成功解密了数据。

但是在做自动化处理时,我发现企鹅的生成的 MD5 有时候与我的工具类生成的值相比最前面多个 0。一开始以为这个 0 有特殊意义,然后为了找到这个“特殊意义”的依据,对着 DB 数据看了两个晚上。

有特殊意义?

最终,功夫不负有心人,让我找到“特殊意义”。然后欢欢喜喜,一口气撸完几个工具类,测试,通过!
真呀嘛真开心,休息一晚补补,不然发际线跟不上了。
后一晚,公司加班,没搞成!
再后一晚,开始分析微信的 DB,又发现了 MD5 多 0 的问题。嗯,一定也是有特殊意义,找找看……
又一晚,突然想到一个问题,会不会是我的代码有问题?然后仔细对比 MD5,发现一个不科学的现象:

  1. //企鹅的MD5
  2. 08711128F207973095353954D9AD22EF
  3. //我的MD5
  4. 8711128F207973095353954D9AD22EF

每次企鹅的 MD5 多 0 的时候,可总位数是 32 位,是 MD5 的长度;而我的只有 31 位,正好少这个 0!!!

PS:不用自己去数数啦,在 AS 里面,选中这个 MD5,AS 底部就会显示都少个字符。

这是咋么肥事?

翻出来我的 MD5 的类,是参考 org.apache.commons.codec.digest.DigestUtils 写的,因为这个包直接导入有编译问题,我也只需这个类,所以就参考它重新写了一次,部分地方自己做了“优化”,代码是这样的:

  1. public static String md5Hex(@NonNull final String data) {
  2. return toHex(md5(data));
  3. }
  4. // byte 数组转 16 进制字符串
  5. private static String toHex(final byte[] digest) {
  6. final StringBuilder sb = new StringBuilder();
  7. for (byte t : digest) {
  8. sb.append(Integer.toHexString(t & 0xFF));
  9. }
  10. return sb.toString();
  11. }
  12. //获取加密后的 byte 数组
  13. public static byte[] getDigestBytes(final Algorithm algorithm, @NonNull final byte[] source) {
  14. try {
  15. return MessageDigest.getInstance(algorithm.value()).digest(source);
  16. }
  17. catch (NoSuchAlgorithmException e) {
  18. throw new IllegalArgumentException(e);
  19. }
  20. }

这方法,网上查一下,基本都是类似的写法,理论上不应该有问题的呀。

既然提到上网查,那就查一下吧,果真还不少人碰到过这种问题,但网上类似的问答、文章似乎都是这个页面 (Java使用MD5加密,生成的密文长度只有31位)的盗版。面对那些盗版页面,气的一度想要举报,却发现盗版页面上没有举报入口,简直可恶至极!

在这个问答下,有大佬解释了问什么少 0

问题出在这一句:Integer.toHexString(t & 0xFF) 当 t 为 14 时,十六进制就是 0e转化成字符串会忽略掉前导零

当然,这里说的 t = 14 只是举例,等于其它前面是 0 的十六进制值时,都会出现丢 0 的问题。

解决方法

方法很简单,转成字符串后,加一次判断即可:

  1. private static String toHex(final byte[] digest) {
  2. final StringBuilder sb = new StringBuilder();
  3. for (byte t : digest) {
  4. String s = Integer.toHexString(t & 0xFF);
  5. if (s.length() == 1) {
  6. s = 0 + s;
  7. }
  8. sb.append(s);
  9. }
  10. return sb.toString();
  11. }

用最新修改后的代码测试,果然,少 0 的问题没有了,找了 2 个晚上的“特殊意义”原来是知识量不够。


其它错误写法

在网上还看到过这么写的:

  1. private static String toHex(final byte[] digest) {
  2. return new BigInteger(1, digest).toString(16);
  3. }

怎么样,一行代码就完成了转换,简单明了,是不是很🐂🍺的赶脚?
但是,实际上,这个的内部实现原理类似,因此使用的时候,也会丢 0