This is the fifth day of my participation in the First Challenge 2022

Happy New Year, everyone. I’m Xiao Hei.

When we are developing applications, as long as the user is involved, the login registration function is essential. However, not everyone can do the login function well. For example, how to save the most basic password? It is not clear which encryption should be used to encrypt passwords.

Once the database leakage, password leakage and other problems, will cause great losses to users.

How do I save my password?

If we want to authenticate the user in the server, we need to complete the following steps:

  • Obtain the user name and password of the user to log in;
  • A user is found in the database based on the user name;
  • Compares the password provided by the user with the password in the database.

So how do we store the user’s password? Let’s take a look at some of the ways and the problems.

Definitely save

The user password is saved in plain text.

Obviously, anyone with common sense should know that passwords can’t be stored in plain text. But then again, systems are developed by people, and the people who develop them may not be professional. For example, before a large Chinese developer community, because of database leakage, resulting in a large number of users’ passwords leaked, and their passwords are kept in plain text.

HASH stored

You can use the Hash function to calculate the Hash value of the password to save the password.

The Hash function is a one-way function and cannot reverse the original value from the result value. The Hash function converts a string of passwords into a fixed length string.

  • During user registration, the Hash value of the user password is calculated using the Hash function and saved to the database.
  • When a user logs in, use the same Hash function to calculate the Hash value of the password submitted by the user and compare it with the Hash value in the database.

In this way, the attacker can avoid directly obtaining the plaintext password of the user. It takes a great deal of energy for the attacker to calculate the hash value of the string through brute force attacks, and the longer the hash value is, the more difficult it is to crack.

But through the rainbow table attack, the attacker can still successfully crack. A rainbow table is a pre-calculated Hash table containing millions of passwords that can be easily cracked for simple passwords.

So, if you’re not sure how to keep passwords for the service you’re signing up for, try to keep your password complexity high.

Add salt to Hash

To prevent rainbow table attacks, you can add salt to the Hash algorithm.

Salt is a random sequence that is concatenated with the original password for the Hash calculation.

  • During user registration, the password and salt value are combined for Hash calculation, and the password result is saved in the database.
  • During login authentication, the user adds salt to the original password to Hash the password and compares the result with the password in the database.

Because the password in the rainbow table is different from the password after salt is added, the rainbow table attack can be prevented. If the salt value is long enough and random, it is guaranteed that no hash value can be found in the rainbow table that matches the password.

However, because it is possible for the attacker to obtain salt value, the attacker can adjust the algorithm of rainbow table generation, and use the obtained salt value to calculate a new rainbow table, which can also obtain the password. Computing a new rainbow table takes a lot of time, but as the hardware gets better, it gets easier.

Therefore, using the Hash algorithm to add salt to the password can ensure that the password cannot be cracked quickly, but it is not secure enough.

Cryptographic encryption function

The Hash function is not only designed to Hash passwords, so the Hash function is very fast, but it also allows attackers to quickly compute the Hash value for brute force cracking.

To solve this problem, we can slow down the Hash encryption function.

As long as we make the password encryption time in the user can accept the time, as slow as possible, so that the attacker brute force cracking will take infinite time.

There are several algorithms specifically used to encrypt passwords:

  • bcrypt
  • scrypt
  • PBKDF2
  • argon2

These algorithms use complex encryption algorithms and deliberately slow down calculations.

Work factors

You can adjust the slowness of the calculation time of the encryption function by configuring the work factor in the algorithm.

Each cryptographic algorithm has its own working factor. The working factor affects the speed at which a cipher is encoded. For example, bcrypt has the parameter Strength, and the algorithm will raise 2 to the strength power to compute the hash value. The larger the number, the slower the encoding.

Encrypt passwords using Spring Security

Now let’s look at how Spring Security supports these algorithms and how we can use them to encrypt passwords.

PasswordEncoder

There is a PasswordEncoder interface in Spring Security. All cipher encoders implement this interface.

public interface PasswordEncoder {
    String encode(CharSequence rawPassword);

    boolean matches(CharSequence rawPassword, String encodedPassword);

    default boolean upgradeEncoding(String encodedPassword) {
        return false; }}Copy the code

There are two methods in this interface:

Encode () method the user converts the plaintext password into ciphertext;

The matches() method compares the plaintext password with the ciphertext password.

