CodeGym /Courses /Module 5. Spring /Password hashing with BCryptPasswordEncoder

Password hashing with BCryptPasswordEncoder

Module 5. Spring
Level 17 , Lesson 7
Available

When it comes to passwords, remember two key things:

  1. Passwords are very personal data. If your server gets compromised, leaked passwords can lead not only to your app being broken into, but to other services of your users being hijacked too, since people often reuse passwords.
  2. Passwords should never be "visible". Never, ever store passwords in the database in plain text. That's about as bad as forgetting the exit condition in an if statement. Even if you think your server is secure, always treat passwords as if a leak is inevitable.

So we don't store them. We only store their hashes.

What is hashing?

You've already seen hashing and hash tables when learning Java. Still, a quick reminder: hashing is a one-way process that converts a string (for example, a password) into a fixed-length string of characters using a specific algorithm. For example:

  • Password: password123
  • Hash (for example, via MD5): 482c811da5d5b4bc6d497ffa98491e38

Hashing is irreversible — you can't recover the original password from the hash. So why use hashes? When a user enters a password, we hash it again and compare it to the stored hash.

But there's a catch…

Why plain hashes aren't enough?

Modern computers are powerful enough to compute millions of hashes per second. Even if you use MD5, an attacker can recover the password via a dictionary attack or brute force.

That's why we need a "salt" — random data added to the password before hashing. It's like a unique signature for every password.

BCrypt — a modern password guardian

BCrypt was designed specifically for safe password storage. Why is it so good?

  • Built-in salt — it automatically adds random data to each password. Even if two users pick "123456", their hashes will be totally different.
  • Smart slow-down — it intentionally makes hashing a bit slower. For a single password that's negligible, but trying millions of variants becomes very time-consuming.
  • Grows with technology — you can increase the hashing cost as computers get faster.

Spring Security gives you all that protection via the simple class BCryptPasswordEncoder. One line of code and your passwords are under solid protection!


BCryptPasswordEncoder in action

Adding a dependency

Chances are you already have Spring Security as a dependency, but if not, add this to your project's pom.xml:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Now you've got BCryptPasswordEncoder in your toolkit.

Hashing a password

Let's write a small example. Suppose we need to register a user with a password:


import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PasswordHashingExample {
    public static void main(String[] args) {
        // Create BCryptPasswordEncoder object
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        // Original password
        String rawPassword = "my_secure_password";

        // Hash the password
        String encodedPassword = passwordEncoder.encode(rawPassword);

        // Print the hashed password
        System.out.println("Hashed password: " + encodedPassword);
    }
}

Sample output:


Hashed password: $2a$10$Dow7EvhZyJ1NPo6QK9j.K.uZt6WbV1g3DQQu7GptucjPnlzxFJq9e

Verifying a password

When a user tries to log in, we need to compare the entered password with the stored hash:


public class PasswordVerificationExample {
    public static void main(String[] args) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        // Hashed password we store (for example, in the database)
        String storedPasswordHash = "$2a$10$Dow7EvhZyJ1NPo6QK9j.K.uZt6WbV1g3DQQu7GptucjPnlzxFJq9e";

        // Password entered by the user
        String enteredPassword = "my_secure_password";

        // Compare
        boolean isPasswordMatch = passwordEncoder.matches(enteredPassword, storedPasswordHash);

        System.out.println("Passwords match? " + isPasswordMatch);
    }
}

That's it: using matches you can verify a password in milliseconds.


Integration with Spring Security

Now let's combine what we've learned with a Spring Boot app.

Registering BCryptPasswordEncoder as a bean

Add a bean in your configuration class:


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Why a bean? Because most Spring Security components, like registration and authentication handling, will automatically use that bean for encoding and verifying passwords.

Example usage in UserDetailsService

Suppose we have a custom UserDetailsService implementation that loads user data from a database:


import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public CustomUserDetailsService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));

        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword()) // Hashed password from DB
                .roles(user.getRoles().toArray(new String[0]))
                .build();
    }

    public void registerUser(String username, String rawPassword) {
        String encodedPassword = passwordEncoder.encode(rawPassword);
        userRepository.save(new User(username, encodedPassword));
    }
}

Note that during registration we use passwordEncoder.encode(rawPassword).


Practice: user registration and login

Now let's build a simple app that lets users register and log in. Here are the main steps:

registration controller

Add a REST endpoint for registration:


import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final CustomUserDetailsService userDetailsService;

    public AuthController(CustomUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @PostMapping("/register")
    @ResponseStatus(HttpStatus.CREATED)
    public String registerUser(@RequestParam String username, @RequestParam String password) {
        userDetailsService.registerUser(username, password);
        return "User registered successfully!";
    }
}

Testing

  1. Register a user via a POST request:
    
    POST /auth/register
    {
        "username": "test_user",
        "password": "strong_password"
    }
    
  2. Check the database: the password should be stored as a hashed value.

Common mistakes

  • Leaving passwords in plain text even at intermediate stages. Don't ever do that.
  • Using outdated algorithms like MD5 or SHA-1. They're obsolete and not suitable for passwords.
  • Trying to compare passwords manually without using matches. That can easily lead to bugs.

For correct behavior, rely on BCryptPasswordEncoder — it already includes the necessary precautions.


Practical use

BCryptPasswordEncoder is the standard for most applications. If you claim to build a secure app — e.g., e-commerce sites, user-data platforms, or banking systems — you'll need this tool.

You'll also frequently get questions about password hashing in interviews, so now you're ready. Try explaining to your interviewer the difference between MD5 and BCrypt — you might get a little karma boost.

Now you're ready for the next step: working with tokens and sessions!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION