MD5(Message-Digest Algorithm 5)即消息摘要算法第五版,是一种被广泛使用的密码散列函数。
MD5 是一种单向哈希函数,无法通过其生成的哈希值反向推导出原始数据。这一特性使得 MD5 在密码存储等场景中具有重要作用,可以保护用户密码等敏感信息不被轻易破解。
MD5 算法无论输入数据的大小如何,都会生成一个固定长度为 128 位的哈希值。这个固定长度的输出使得 MD5 在存储和比较哈希值时非常方便。
MD5 算法的计算速度相对较快,能够在较短的时间内处理大量的数据。这使得它在需要快速计算哈希值的场景中得到广泛应用。
当你下载一个文件时,可以使用 MD5 来验证文件的完整性。下载提供方会公布文件的 MD5 值,你在下载完成后计算文件的 MD5 值并与公布的值进行比较。如果两者一致,则说明文件在下载过程中没有被损坏或篡改。
在许多系统中,用户密码不会以明文形式存储,而是存储其 MD5 值。当用户登录时,系统会计算用户输入密码的 MD5 值,并与存储的 MD5 值进行比较。这样即使数据库被泄露,攻击者也很难直接获取用户的原始密码。
MD5 可以与数字签名技术结合使用,确保数据的真实性和完整性。发送方对数据进行 MD5 计算,然后使用私钥对 MD5 值进行签名。接收方可以使用发送方的公钥验证签名,并重新计算数据的 MD5 值进行比较。
虽然 MD5 在很长一段时间内被认为是安全的,但随着密码学的发展,人们发现 MD5 存在碰撞攻击的可能性。即不同的输入数据可能会产生相同的 MD5 值。这使得 MD5 在一些对安全性要求极高的场景中不再适用。
MD5 的弱抗碰撞性意味着对于给定的一个消息和其 MD5 值,找到另一个不同的消息具有相同的 MD5 值在计算上是可行的,但具有一定的难度。这种弱抗碰撞性也对 MD5 的安全性构成了威胁。
尽管 MD5 存在一些安全性问题,但在一些对安全性要求不是特别高的场景中,它仍然被广泛使用。同时,随着技术的不断发展,出现了更安全的哈希算法,如 SHA-256、SHA-3 等,这些算法在安全性和抗碰撞性方面都有了很大的提升。
MD5(Message-Digest Algorithm 5)的实现原理主要包括以下几个步骤:
首先,对输入的消息进行填充,使其长度对 512 取模后余数为 448。这是通过在消息末尾添加一个 “1” 位,然后添加若干个 “0” 位来实现的。
接着,在填充后的消息末尾附加一个 64 位的长度值,这个长度值表示原始消息的长度(以位为单位)。
下面以“aaaaaa”为例:
aaaaaa 长度为 6 * 8 = 48,对应的二进制位如下: 00001010 00001010 00001010 00001010 00001010 00001010 448 % 512 = 448 - 48 = 400 / 8 = 80,即还需再上述二进制位后添加 80 个字节 填充后的二进制位序列如下: 00001010 00001010 00001010 00001010 00001010 00001010 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
MD5 使用一个 128 位的缓冲区来保存中间结果和最终的哈希值。这个缓冲区被分为四个 32 位的寄存器,分别标记为 A、B、C、D。
初始时,这四个寄存器被赋予固定的初始值,这些值是经过精心选择的,以确保算法的安全性和随机性。
将填充后的消息分成若干个 512 位的消息块。
对每个消息块进行四轮循环处理,每轮循环有 16 步操作。
每步操作都涉及对 A、B、C、D 四个寄存器的非线性函数运算、加法运算和循环左移操作。
非线性函数包括 F、G、H、I 四种,分别根据不同的条件对三个寄存器的值进行运算。
加法运算将上一步的结果与当前消息块中的某个 32 位字以及一个常数相加。
循环左移操作则对寄存器的值进行位移,以增加算法的复杂性和安全性。
经过所有消息块的处理后,A、B、C、D 四个寄存器的值组合在一起,形成一个 128 位的哈希值。
这个哈希值通常以十六进制的形式表示,以便于阅读和使用。
以下是使用 Java 实现 MD5 算法的示例代码:
借助 JDK 库实现方便快捷,可以避免重复造轮子,代码如下:
package com.hxstrive.encryption_decryption.md5; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * @author hxstrive.com */ public class MD5Example { public static String getMD5(String input) { try { // 获取 MD5 消息摘要对象 MessageDigest md = MessageDigest.getInstance("MD5"); // 将输入字符串转换为字节数组并更新摘要 byte[] messageDigest = md.digest(input.getBytes()); // 将字节数组转换为十六进制字符串表示 StringBuilder hexString = new StringBuilder(); for (byte b : messageDigest) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } public static void main(String[] args) { String input = "Hello, World!"; String md5Hash = getMD5(input); if (md5Hash!= null) { System.out.println("输入字符串:" + input); System.out.println("MD5 哈希值:" + md5Hash); // 65a8e27d8879283831b664bd8b7f0ad4 } } }
在上述代码中,getMD5 方法接受一个字符串作为输入,使用 Java 的 MessageDigest 类来获取 MD5 消息摘要对象,并将输入字符串转换为字节数组进行处理,最后将生成的 MD5 哈希值以十六进制字符串的形式返回。
不使用 Java 提供的消息摘要 API,完全使用 Java 基本语法来实现 MD5 算法。这可以更好的理解 MD5 算法的原理,代码如下:
public class MD5FromScratch { // 定义四个初始常量 private static final int A = 0x67452301; private static final int B = (int) 0xEFCDAB89L; private static final int C = (int) 0x98BADCFEL; private static final int D = 0x10325476; // 定义左移位数常量数组 private static final int[] S = {7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21}; // 定义常量 T private static final int[] T = new int[64]; static { for (int i = 0; i < 64; i++) { T[i] = (int) (long) ((1L << 32) * Math.abs(Math.sin(i + 1))); } } public static String md5(String input) { // 将输入字符串转换为字节数组 byte[] bytes = input.getBytes(); // 计算字节数组长度 int length = bytes.length; // 填充消息长度 int newLength = ((length + 8) >>> 6) << 6; byte[] newBytes = new byte[newLength + 8]; System.arraycopy(bytes, 0, newBytes, 0, length); newBytes[length] = (byte) 0x80; long bitLength = (long) length * 8; for (int i = 0; i < 8; i++) { newBytes[newLength + i] = (byte) (bitLength >>> (i * 8)); } // 初始化变量 int a = A; int b = B; int c = C; int d = D; // 处理消息 for (int i = 0; i < newBytes.length >>> 6; i++) { int[] M = new int[16]; for (int j = 0; j < 16; j++) { M[j] = ((newBytes[i * 64 + j * 4 + 3] & 0xff) << 24) | ((newBytes[i * 64 + j * 4 + 2] & 0xff) << 16) | ((newBytes[i * 64 + j * 4 + 1] & 0xff) << 8) | (newBytes[i * 64 + j * 4 + 0] & 0xff); } int aa = a; int bb = b; int cc = c; int dd = d; for (int j = 0; j < 64; j++) { int f, g; if (j < 16) { f = (b & c) | ((~b) & d); g = j; } else if (j < 32) { f = (d & b) | ((~d) & c); g = (5 * j + 1) % 16; } else if (j < 48) { f = b ^ c ^ d; g = (3 * j + 5) % 16; } else { f = c ^ (b | (~d)); g = (7 * j) % 16; } int temp = d; d = c; c = b; b = b + leftRotate((a + f + T[j] + M[g]), S[j]); a = temp; } a += aa; b += bb; c += cc; d += dd; } // 生成最终的哈希值 return toHexString(a) + toHexString(b) + toHexString(c) + toHexString(d); } private static int leftRotate(int n, int s) { return (n << s) | (n >>> (32 - s)); } private static String toHexString(int n) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 4; i++) { int b = (n >>> (i * 8)) & 0xff; String hex = Integer.toHexString(b); if (hex.length() == 1) { sb.append("0"); } sb.append(hex); } return sb.toString(); } public static void main(String[] args) { String input = "Hello, World!"; String md5Hash = md5(input); System.out.println("输入字符串:" + input); System.out.println("MD5 哈希值:" + md5Hash); } }
这个实现虽然较为复杂,但完全不依赖 Java 内置的消息摘要 API,通过手动实现 MD5 算法的各个步骤来计算哈希值。