Unlocking the Secrets of Password Encryption

Published on
6 mins read
––– views
thumbnail-image

Introduction

Today I received a task like this:

Find accounts using default passwords in the system (System uses Laravel 5.8)

Initially, I thought that I just needed to retrieve the hash of the default password and query the database for accounts using passwords that match that hash. However, when I executed the query, the result was 0 records.

Let's explore the reasons for this issue in this blog.

1. Why do we need to hash passwords ?

Encrypting passwords is a familiar concept for every programmer. It's a crucial step to ensure user information is protected, even if our database is compromised. There have been many cases of web applications leaking user information, which becomes more dangerous if passwords aren't carefully encrypted. Many people use the same password for most of their accounts, so when one password is leaked, all other accounts are at risk of being stolen. Therefore, we should never store passwords as plain text.

One of the most common methods to address this issue is using password hashing techniques.

2. Password hashing basics

Hashing

Password hashing is a widely used method for protecting passwords. A hash function is a one-way function that converts the original password into an encrypted string that cannot be decrypted. Each unique hash value corresponds to a specific password. We store this hash value in the database. When a user logs in, the system hashes their input password and compares this hash value to the stored value in the database to authenticate.

Example: "Tupt.dev" hashed with the MD5 algorithm will produce the following result: "c229312e9bc2d88dd9365c9dc8b04cbb".

With today's computer technology, decrypting hash functions is impossible. However, if not careful, attackers can still retrieve passwords if they have the hash value. Two common attack methods are:

  • Rainbow table attack (using precomputed password dictionaries).
  • Brute force attack (trying all possible combinations).

To resolve this issue, we need to delve into Salting and Stretching.

Salting

As mentioned above, simple password hashing is not enough to protect against pre-calculation attacks. To handle this issue, we use Salt. Salting is the process of adding a random piece of data to the original password before hashing.

Salting is a simple yet highly effective technique. It significantly increases the complexity of the password, making the original password longer and more intricate. By introducing salt, we render pre-calculation attacks ineffective. Each user account is assigned a unique random salt value during registration. Even two accounts with the same password will not produce the same hash value due to the salt. Since salting's primary purpose is to thwart pre-calculation attacks, there's no need to establish a separate storage system for salt. Typically, salt is stored directly in the same table as the password's hash value.

Stretching

Alongside rainbow table attacks, brute force is another common attack method. If the computational process is not too complex, an attacker can quickly try all possibilities to crack a user's password. To combat this type of attack, we employ the stretching method.

Stretching increases the complexity of the hashing algorithm, making brute force attacks significantly more difficult. To achieve this, the hashing process is repeated thousands of times. While this adds a slight delay to the user authentication process, it exponentially increases the time required for an attacker to crack the password. With a well-protected password, brute-forcing it becomes virtually impossible.

3. Bcrypt

The bcrypt hashing sequence is usually presented in the following format:

$[algorithm]$[cost]$[22 characters salt][31 characters hash]

Syntax:

  • algorithm: Hash algorithm used. In this case $2y$, represents the bcrypt algorithm version 2.
  • cost: Computational cost, expressed as a positive integer. This value determines the number of iterations of the Blowfish hash function during hash generation. The higher the value, the slower the hashing process but also the more secure it is.
  • salt: 16 byte (128 bit) random salt string, base64 encoded into 22 characters. The purpose of salt is to increase security by making it more difficult to create a rainbow table.
  • hash: The actual hash of the password, generated by combining the password with a salt and using the Blowfish hashing algorithm. This hash is 24 bytes (192 bits) long and is base64 encoded into 31 characters.

Example: The hash sequence $2y$10$RT2Pnlx7aaG2EDHy8SgdIunndkeiLB.kUTzSdVoKgL2x/TEr1tkZG can be parsed as follows:

  • Algorithm: Bcrypt version 2 ($2y$)
  • Cost: 1024 iterations (=2^10)
  • salt: RT2Pnlx7aaG2EDHy8SgdIu (base64 encoded from 16 byte random salt string)
  • Hash code: nndkeiLB.kUTzSdVoKgL2x/TEr1tkZG (base64 encoded from 24 byte hash code)

Meaning:

This hash sequence indicates that the password has been hashed using the bcrypt algorithm version 2 with 1024 iterations. Random salt RT2Pnlx7aaG2EDHy8SgdIu was used to increase security. The actual hash of the password is nndkeiLB.kUTzSdVoKgL2x/TEr1tkZG.

** Note**:

  • The bcrypt hash sequence cannot be decrypted to retrieve the original password.
  • Higher hashing cost results in longer hash generation time but provides better security.
  • Random salt plays an important role in defending against rainbow table attacks

4. How to solve the above problem

I wrote a script with the following scenario

  1. Connect to database, query all users
  2. Loop through each user, checking the default password with the password hash using the password_verify function => If true means that user uses the default password.

Below is the sample code

<?php
// Connect to database
$servername = "localhost";
$username = "your_username";
$password = "your_password";
$dbname = "your_database";

$conn = mysqli_connect($servername, $username, $password, $dbname);

if (!$conn) {
    die("Connection failed: " . mysqli_connect_error());
}

// default password
$password_default = 'tupt.dev';

// Query database get info users
$sql = "SELECT name, password FROM users";
$result = mysqli_query($conn, $sql);

if (mysqli_num_rows($result) > 0) {
    // Loop user
    while($row = mysqli_fetch_assoc($result)) {
        $username = $row["name"];
        $hashed_password = $row["password"];

        // Check hashed password with default password
        if (password_verify($password_default, $hashed_password)) {
            echo "User: " . $username . "<br>";
        }
    }
} else {
    echo "No one uses the default password";
}
mysqli_close($conn);
?>

Conclusion

Through this article we have explored password encryption in more depth, common types of attacks, and how to defend against them using salts and stretching.

Besides that, we learned about the bcrypt hash function and how these concepts are applied in practice.

Happy coding