Insecure encryption algorithm - MD5 - Java

Insecure encryption algorithm - MD5 - Java

Need

Implementation of secure encryption algorithms for password hashing

Context

  • Usage of Java for building cross-platform applications
  • Usage of java.security.MessageDigest for cryptographic hashing and message digest operations
  • Usage of javax.xml.bind.DatatypeConverter for XML data conversion and manipulation
  • Usage of javax.servlet.http.HttpServlet for handling HTTP requests in Java Servlets
  • Usage of javax.servlet.ServletException for handling servlet-related exceptions
  • Usage of java.io.IOException for handling input/output errors in Java
  • Usage of NoSuchAlgorithmException for handling Java security algorithms

Description

Non compliant code

        import java.security.*;
import javax.xml.bind.DatatypeConverter;
import javax.servlet.http.*;

public class PasswordHashingServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String password = request.getParameter("password");

        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(password.getBytes());
            byte[] digest = md.digest();
            String myHash = DatatypeConverter.printHexBinary(digest).toUpperCase();
            
            // Store the hashed password in the database
            // ...
        } catch (NoSuchAlgorithmException e) {
            // Handle the exception
        }
    }
}
        
        

The above Java Servlet handles a POST request to receive a password from the client. It then uses the MD5 algorithm to hash the password. The MD5 MessageDigest instance is created with MessageDigest.getInstance("MD5"). The password string is converted to bytes and updated to the MessageDigest with md.update(password.getBytes()). The hashing is done with md.digest() which is then converted to a hexadecimal string.

The MD5 hash is considered insecure for hashing passwords. The main reason is that it's fast. This might seem like a good thing, but when it comes to password cracking, it's a disadvantage. A faster algorithm means a brute force attack can try more possibilities in a shorter time. Additionally, MD5 is vulnerable to hash collisions, where different inputs produce the same hash output.

In this scenario, an attacker who is able to access the hashed passwords (for example, by exploiting a SQL injection vulnerability or a database breach) could use pre-computed tables of MD5 hashes (also known as rainbow tables) or even brute force attacks to reverse-engineer the passwords. This is a serious security risk and could lead to unauthorized access to user accounts.

Steps

  • Replace the use of MD5 algorithm with a stronger and more secure hashing algorithm such as SHA-256 or bcrypt.
  • Update the code to use the recommended hashing algorithm.
  • Ensure that the chosen hashing algorithm is implemented correctly and securely.
  • Consider using a salt value to further enhance the security of the hashed passwords.
  • Update the database schema to accommodate the new hashing algorithm and any additional data required for salting.
  • Update any other parts of the application that interact with the password hashing functionality to use the new algorithm.

Compliant code

        import java.security.*;
import javax.xml.bind.DatatypeConverter;
import javax.servlet.http.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Base64;

public class PasswordHashingServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String password = request.getParameter("password");
        byte[] salt = new byte[16]; // Consider a secure way to generate and store salt per user

        try {
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            byte[] hash = factory.generateSecret(spec).getEncoded();
            String securePassword = Base64.getEncoder().encodeToString(hash);
            
            // Store the hashed password and salt in the database
            // ...

        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            // Handle the exception
        }
    }
}
        
        

The updated code replaces the insecure MD5 hashing algorithm with the more secure PBKDF2WithHmacSHA256 algorithm. This algorithm is currently recommended for password hashing as it is computationally expensive, which makes brute-force and rainbow table attacks more difficult.

The password is first converted to a character array, which is then combined with a salt value and passed to the PBEKeySpec constructor along with the iteration count and the desired key length. The salt is a random value that should be generated for each user individually and stored in the database along with the hashed password. It is used to prevent pre-computed rainbow table attacks.

The SecretKeyFactory instance is then used to generate a SecretKey from the specified key specification. The getEncoded method is used to retrieve the key in its primary encoding format, which is then encoded to a string using Base64 encoding.

The hashed password (and salt) should then be stored in the database. Any part of the application that interacts with the password hashing functionality should be updated to use this new algorithm.

References