Hello everyone, today we are going to talk about a topic that countless backend developers both love and hate—MD5 encryption.
Have you encountered these scenarios:
- The interviewer asks: “Is MD5 an encryption algorithm?” You immediately reply “yes,” and then get criticized thoroughly.
- You store user passwords using MD5, only to have them cracked in seconds by a rainbow table, leading to a complete data leak.
- You performed an MD5 check, but the file transfer still failed, and after searching for a long time, you discovered the MD5 collision issue.
- Your boss asks you to “encrypt” sensitive data, and you use MD5, only to find out that it cannot be decrypted at all.
I once worked at an internet company where my insufficient understanding of MD5 led to user passwords being brute-forced, and I almost got fired. After in-depth study and practice, I summarized the correct usage of MD5, and today I will share it all!
Let’s help you thoroughly understand what MD5 really is!
1. What is MD5? Stop calling it “encryption!”
First, let’s correct a huge misconception:MD5 is not an encryption algorithm, but a hash algorithm (digest algorithm)!
1. Confused between encryption and hashing?
Encryption algorithms:
- Can encrypt and decrypt
- Involves the concept of keys
- The purpose is to protect data from being seen
- Examples: AES, RSA, DES
Hash algorithms:
- Can only compute one way, cannot be reversed
- No concept of keys
- The purpose is to generate a “fingerprint” of the data
- Examples: MD5, SHA-1, SHA-256
// Incorrect example: using MD5 as encryption
public class WrongExample {
public static void main(String[] args) {
String password = "123456";
String encrypted = MD5Util.encrypt(password); // ❌ Incorrect! MD5 is not encryption
System.out.println("Encrypted: " + encrypted);
// Want to decrypt? Not possible!
// String decrypted = MD5Util.decrypt(encrypted); // ❌ This method does not exist
}
}
// Correct example: MD5 is a hash
public class CorrectExample {
public static void main(String[] args) {
String password = "123456";
String hash = MD5Util.hash(password); // ✅ Correct! Generates hash value
System.out.println("Hash value: " + hash); // e10adc3949ba59abbe56e057f20f883e
// Verify password
String inputPassword = "123456";
boolean isValid = MD5Util.hash(inputPassword).equals(hash); // ✅ This is how to verify
System.out.println("Password correct: " + isValid);
}
}
2. Core characteristics of MD5
Fixed length output:
- No matter how long the input, MD5 always outputs 128 bits (32 hexadecimal characters)
- “a” → 0cc175b9c0f1b6a831c399e269772661
- “A very long piece of text…” → also 32 bits
One-way and irreversible:
- MD5(“123456”) = “e10adc3949ba59abbe56e057f20f883e”
- But you can never reverse “e10adc3949ba59abbe56e057f20f883e” back to “123456”
Avalanche effect:
- A slight change in input results in a completely different output
- MD5(“123456”) = “e10adc3949ba59abbe56e057f20f883e”
- MD5(“123457”) = “fcea920f7412b5da7be0cf42b8c93759”
2. The principle of the MD5 algorithm, helping you thoroughly understand the internal mechanism
1. The 4 steps of the MD5 algorithm
public class MD5Algorithm {
/**
* The 4 core steps of the MD5 algorithm
*/
public String md5(String input) {
// Step 1: Pad the message
byte[] paddedMessage = padMessage(input.getBytes());
// Step 2: Initialize MD buffer
int[] md = initializeMD();
// Step 3: Process message blocks
processMessageBlocks(paddedMessage, md);
// Step 4: Output result
return formatOutput(md);
}
/**
* Step 1: Message padding
* Goal: Make message length ≡ 448 (mod 512)
*/
private byte[] padMessage(byte[] message) {
int originalLength = message.length;
int bitLength = originalLength * 8;
// Calculate the length to be padded
int paddingLength = (448 - (bitLength % 512) + 512) % 512;
if (paddingLength == 0) paddingLength = 512;
// Create padded message
int totalLength = originalLength + (paddingLength / 8) + 8;
byte[] paddedMessage = new byte[totalLength];
// Copy original message
System.arraycopy(message, 0, paddedMessage, 0, originalLength);
// Add 1 bit of '1' and several bits of '0' (simplified)
paddedMessage[originalLength] = (byte) 0x80; // 10000000
// Add original length (64 bits)
for (int i = 0; i < 8; i++) {
paddedMessage[totalLength - 8 + i] = (byte) (bitLength >>> (i * 8));
}
return paddedMessage;
}
/**
* Step 2: Initialize MD buffer
* 4 32-bit registers: A, B, C, D
*/
private int[] initializeMD() {
return new int[]{
0x67452301, // A
0xEFCDAB89, // B
0x98BADCFE, // C
0x10325476 // D
};
}
/**
* Step 3: Process message blocks (core algorithm)
* Each block is 512 bits, performing 64 rounds of operations
*/
private void processMessageBlocks(byte[] message, int[] md) {
// 4 rounds, each with 16 steps, totaling 64 steps
int[] X = new int[16]; // Current 512-bit block
for (int i = 0; i < message.length; i += 64) {
// Convert 64 bytes to 16 ints
for (int j = 0; j < 16; j++) {
X[j] = bytesToInt(message, i + j * 4);
}
// Save current MD value
int A = md[0], B = md[1], C = md[2], D = md[3];
// Round 1: F function
A = round1(A, B, C, D, X[0], 7, 0xD76AA478);
D = round1(D, A, B, C, X[1], 12, 0xE8C7B756);
// ... other 15 steps
// Round 2: G function
// Round 3: H function
// Round 4: I function
// ...
// Accumulate to MD buffer
md[0] += A;
md[1] += B;
md[2] += C;
md[3] += D;
}
}
/**
* 4 auxiliary functions of MD5
*/
private int F(int x, int y, int z) {
return (x && y) | (~x && z);
}
private int G(int x, int y, int z) {
return (x && z) | (y && ~z);
}
private int H(int x, int y, int z) {
return x ^ y ^ z;
}
private int I(int x, int y, int z) {
return y ^ (x | ~z);
}
}
At this point, you might feel overwhelmed. But don’t worry, in actual development, we don’t need to implement the MD5 algorithm ourselves; Java provides ready-made tools.
2. Correct usage of MD5 in Java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
/**
* Standard MD5 hash
*/
public static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(input.getBytes("UTF-8"));
// Convert to hexadecimal string
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 hash failed", e);
}
}
/**
* MD5 with salt (recommended)
*/
public static String md5WithSalt(String input, String salt) {
return md5(input + salt);
}
/**
* Multiple rounds of MD5 hash (enhanced security)
*/
public static String md5Multiple(String input, int rounds) {
String result = input;
for (int i = 0; i < rounds; i++) {
result = md5(result);
}
return result;
}
/**
* Verify MD5 hash
*/
public static boolean verify(String input, String hash) {
return md5(input).equals(hash);
}
/**
* Verify MD5 with salt
*/
public static boolean verifyWithSalt(String input, String salt, String hash) {
return md5WithSalt(input, salt).equals(hash);
}
}
// Usage example
public class MD5Example {
public static void main(String[] args) {
String password = "123456";
// Basic MD5
String hash1 = MD5Util.md5(password);
System.out.println("Basic MD5: " + hash1);
// MD5 with salt
String salt = "mySecretSalt";
String hash2 = MD5Util.md5WithSalt(password, salt);
System.out.println("MD5 with salt: " + hash2);
// Multiple rounds of MD5
String hash3 = MD5Util.md5Multiple(password, 1000);
System.out.println("Multiple rounds MD5: " + hash3);
// Verification
boolean isValid = MD5Util.verify("123456", hash1);
System.out.println("Verification result: " + isValid);
}
}
3. 5 Practical Application Scenarios of MD5
Scenario 1: Password Storage – Stop Exposing Yourself!
Incorrect approach:
// ❌ Never do this!
public class BadPasswordStorage {
public void saveUser(String username, String password) {
// Directly storing plaintext passwords is simply suicidal
userDao.save(new User(username, password));
}
}
Correct approach:
// ✅ Recommended password storage scheme
@Service
public class SecurePasswordService {
private final String GLOBAL_SALT = "MyApp_Secret_Salt_2024";
/**
* Register user - securely store password
*/
public void registerUser(String username, String password) {
// 1. Generate user-specific salt
String userSalt = generateUserSalt(username);
// 2. Multiple hashing
String hashedPassword = hashPassword(password, userSalt);
// 3. Store user information
User user = new User();
user.setUsername(username);
user.setPasswordHash(hashedPassword);
user.setSalt(userSalt);
userDao.save(user);
}
/**
* User login - verify password
*/
public boolean loginUser(String username, String password) {
User user = userDao.findByUsername(username);
if (user == null) {
return false;
}
// Hash the input password using the same method
String hashedInput = hashPassword(password, user.getSalt());
// Compare hash values
return hashedInput.equals(user.getPasswordHash());
}
/**
* Password hash algorithm
*/
private String hashPassword(String password, String userSalt) {
// Combine: password + user salt + global salt
String combined = password + userSalt + GLOBAL_SALT;
// Multiple rounds of MD5 to enhance security
return MD5Util.md5Multiple(combined, 3000);
}
/**
* Generate user-specific salt
*/
private String generateUserSalt(String username) {
// Generate a unique salt based on username and timestamp
String saltSource = username + System.currentTimeMillis() + Math.random();
return MD5Util.md5(saltSource).substring(0, 16);
}
}
Scenario 2: File Integrity Check – Prevent Data Corruption
@Service
public class FileIntegrityService {
/**
* Generate file MD5 checksum
*/
public String generateFileMD5(File file) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
try (FileInputStream fis = new FileInputStream(file);
DigestInputStream dis = new DigestInputStream(fis, md)) {
byte[] buffer = new byte[8192];
while (dis.read(buffer) != -1) {
// DigestInputStream will automatically update MD5
}
}
// Get final MD5 value
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("Failed to generate file MD5", e);
}
}
/**
* Verify file integrity
*/
public boolean verifyFileIntegrity(File file, String expectedMD5) {
String actualMD5 = generateFileMD5(file);
return actualMD5.equalsIgnoreCase(expectedMD5);
}
/**
* File upload integrity check
*/
@PostMapping("/upload")
public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("md5") String clientMD5) {
try {
// Save temporary file
File tempFile = File.createTempFile("upload_", ".tmp");
file.transferTo(tempFile);
// Verify MD5
if (!verifyFileIntegrity(tempFile, clientMD5)) {
tempFile.delete();
return ResponseEntity.badRequest().body("File MD5 check failed, please re-upload");
}
// MD5 verification passed, move to final directory
String finalPath = moveToFinalLocation(tempFile, file.getOriginalFilename());
return ResponseEntity.ok(Map.of(
"message", "Upload successful",
"path", finalPath,
"md5", clientMD5
));
} catch (Exception e) {
return ResponseEntity.status(500).body("Upload failed: " + e.getMessage());
}
}
}
Scenario 3: Cache Key Generation – Making Caching Smarter
@Service
public class SmartCacheService {
@Autowired
private RedisTemplate redisTemplate;
/**
* Smart cache key generation
*/
public String generateCacheKey(String prefix, Object... params) {
// Concatenate all parameters
StringBuilder keyBuilder = new StringBuilder();
for (Object param : params) {
keyBuilder.append(param.toString()).append("|");
}
String paramString = keyBuilder.toString();
// If parameters are too long, use MD5 to compress
if (paramString.length() > 100) {
String md5Key = MD5Util.md5(paramString);
return prefix + ":" + md5Key;
} else {
return prefix + ":" + paramString.replaceAll("[^a-zA-Z0-9]", "_");
}
}
/**
* User personalized recommendation cache
*/
public List getUserRecommendations(Long userId, String category,
List tags, Map filters) {
// Generate complex cache key
String cacheKey = generateCacheKey("user_recommend",
userId, category, String.join(",", tags), filters.toString());
// Try to get from cache
List cached = (List) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// Cache miss, calculate recommendation results
List recommendations = calculateRecommendations(userId, category, tags, filters);
// Store in cache, expires in 1 hour
redisTemplate.opsForValue().set(cacheKey, recommendations, Duration.ofHours(1));
return recommendations;
}
/**
* API response caching
*/
public Object cacheApiResponse(String apiPath, Map params) {
// Generate API cache key
String cacheKey = generateCacheKey("api_cache", apiPath, params);
Object cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// Execute actual API call
Object result = executeApiCall(apiPath, params);
// Cache result
redisTemplate.opsForValue().set(cacheKey, result, Duration.ofMinutes(30));
return result;
}
}
Scenario 4: Distributed Lock – Solving Concurrency Issues
@Component
public class DistributedLockService {
@Autowired
private RedisTemplate redisTemplate;
/**
* Acquire distributed lock
*/
public boolean acquireLock(String resource, String requester, int expireSeconds) {
// Use MD5 to generate lock key
String lockKey = "distributed_lock:" + MD5Util.md5(resource);
// Lock value contains requester information and timestamp
String lockValue = requester + ":" + System.currentTimeMillis();
// Try to acquire lock
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, Duration.ofSeconds(expireSeconds));
return Boolean.TRUE.equals(success);
}
/**
* Release distributed lock
*/
public boolean releaseLock(String resource, String requester) {
String lockKey = "distributed_lock:" + MD5Util.md5(resource);
// Lua script to ensure atomicity
String luaScript = """
local lockKey = KEYS[1]
local expectedValue = ARGV[1]
local actualValue = redis.call('GET', lockKey)
if actualValue and string.find(actualValue, expectedValue) == 1 then
return redis.call('DEL', lockKey)
else
return 0
end
""";
DefaultRedisScript script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(lockKey), requester);
return result != null && result == 1;
}
/**
* Example of stock deduction
*/
@Transactional
public boolean decreaseStock(Long productId, int quantity) {
String lockResource = "product_stock:" + productId;
String requester = Thread.currentThread().getName();
// Acquire lock
if (!acquireLock(lockResource, requester, 10)) {
throw new RuntimeException("Failed to acquire stock lock, please try again later");
}
try {
// Execute stock deduction
Product product = productService.findById(productId);
if (product.getStock() < quantity) {
return false;
}
product.setStock(product.getStock() - quantity);
productService.update(product);
return true;
} finally {
// Release lock
releaseLock(lockResource, requester);
}
}
}
Scenario 5: Data Deduplication – Prevent Duplicate Processing
@Service
public class DataDeduplicationService {
@Autowired
private RedisTemplate redisTemplate;
/**
* Order deduplication processing
*/
public boolean processOrderIdempotent(OrderRequest request) {
// Generate idempotency key
String idempotentKey = generateIdempotentKey("order", request);
// Check if already processed
if (isAlreadyProcessed(idempotentKey)) {
log.info("Order has been processed, skipping: {}", request.getOrderNo());
return true;
}
try {
// Mark as processing
markProcessing(idempotentKey);
// Execute order processing logic
Order order = createOrder(request);
paymentService.processPayment(order);
notificationService.sendOrderConfirmation(order);
// Mark as processed
markProcessed(idempotentKey, order.getId());
return true;
} catch (Exception e) {
// Processing failed, clear mark
clearProcessingMark(idempotentKey);
throw e;
}
}
/**
* Generate idempotency key
*/
private String generateIdempotentKey(String operation, Object request) {
// Serialize request object to JSON
String requestJson = JSON.toJSONString(request);
// Generate MD5 as unique identifier
String md5Key = MD5Util.md5(requestJson);
return String.format("idempotent:%s:%s", operation, md5Key);
}
/**
* Check if already processed
*/
private boolean isAlreadyProcessed(String key) {
String status = redisTemplate.opsForValue().get(key);
return "PROCESSED".equals(status);
}
/**
* Mark as processing
*/
private void markProcessing(String key) {
redisTemplate.opsForValue().set(key, "PROCESSING", Duration.ofMinutes(5));
}
/**
* Mark as processed
*/
private void markProcessed(String key, Long orderId) {
redisTemplate.opsForValue().set(key, "PROCESSED:" + orderId, Duration.ofHours(24));
}
/**
* Clear processing mark
*/
private void clearProcessingMark(String key) {
redisTemplate.delete(key);
}
/**
* Message deduplication example
*/
public void processMessageIdempotent(String messageId, String messageContent) {
// Generate deduplication key based on message ID and content
String dedupeKey = "message_dedupe:" + MD5Util.md5(messageId + messageContent);
// Check if already processed
if (redisTemplate.hasKey(dedupeKey)) {
log.info("Message duplicate, skipping processing: {}", messageId);
return;
}
// Mark message as processed
redisTemplate.opsForValue().set(dedupeKey, "processed", Duration.ofHours(2));
// Process message
handleMessage(messageContent);
}
}
4. 3 Fatal Weaknesses of MD5 You Must Know!
Weakness 1: Rainbow Table Attack – Common Passwords Cracked Instantly
What is a rainbow table? A rainbow table is a pre-computed “password → MD5” lookup table. Attackers trade space for time by pre-computing the MD5 values of common passwords.
// Dangerous example: directly using MD5 to store passwords
public class VulnerablePasswordStorage {
public void savePassword(String username, String password) {
String md5Hash = MD5Util.md5(password);
// Storing passwords this way is easily cracked by rainbow tables
userDao.updatePassword(username, md5Hash);
}
}
// MD5 values of common passwords (attackers already know)
// "123456" → "e10adc3949ba59abbe56e057f20f883e"
// "password" → "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
// "admin" → "21232f297a57a5a743894a0e4a801fc3"
Defense strategy:
// Secure practice: use salt
public class SecurePasswordStorage {
public void savePassword(String username, String password) {
// 1. Generate random salt
String salt = generateRandomSalt();
// 2. Hash password + salt
String secureHash = MD5Util.md5(password + salt);
// 3. Store hash value and salt
userDao.save(new User(username, secureHash, salt));
}
private String generateRandomSalt() {
return UUID.randomUUID().toString().replace("-", "");
}
}
Weakness 2: MD5 Collision – Different Inputs Produce the Same Output
In 2004, Chinese cryptography expert Wang Xiaoyun proved that MD5 has a collision vulnerability. Although it is difficult to exploit in practical applications, there is indeed a theoretical security risk.
// MD5 collision demonstration (theoretically exists, practically hard to construct)
public class MD5CollisionDemo {
public static void main(String[] args) {
// Theoretically, there exist two different inputs that produce the same MD5
// However, constructing such a collision in practice requires enormous computational resources
String input1 = "input1";
String input2 = "input2";
String hash1 = MD5Util.md5(input1);
String hash2 = MD5Util.md5(input2);
// Normally they should not be equal
System.out.println("Hash1: " + hash1);
System.out.println("Hash2: " + hash2);
System.out.println("Equal: " + hash1.equals(hash2));
}
}
Weakness 3: Too Fast Calculation Speed – A Breeding Ground for Brute Force Cracking
The calculation speed of MD5 is very fast, which becomes a disadvantage in the face of brute force cracking. Modern GPUs can compute billions of MD5 hashes per second.
// Demonstration: brute force cracking simple passwords
public class BruteForceDemo {
// Simulate brute force cracking (for educational purposes only, do not use for illegal purposes)
public String bruteForceSimplePassword(String targetMD5) {
String chars = "0123456789";
// Try 4-digit numeric passwords
for (int i = 0; i < 10000; i++) {
String password = String.format("%04d", i);
String hash = MD5Util.md5(password);
if (hash.equals(targetMD5)) {
return password;
}
}
return null;
}
public static void main(String[] args) {
BruteForceDemo demo = new BruteForceDemo();
// Password to crack: "1234"
String targetHash = MD5Util.md5("1234");
System.out.println("Target hash: " + targetHash);
long startTime = System.currentTimeMillis();
String cracked = demo.bruteForceSimplePassword(targetHash);
long endTime = System.currentTimeMillis();
System.out.println("Cracking result: " + cracked);
System.out.println("Time taken: " + (endTime - startTime) + "ms");
}
}
5. Alternatives to MD5 for a More Secure System
1. SHA-256 – The Modern Alternative to MD5
import java.security.MessageDigest;
public class SHA256Util {
public static String sha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff && b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("SHA-256 hash failed", e);
}
}
// Usage example
public static void main(String[] args) {
String password = "123456";
String md5 = MD5Util.md5(password);
String sha256 = SHA256Util.sha256(password);
System.out.println("MD5 (32 bits): " + md5);
System.out.println("SHA256(64 bits): " + sha256);
}
}
2. BCrypt – A Hash Algorithm Designed Specifically for Passwords
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Service
public class BCryptService {
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
/**
* Encrypt password
*/
public String encodePassword(String password) {
return encoder.encode(password);
}
/**
* Verify password
*/
public boolean matches(String password, String hash) {
return encoder.matches(password, hash);
}
/**
* User registration example
*/
public void registerUser(String username, String password) {
// BCrypt automatically handles salt, no need to add manually
String hashedPassword = encodePassword(password);
User user = new User();
user.setUsername(username);
user.setPassword(hashedPassword);
userDao.save(user);
}
/**
* User login example
*/
public boolean login(String username, String password) {
User user = userDao.findByUsername(username);
if (user == null) {
return false;
}
return matches(password, user.getPassword());
}
}
3. PBKDF2 – Industrial-Grade Password Hashing
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class PBKDF2Util {
private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
private static final int ITERATIONS = 100000; // 100,000 iterations
private static final int KEY_LENGTH = 256; // 256-bit key
/**
* Generate PBKDF2 hash
*/
public static String generatePBKDF2(String password, byte[] salt) {
try {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
byte[] hash = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("PBKDF2 hash failed", e);
}
}
/**
* Generate random salt
*/
public static byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
/**
* Verify password
*/
public static boolean verify(String password, String hash, byte[] salt) {
String computedHash = generatePBKDF2(password, salt);
return computedHash.equals(hash);
}
}
6. Practical Experience: When Should You Use MD5?
✅ Scenarios Suitable for Using MD5
- File Integrity Check
// Verify integrity after downloading a file
String downloadedFileMD5 = FileUtil.calculateMD5(downloadedFile);
if (!downloadedFileMD5.equals(expectedMD5)) {
throw new RuntimeException("File download incomplete, please re-download");
}
- Cache Key Generation
// Generate cache key for complex query conditions
String cacheKey = "user_search:" + MD5Util.md5(queryConditions.toString());
- Data Deduplication Identifier
// Generate unique identifier for data
String dataId = MD5Util.md5(dataContent);
- Load Balancing Hash
// Select server based on user ID
int serverIndex = Math.abs(MD5Util.md5(userId).hashCode()) % serverList.size();
❌ Scenarios Not Suitable for Using MD5
- Password Storage – Use BCrypt or PBKDF2
- Digital Signatures – Use RSA or ECDSA
- High Security Requirement Scenarios – Use SHA-256 or more advanced algorithms
- Legal Compliance Requirements – Some industries prohibit the use of MD5
7. Performance Comparison: Choosing Various Hash Algorithms
// Performance testing code
public class HashPerformanceTest {
private static final String TEST_DATA = "This is a test data used to compare the performance of various hash algorithms";
private static final int ITERATIONS = 100000;
public static void main(String[] args) {
System.out.println("Hash algorithm performance comparison (" + ITERATIONS + " iterations):");
// MD5 performance test
long startTime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
MD5Util.md5(TEST_DATA + i);
}
long md5Time = System.currentTimeMillis() - startTime;
// SHA-256 performance test
startTime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
SHA256Util.sha256(TEST_DATA + i);
}
long sha256Time = System.currentTimeMillis() - startTime;
// BCrypt performance test (limited testing due to slowness)
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
startTime = System.currentTimeMillis();
for (int i = 0; i < 100; i++) { // Only test 100 times
encoder.encode(TEST_DATA + i);
}
long bcryptTime = System.currentTimeMillis() - startTime;
System.out.println("MD5 : " + md5Time + "ms");
System.out.println("SHA-256: " + sha256Time + "ms");
System.out.println("BCrypt : " + bcryptTime + "ms (only 100 times)");
// Result analysis
System.out.println("\nConclusion:");
System.out.println("- MD5 is the fastest, suitable for processing large amounts of data");
System.out.println("- SHA-256 is slightly slower, but more secure");
System.out.println("- BCrypt is the slowest, but most suitable for password storage");
}
}
8. Conclusion: The Golden Rules for Using MD5
3 Core Points
- MD5 is not encryption, it is hashing – One-way and irreversible, with no concept of keys
- Security is outdated – Do not use for password storage and security-sensitive scenarios
- Performance is still excellent – Suitable for file verification, cache keys, and other non-security scenarios
5 Usage Recommendations
- Use BCrypt for password storage: Automatically salts, resistant to brute force cracking
- Use MD5 for file verification: Fast, can detect data corruption
- Use MD5 for cache key generation: Short and concise, low collision probability
- Use SHA-256 for new projects: Higher security, longer-lasting for the future
- Implement multiple protections for critical business: Use MD5 in combination with other algorithms
Best Practice Checklist
- [ ] Understand that MD5 is a hash algorithm, not an encryption algorithm
- [ ] Use BCrypt for password storage, not MD5
- [ ] MD5 can be used for file verification
- [ ] MD5 can be used for cache key generation
- [ ] Be aware of MD5’s security weaknesses
- [ ] Choose stronger algorithms in security-sensitive scenarios
Remember the words of an experienced developer: Tools are not good or bad, only suitable or unsuitable. MD5 may be old, but when used in the right place, it is still a powerful tool!
Now, will you still say MD5 is an encryption algorithm? 😏
If you find this useful, please like, share, and forward! In the next issue, we will discuss “How to Design a High-Performance Image Upload System,” stay tuned!
Follow our public account: Selected Backend Technology to share practical experience in backend architecture design every week, making technology warmer!