MD5 and SHA-1 are cryptographically broken, yet they still appear in Java codebases everywhere. Learn how to detect them, why they're dangerous, and how to migrate to SHA-256 or bcrypt.
JOptimize Team
MD5 and SHA-1 are cryptographically broken - this has been known since 2004 for MD5 and 2017 for SHA-1. Yet in 2026, they still appear in Java codebases everywhere: password hashing, file integrity checks, token generation, API signatures. This is an OWASP Top 10 issue (A02: Cryptographic Failures), and it's exploitable.
Both algorithms are vulnerable to collision attacks - an attacker can generate two different inputs that produce the same hash. MD5 collisions can be computed in seconds on a modern laptop. SHA-1 was cracked by Google's SHAttered attack in 2017.
For password hashing, they're even worse: they're fast by design, which means attackers can brute-force billions of MD5 hashes per second using GPU rigs.
// DANGEROUS - never do this for passwords MessageDigest md = MessageDigest.getInstance("MD5"); byte[] hash = md.digest(password.getBytes()); String hex = HexFormat.of().formatHex(hash); // A modern GPU cracks this in minutes
Weak hashing shows up in several patterns:
1. Password storage (most critical)
// Anti-pattern - seen in legacy Spring apps public String hashPassword(String password) { return DigestUtils.md5DigestAsHex(password.getBytes()); // BROKEN }
2. File integrity verification
// MD5 checksums can be forged MessageDigest md = MessageDigest.getInstance("MD5"); try (InputStream is = new FileInputStream(file)) { byte[] buffer = new byte[8192]; int read; while ((read = is.read(buffer)) != -1) { md.update(buffer, 0, read); } } return HexFormat.of().formatHex(md.digest());
3. Token or ID generation
// SHA-1 used as a "unique" identifier - predictable and forgeable String token = DigestUtils.sha1Hex(userId + System.currentTimeMillis());
Passwords need a slow hashing algorithm specifically designed to resist brute-force attacks. Spring Security ships with all three:
// Spring Security - bcrypt (recommended default) @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // cost factor 12 } // Usage: public void register(String username, String plainPassword) { String hashed = passwordEncoder.encode(plainPassword); userRepository.save(new User(username, hashed)); } public boolean login(String plainPassword, String storedHash) { return passwordEncoder.matches(plainPassword, storedHash); }
For new projects, prefer Argon2 (winner of the Password Hashing Competition):
@Bean public PasswordEncoder passwordEncoder() { return new Argon2PasswordEncoder( 16, // salt length 32, // hash length 1, // parallelism 65536, // memory in KB 3 // iterations ); }
// SHA-256 - drop-in replacement for MD5/SHA-1 public String hashFile(File file) throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-256"); try (InputStream is = new FileInputStream(file)) { byte[] buffer = new byte[8192]; int read; while ((read = is.read(buffer)) != -1) { digest.update(buffer, 0, read); } } return HexFormat.of().formatHex(digest.digest()); } // Spring's DigestUtils also supports SHA-256: String hash = DigestUtils.sha256Hex(inputStream);
// Cryptographically secure random token public String generateToken() { byte[] bytes = new byte[32]; new SecureRandom().nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } // Or simply: String id = UUID.randomUUID().toString();
If you have a live system with MD5-hashed passwords, you can migrate transparently on login:
@Service public class AuthService { private final PasswordEncoder newEncoder = new BCryptPasswordEncoder(12); public boolean authenticate(String username, String plainPassword) { User user = userRepository.findByUsername(username); if (isMd5Hash(user.getPasswordHash())) { // Legacy check String md5 = DigestUtils.md5DigestAsHex(plainPassword.getBytes()); if (!md5.equals(user.getPasswordHash())) return false; // Upgrade on successful login user.setPasswordHash(newEncoder.encode(plainPassword)); userRepository.save(user); return true; } return newEncoder.matches(plainPassword, user.getPasswordHash()); } private boolean isMd5Hash(String hash) { return hash != null && hash.matches("[a-f0-9]{32}"); } }
Users are silently upgraded to bcrypt on their next login. No password resets required.
MD5 and SHA-1 are broken and should never appear in new Java code. For passwords, use bcrypt or Argon2 via Spring Security's PasswordEncoder. For file integrity and tokens, use SHA-256 or SecureRandom. Existing MD5 password databases can be migrated transparently on login without forcing password resets.
JOptimize scans your Java project for MessageDigest.getInstance("MD5"), DigestUtils.md5*, and SHA-1 usages and flags them as security vulnerabilities with a suggested replacement.
Find weak crypto before an attacker does - free scan, no configuration required.
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.