Home > other >  Bcrypt providing different hash for password while login and registration
Bcrypt providing different hash for password while login and registration

Time:02-15

I am new to bcrypt and not sure i understand how is it working. I have a registration endpoint as below where i accept user information including username and password from the user. I use bcrypt to encode this password and store it in DB.

While login i take username and password from user as input. I encode the provided password and compare the hash to the once stored in DB. Somehow bcrypt is giving out two different hashes for same password causing the login to fail.

Below are my controller , service and utility classes :

Controller :

@RestController
@RequestMapping(value = "/user")
public class UserController {

private static final Logger logger = LogManager.getLogger(UserController.class);


@Autowired
RegistrationService registrationService;

@Autowired
LoginService loginService;

/**
 * Method to allow user to register for application
 * @param request
 * @return
 */
@RequestMapping(value = "/register", method = RequestMethod.POST, produces = "application/json" )
public boolean registerUser(@Valid @RequestBody RegistrationRequestDTO request) {
    logger.debug("Register endpoint initiaited to create a new user!!");
    registrationService.saveUserDetails(request);
    logger. info("New user with login id : "   request.getUserName()   " has been created!!");  
    return true;
}

/**
 * Method to authenticate user
 * @param request
 * @return
 * @throws Exception
 */
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json")
public boolean login(@Valid @RequestBody LoginRequestDTO request) throws Exception {
    logger.debug("Login endpoint has been called!!!");
    if(request == null) {
        logger.error("No credentials received in the request!!");
        throw new Exception("No user credentials recieved");
        
    }
    
    boolean isUserAuthenticated = loginService.validateUser(request);
    return isUserAuthenticated;
    
}

}

Service :

@Service
public class LoginService {

private static final Logger logger = LogManager.getLogger(LoginService.class);

@Autowired
UserUtility userUtility;

@Autowired
UsersDAO usersDAO;

public boolean validateUser(LoginRequestDTO request) throws Exception {
    
    String userName = request.getUserName();
    String password = request.getPassword();
    logger.info("User  : "   userName   " is logging in");
        List<Users> users = usersDAO.fetchUserForUserName(userName);
        if(users.size() > 1) {
            logger.error("Duplicate records found for user name : "   userName);
            throw new LoginException("Duplicate record found!!");
        }else if(users.size() == 0) {
            logger.error("No records found for user name : "   userName);
            throw new LoginException("User name is invalid!!");
        }
        
        Users user = users.get(0);
        boolean isUserAuthenticated = userUtility.matchPassword(password, user);
    return isUserAuthenticated;
    
}

}

@Service
public class RegistrationService {

@Autowired
ModelMapper modelMapper;

@Autowired
UserUtility userUtility;

@Autowired
UsersDAO usersDAO;

Logger logger = LogManager.getLogger(RegistrationService.class);

public boolean saveUserDetails(RegistrationRequestDTO registrationRequestDTO) {
    
    logger.debug("User details recieved from customer -- "   registrationRequestDTO.toString());
    
    Users user = modelMapper.map(registrationRequestDTO, Users.class);
    user.setPassword(userUtility.createPasswordHash(user.getPassword()));
    usersDAO.saveUser(user);
    
    return true;
}

}

Utility Class :

@Component
public class UserUtility {

Logger logger = LogManager.getLogger(UserUtility.class);

@Autowired
PasswordEncoder encoder;

public String createPasswordHash(String password) {
    
    if(StringUtils.isEmpty(password)) {
        logger.error("Password recieved is either empty or null!!");
        return null;
        
    }
    
    return encoder.encode(password);
}

public boolean matchPassword(String password, Users user) throws Exception{
    
    if(StringUtils.isEmpty(password)) {
        logger.error("Password not entered by user");
        throw new Exception("Password dnot entered by user!!");
    }
    
    String hashedPasswordProvided = createPasswordHash(password);
    logger.info("Hashed Password Provided : "   hashedPasswordProvided);
    logger.info("Hashed Password Stored : "   user.getPassword());
    String hashedPasswordStored = user.getPassword();
    if(hashedPasswordProvided.equals(hashedPasswordStored))
        return true;
    else
        return false;
    
}

}

I am not able to figure out why the same library is providing two different hashed for same string. I am not using salt here while encrypting the password on registration as well as login.

CodePudding user response:

That's perfectly normal. In order to generate password hash, BCrypt implementation of PasswordEncoder uses randomly generated salt. That salt is generated every time you hash/encode a raw password.

That's why comparing raw input password with hash like this:

encoder.encode("raw-input").equals("hashed-password-stored");

is wrong. Every time you encode raw-input you will get different password hash.

You should use this method - boolean matches(CharSequence rawPassword, String encodedPassword) provided by PasswordEncoder. Like this:

encoder.matches("raw-input", "hashed-password-stored");

You should read the docs about password encoders.

CodePudding user response:

While login i take username and password from user as input. I encode the provided password and compare the hash to the once stored in DB. Somehow bcrypt is giving out two different hashes for same password causing the login to fail.

Which is why it fails. That is not how BCrypt works. Each time you encode a password it will do so with a new random salt, which is actually part of the hash. Which is why you need the actual hash, to re-hash the entered password and then compare.

If you take a look at the Javadoc of the PasswordEncoder you will see a matches method. Which is what you should use. So instead of creating the hash and comparing yourself, use that method instead.

public boolean matchPassword(String password, Users user) throws Exception{
    
    if(StringUtils.isEmpty(password)) {
        logger.error("Password not entered by user");
        throw new Exception("Password dnot entered by user!!");
    }
    return passwordEncoder.matches(password, user.getPassword());    
}

NOTE: All of this looks like you are working around Spring Security (only using the password encoded and trying to do the login yourself instead of letting Spring Security handle it!).

  • Related