Back to Blog
javasecuritycryptographyowaspsha-256spring-boot

MD5 and SHA-1 in Java: Why Weak Hashing Is Still Killing Apps (2026)

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.

J

JOptimize Team

May 20, 2026· 7 min read

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.


Why MD5 and SHA-1 Are Broken

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

Where Weak Hashing Hides in Java Code

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());

The Fix: Use the Right Algorithm for the Job

For passwords: use bcrypt, Argon2, or PBKDF2

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 ); }

For file integrity and general hashing: use SHA-256 or SHA-3

// 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);

For tokens and IDs: use SecureRandom or UUID

// 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();

Migrating Existing Password Hashes

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.


Common Mistakes to Avoid

  • Using MD5 for "non-security" purposes - attackers don't care about your intent; if it can be forged, it will be
  • Salting MD5/SHA-1 manually - salting helps against rainbow tables but not against collision attacks or GPU brute-force
  • Using SHA-256 for passwords - SHA-256 is fast, which is great for files but terrible for passwords; always use bcrypt/Argon2 for credentials
  • Hardcoding salt values - salt must be random and unique per password; hardcoded salts defeat the purpose entirely

Summary

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.


Detect Weak Crypto Automatically in Your Codebase

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.

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect issues in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.