BCryptPasswordEncoder

String plainPassword = "123456";
// Work factor
int strength = 10;
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength, new SecureRandom());
String encodedPassword = bCryptPasswordEncoder.encode(plainPassword);
System.out.println(encodedPassword);
Copy the code

The parameter strength in BCryptPasswordEncoder is the working factor of the password encryption algorithm, and the default value in Spring Security is 10.

Specify SecureRandom as the random salting generator at creation time.

$2a$10$pYxXvggEgN7znYKofHIr/uRTw.dsYeW9mbxzNMSNOoGIYZU8twXNG
Copy the code

Pbkdf2PasswordEncoder

The PBKDF2 algorithm is not designed specifically for cryptography, but for deriking keys from cryptography. When we want to encrypt some data with a password, we usually need a key, but the password is not strong enough to be used as an encryption key.

String plainPassword = "123456";
// Encrypt the key
String pepper = "Black says JAVA.";
// hash times
int iterations = 200000;
// Hash length
int hashWidth = 256;

Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);
pbkdf2PasswordEncoder.setEncodeHashAsBase64(true);
String encodedPassword = pbkdf2PasswordEncoder.encode(plainPassword);
System.out.println(encodedPassword);
Copy the code

Pbkdf2PasswordEncoder runs the hash algorithm multiple times on a normal password. We can define the hash length of the output and use pepper to make the password encoding more secure.

WnCG4wMZFHPAD9DGg+SChNceQqbeAZRQyf2OHCK5WKdYBRzbeAGsQg==
Copy the code

Pbkdf2PasswordEncoder performs 185000 hash calculations by default. The default hash length is 256.

SCryptPasswordEncoder

The SCryptPasswordEncoder algorithm sets CPU and memory costs to make it harder for an attacker to crack a password.

String plainPassword = "123456";
/ / CPU consumption
int cpuCost = (int) Math.pow(2.14);
// Memory consumption
int memoryCost = 8;
// currently not supported by Spring Security
int parallelization = 1;
// The length of the key
int keyLength = 32;
// Salt length
int saltLength = 64;

SCryptPasswordEncoder sCryptPasswordEncoder = new SCryptPasswordEncoder(
    cpuCost,
    memoryCost,
    parallelization,
    keyLength,
    saltLength);
String encodedPassword = sCryptPasswordEncoder.encode(plainPassword);
System.out.println(encodedPassword);
Copy the code

The following output is displayed:

$e0801$PgZZvXdDjbxMZJi4eidFCHblUdvwOT/n0FZFyCWIHloqL6Wkbk7bAJ2nwVIWsW9PJTodncEtok1qcaWR+u+pZg==$lcqK7ACDTv8gG3ZwGoz0X7rn4EnZvnEcZ7rS0Qq31Ng=
Copy the code

Argon2PasswordEncoder

The Argon2 algorithm was the winner of the 2015 Password Hashing contest. The algorithm also allows us to adjust CPU and memory costs. This algorithm stores all parameters in the result string.

int saltLength = 16;
int hashLength = 32;
int parallelism = 1;
int memory = 4096;
int iterations = 3;

Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder(
    saltLength,
    hashLength,
    parallelism,
    memory,
    iterations);
String encodePassword = argon2PasswordEncoder.encode(plainPassword);
Copy the code

The following output is displayed:

$argon2id$v= 19$m=4096,t=3,p=1$uft4b+crs6tiwOhDnuFsIg$d/GXjYZnEw+/ubVnPqNeQDFX32GRYe+yTwuwydXLjos
Copy the code

Set PasswordEncoder in Spring Boot

Next, to better understand how PasswordEncoder works in Spring Boot, let’s develop a Rest Api and configure Spring Security to support password-based authentication.

Configuration PasswordEncoder

First, we create a Rest API that needs Spring Security protection:

@RestController
public class BlogRest {

    @GetMapping(path = "/blogs")
    public List<Blog> blogs(a) {
        return Lists.newArrayList(new Blog("hello world"."Black says Java.")); }}Copy the code

We need access to the/Blogs interface to be verified by the user. Therefore, we use the Spring Security configuration:

