最近看到一个系统的用户密码直接就是用MD5加密的方式存在数据库的,而且也没有加盐,顿时有些好奇,因为一直听说MD5加密不够安全,很容易碰撞攻击,但是这个容易是有多容易,如果要破解一个MD5加密的密码大概要多久?
这人一好奇就停不下来,直接上手撸代码
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;/*** @author Sakura* @date 2024/11/8 13:01*/
public class MD5CollisionFinder {// 列出密码组成可能出现的字符private static final char[] CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._/-@".toCharArray();// 控制密码碰撞尝试的最大长度,长度越大需要的时间就越久,当然你的机器计算能力越强时间就越短private static final int MAX_LENGTH = 11;// 这里因为我把密码放在数据表格里所以需要配置数据库 private static final String URL = "jdbc:mysql://192.168.31.118:3306/ceshi?useUnicode=true&characterEncoding=utf-8&useSSL=false";private static final String USER = "admin";private static final String PASSWORD = "px123456";public static void main(String[] args) throws InterruptedException, ExecutionException {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");long startTime = System.currentTimeMillis();Date startDate = new Date(startTime);System.out.println("开始时间: " + dateFormat.format(startDate));// 原始JDBC连接数据库List<String> passwords = new ArrayList<>();try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {System.out.println("数据库连接成功!");String sql = "SELECT pass_word FROM user";try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {try (ResultSet resultSet = preparedStatement.executeQuery()) {while (resultSet.next()) {String passWord = resultSet.getString("pass_word");passwords.add(passWord);}}}} catch (SQLException e) {System.out.println("数据库操作异常: " + e.getMessage());}// 这里我们分开多个线程加快速度,我这里是根据密码最大长度MAX_LENGTH来控制线程的// 比如1号线程处理1个字符长度的 2号线程处理2个线程长度一次类推Map<String, String> foundMatches = new ConcurrentHashMap<>();ExecutorService executor = Executors.newFixedThreadPool(MAX_LENGTH);List<Future<?>> futures = new ArrayList<>();// 为每种长度的字符串创建一个独立的线程任务for (int length = 1; length <= MAX_LENGTH; length++) {final int currentLength = length;futures.add(executor.submit(() -> findCollisions("", passwords, foundMatches, currentLength)));}// 等待所有任务完成for (Future<?> future : futures) {future.get();}executor.shutdown();System.out.println("找到的碰撞字符串:");for (Map.Entry<String, String> entry : foundMatches.entrySet()) {System.out.println("MD5: " + entry.getKey() + " 对应的原始字符串: " + entry.getValue());}long endTime = System.currentTimeMillis();Date endDate = new Date(endTime);System.out.println("结束时间: " + dateFormat.format(endDate));}public static void findCollisions(String current, List<String> targetHashes, Map<String, String> foundMatches, int targetLength) {if (current.length() == targetLength) {String currentHash = md5Encrypt(current);if (targetHashes.contains(currentHash) && !foundMatches.containsKey(currentHash)) {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");long dataTime = System.currentTimeMillis();Date date = new Date(dataTime);System.out.println(dateFormat.format(date) + "找到碰撞字符串:" + current + ",MD5:" + currentHash);foundMatches.put(currentHash, current);}return;}for (char c : CHARSET) {findCollisions(current + c, targetHashes, foundMatches, targetLength);}}public static String md5Encrypt(String input) {try {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) {throw new RuntimeException(e);}}
}
大家可以看到为了加快速度我这里是开了多个线程的,但是目前我的电脑配置理论上是带不动这么多线程,所以如果有人测试过上面这个代码可以告诉我一下你们的配置和破解一个多少位密码所需要的时间
为了方便测试我随便造了几条数据
程序运行后要等一会,比如我这个大概十分钟左右就已经可以破解出5位长度的密码
如果字符集大小为 64(如 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._/-@)
5 位密码的组合数为 6 4 5 64^{5} 645=1,073,741,824(约 10 亿种可能)
6 位密码的组合数为 6 4 6 64^{6} 646=68,719,476,736(约 687 亿种可能)
最后告诫大家,如果真的想保证数据安全,尽量不要用MD5加密密码,就算用MD5至少加个盐,当然我曾经还见过一个系统它真的加盐了,但是所有的密码都是用的同一个盐,这就属于掩耳盗铃的典范了