关于 MD5 加密中 ByteToHex 操作使用位运算的原理
先上一段使用 MD5 加密的代码
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytesArr1 = txt.getBytes(StandardCharsets.UTF_8);
byte[] bytesArr2 = md5.digest(bytesArr1);
StringBuilder encrypt = new StringBuilder("");
for (byte temp : bytesArr2) {
encrypt.append(Integer.toHexString((temp & 0x000000FF) | 0xFFFFFF00).substring(6));
}
return encrypt.toString();
- 这里有这样一段代码:
(temp & 0x000000FF) | 0xFFFFFF00
, 根据运算符优先级其实它等同于temp & 0x000000FF | 0xFFFFFF00
- 这段代码的意义就是 ByteToHex 的核心, 根据代码上下文可知 temp 是遍历 md5.digest() 返回值的 byte 类型临时变量, 其总长度为 16, 而下面的 for 循环就是将这个长度为 16 的 byte 数组转换为 hex 字符串
- 转换的规则为: 如果 byte 的值大于 15 则直接将其 16 进制形式的文本追加到 hex 字符串上, 否则先在 hex 字符串上追加一个
0
后追加到 hex 字符串上(其实等同于temp/16
,temp%16
两位数字, 前提是temp 必须为正数
) - 上述转换规则的原因是: 一个 byte 数据为 8 个二进制位, 而一个 hex 数据(如: f)最大占用 4 个二进制位, 因此将一个 byte 数据对照为一个两位 hex 数据, 不足两位则在前面补
0
, 从而使得 MD5 的返回值为 32 位 - 代码
Integer.toHexString((temp & 0x000000FF) | 0xFFFFFF00).substring(6)
就是上述内容的具体实现
补充内容:
在以模为 256 的系统中, -13 == +243
, 我们称 13
和 243
这两个数在模为 256 的系统中互为补数, 如: 20 - 13 == 20 + 243 - 256 == 7
补数: 补数_百度百科
temp & 0x000000FF
的目的是将 temp 为负数的数据转换为其对应符号为正的补数, 而 temp 为正的 byte 数据不受其影响(此时无论 temp 原本为正数还是负数, 其左侧 24 位都为 0)- 再拿上一步的运算结果与
0xFFFFFF00
进行或运算
使得其 HexString 的长度一定为8
位(因为如果左侧为 0 则会被省略, 导致 byte 对应 hex 的长度可能为 2 位(值大于等于 16), 也有可能为 1 位(值小于 16)。而0xFFFFFF00
保证了左侧 24 位都为1
, 即: HexString 左侧 6 位一定为FFFFFF
, 从而使得其 HexString 的长度一定为 8) - 最终再使用
substring(6)
将左侧 6 位剪去, 这样就保证了在 byte 值小于 16 的数据前面进行补0
操作。
Integer.toHexString((temp & 0x000000FF) | 0xFFFFFF00).substring(6)
等效于Integer.toHexString((temp & 0x000000FF) | 0xF00).substring(1)