通过哈希算法,我们可以验证一段数据是否有效,通过对比该数据的哈希值。例如,判断用户口令是否正确,我们用保存在数据库中的密码哈希值对比计算出来的哈希值;如果一致,用户输入的口令就是正确的。
为了防止黑客通过彩虹表根据哈希值反推原始口令,在计算哈希的时候,不能仅针对原始输入计算,需要增加一个 salt(盐)来使得相同的输入也能得到不同的哈希;这样大大增加了黑客破解的难度。
什么是彩虹表?
彩虹表是一个用于加密散列函数逆运算的预先计算好的表, 为破解密码的散列值(或称哈希值、微缩图、摘要、指纹、哈希密文)而准备。一般主流的彩虹表都在100G以上。 这样的表常常用于恢复由有限集字符组成的固定长度的纯文本密码。这是空间/时间替换的典型实践, 比每一次尝试都计算哈希的暴力破解处理时间少而储存空间多,但却比简单的对每条输入散列翻查表的破解方式储存空间少而处理时间多。使用加 salt(盐)的 KDF 函数可以使这种攻击难以实现。彩虹表是马丁·赫尔曼早期提出的简单算法的应用。
如果 salt 是我们自己随机生成的,通常我们计算 MD5 时采用 md5(message + salt)。但实际上,把 salt 看做一个 “口令”,加salt 的哈希就是:计算一段 message 的哈希时,根据不同口令计算出不同的哈希。要验证哈希值,必须同时提供正确的口令。
这实际上就是 Hmac 算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把 key 混入计算过程中。
和我们自定义的加 salt 算法不同,Hmac 算法针对所有哈希算法都通用,无论是 MD5 还是 SHA-1。采用 Hmac 替代我们自己的 salt 算法,可以使程序算法更标准化,也更安全。
该枚举类提供了 HmacUtils 类使用的算法名称枚举。提供了如下算法枚举:
HMAC_MD5:RFC 2104和RFC 1321中指定的HmacMD5消息认证代码(MAC)算法。
HMAC_SHA_1:RFC 2104和FIPS PUB 180-2中指定的HmacSHA1消息认证代码(MAC)算法。
HMAC_SHA_224:RFC 2104和FIPS PUB 180-2中指定的HmacSHA224消息认证代码(MAC)算法。
HMAC_SHA_256:RFC 2104和FIPS PUB 180-2中指定的HmacSHA256消息认证代码(MAC)算法。
HMAC_SHA_384:RFC 2104和FIPS PUB 180-2中指定的HmacSHA384消息认证代码(MAC)算法。
HMAC_SHA_512:RFC 2104和FIPS PUB 180-2中指定的HmacSHA512消息认证代码(MAC)算法。
注意:并不是所有的JCE实现都支持这个枚举中的所有算法。
该类简化了常见的 Mac 任务,并且该类是不可变的、线程安全的。
注意:并非所有 JCE 实现都支持所有算法。如果不支持,则抛出 IllegalArgumentException 异常。
实例1:采用 Hmac 和 MD5 对相同字符串进行哈希值计算,如下:
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.codec.digest.HmacUtils; public class HmacDemo1 { public static void main(String[] args) { HmacUtils hmacUtils = new HmacUtils(HmacAlgorithms.HMAC_MD5, "myKey"); byte[] bytes = hmacUtils.hmac("aaaaaa"); System.out.println(Hex.encodeHexString(bytes)); // Hmac 和 MD5 生成的摘要不同 System.out.println(DigestUtils.md5Hex("aaaaaa".getBytes())); } }
输出结果:
8055a42f5f652e0d408689961c215226 0b4e7a0e5fe84ad35fb5f95b9ceeac79
实例2:直接在 HmacUtils 指定算法名称,而不是使用 HmacAlgorithms 枚举。如下:
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.codec.digest.HmacUtils; public class HmacDemo2 { public static void main(String[] args) { HmacUtils hmacUtils = new HmacUtils("HmacMD5", "myKey"); byte[] bytes = hmacUtils.hmac("aaaaaa"); System.out.println(Hex.encodeHexString(bytes)); // Hmac 和 MD5 生成的摘要不同 System.out.println(DigestUtils.md5Hex("aaaaaa".getBytes())); } }
输出结果:
8055a42f5f652e0d408689961c215226 0b4e7a0e5fe84ad35fb5f95b9ceeac79