/ * * *@authorHei says Java *@ClassName SecurityConfiguration
 * @Description
 * @date2022/2/3 * * /
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
            .csrf()
            .disable()
            .authorizeRequests()
            .antMatchers("/registration")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic();
    }
    // other codes.
}
Copy the code
  • This configuration indicates that except/registrationAll other request paths require authentication.
  • Spring Security checks whenever an HTTP request is sent to an applicationHeaderDoes it includeAuthorization: Basic <credentials>.
  • If the Header is not set, the server returns401;
  • If Spring Security finds the corresponding Header, it authenticates.

When Spring Security performs authentication, it needs to query the user name and password information from the database. It needs to provide an implementation class of the UserDetailsService interface to implement the loadUserByUsername method of the interface. So we define the interface DatabaseUserDetailsService as follows:

/ * * *@authorHei says Java *@ClassName DataBaseUserDetailService
 * @Description
 * @date2022/2/3 * * /
@Service
@Transactional
public class DataBaseUserDetailService implements UserDetailsService {

    private final UserDAO userDAO;

    private final UserMapper userMapper;

    public DataBaseUserDetailService(UserDAO userDAO, UserMapper userMapper) {
        this.userDAO = userDAO;
        this.userMapper = userMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDAO.selectByUserName(username);
        returnuserMapper.toUserDetails(user); }}Copy the code

The implementation of the AuthenticationProvider interface in Spring Security uses the UserDetailsService to perform the authentication logic during authentication.

AuthenticationProvider the implementation of the interface has a lot of, because of our user information exists in the database, so we use DaoAuthenticationProvider:

@Configuration
@EnableWebSecurity
class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final DatabaseUserDetailsService databaseUserDetailsService;

    // constructor ...

    @Bean
    public AuthenticationProvider daoAuthenticationProvider(a) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // Set the password encryptor
        provider.setPasswordEncoder(passwordEncoder2());
        // Set the user information query service
        provider.setUserDetailsService(this.databaseUserDetailsService);
        return provider;
    }

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

    // ...

}
Copy the code

At this point, we have configured Spring Security so that if a client sends an HTTP request with a Header with basic authentication, Spring Security will read the Header information and get the user information in the database based on username. BCryptPasswordEncoder is used to verify the password. If the password is the same, the authentication succeeds. If not, the server responds with 401.

User Registration Service

Before authenticating the user, we need to save the user in the database, that is, the user needs to register an account first. So let’s implement a user registration interface:

@RestController
public class UserRest {

    private final UserRegistrationService userRegistrationService;

    public UserRest(UserRegistrationService userRegistrationService) {
        this.userRegistrationService = userRegistrationService;
    }

    @PostMapping("/registration")
    @ResponseStatus(code = HttpStatus.CREATED)
    public void register(@RequestBody UserDTO user) {
        // Register a useruserRegistrationService.register(user); }}Copy the code

As defined by our Spring Security rules, access to the /registration path does not require authentication.

We register method invokes the userRegistrationService. Register (user) for user registration.

@Service
@Transactional
public class UserRegistrationService {

    private final UserDAO userDAO;

    private final PasswordEncoder passwordEncoder;

    public UserRegistrationService(UserDAO userDAO, PasswordEncoder passwordEncoder) {
        this.userDAO = userDAO;
        this.passwordEncoder = passwordEncoder;
    }

    public void register(UserDTO userDTO) {
        User user = newUser(); user.setUserStatus(UserStatusEnum.INFORCE.getStatus()); user.setUsername(userDTO.getUsername()); user.setPassword(passwordEncoder.encode(userDTO.getPassword())); userDAO.insert(user); }}Copy the code

During user registration, we use PasswordEncoder to encrypt the plaintext password provided by the user and save it in the database.

summary

This is the main content of this issue, we talked about how to save passwords in the system, the most common sense and security awareness is plaintext save; Using the Hash algorithm to save will be attacked by the rainbow table, also not recommended; Although salted Hash encryption can reduce the possibility of rainbow table attack to a certain extent, it may also be attacked by rainbow table with the development of hardware performance, so we should select some specific password encryption algorithms. Such as Bcrypt, Pbkdf2, Scrypt, Argon2, etc.

Finally, we completed a user login and registration function by SpringBoot+Spring Security.

Hope this article can be helpful to you. It is not easy to write and you need some positive feedback.

I am xiao Hei, a programmer in the Internet “casual”

Water does not compete, you are in the flow