-
PasswordEncoder, BcryptPasswordEncoderSpring 2023. 1. 19. 11:37
절대로 패스워드를 평문으로 저장해서는 안된다.
- 해당 서비스의 관리자가 DB에서 모든 사용자의 비밀번호를 볼 수 있으며 DB가 노출되는 경우 공격자에게 계정 정보가 넘어갈 수 있다.
- id와 password를 동일하게 여러 곳에서 사용하는 경우가 많아서 추가적인 피해가 발생할 수 있다.
PasswordEncoder
Spring Security 에서는 비밀번호를 안전하게 저장할 수 있도록 비밀번호의 단반향 암호화를 지원하는 PasswordEncoder 인터페이스와 구현체들을 제공한다.
public interface PasswordEncoder { // 비밀번호를 단방향 암호화 String encode(CharSequence rawPassword); // 암호화되지 않은 비밀번호(raw-)와 암호화된 비밀번호(encoded-)가 일치하는지 비교 boolean matches(CharSequence rawPassword, String encodedPassword); // 암호화된 비밀번호를 다시 암호화하고자 할 경우 true를 return하게 설정 default boolean upgradeEncoding(String encodedPassword) { return false; }; }
PasswordEncoder를 이용하여 실제로 DB에 패스워드를 저장할 때, encode 하여 저장하면 된다.
public User saveNewUser(@Valid SignUpForm signUpForm) { User newUser = User.builder() .email(signUpForm.getEmail()) .nickname(signUpForm.getNickname()) .password(passwordEncoder.encode(signUpForm.getPassword())) .build(); return userRepository.save(newUser); }
passwordEncoder.encode(password);
유저가 입력한 패스워드를 Bcrypt로 해싱하여 저장하게 되고, 아래와 같이 DB에 저장된다.
BCrypt 암호화
복호화가 가능한지에 따라 양방향 / 단방향으로 구분되고,
복호화 할 때 사용하는 비밀키가 암호화 할 때 그대로 사용되면 대칭키(비밀키), 서로 다른 키를 사용하면 비대칭키(공개키) 이다.
BCrypt 알고리즘은 HASH 알고리즘으로 복호화가 불가능한 단반향 알고리즘이다.
Spring Security에서 채택하고 사용하고 있으며, 패스워드를 저장하기 위한 목적으로 개발되어 패스워드 암호화에 최적화 되어있다.
단방향 암호화의 단점을 보완하기 위해 salting이 추가되었다.
* salt
해시 함수에 넣기 전에 난수를 추가하므로 인해 복잡도를 올리고 보안성을 높인다. DB에는 [Salt]값과 [비밀 번호 + Salt값]의 해시가 함께 저장되며, 유저는 이후 로그인 할 때마다 실제 비밀 번호를 입력하면, 서버는 해시값을 DB와 비교하여 입장 여부를 결정한다.
passwordEncoder Bean 설정
@Bean public PasswordEncoder passwordEncoder(){ return PasswordEncoderFactories.createDelegatingPasswordEncoder(); }
여기서 createDelegatingPasswordEncoder()를 확인해보면, 다양한 passwordEncoder가 정의되어 있다.
PasswordEncoder를 여러개 선언한 뒤, 상황에 맞게 골라쓸 수 있도록 지원하는 Encoder이다.
public static PasswordEncoder createDelegatingPasswordEncoder() { String encodingId = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(encodingId, new BCryptPasswordEncoder()); encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder()); encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5")); encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); encoders.put("scrypt", new SCryptPasswordEncoder()); encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1")); encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256")); encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); encoders.put("argon2", new Argon2PasswordEncoder()); return new DelegatingPasswordEncoder(encodingId, encoders); }
BcryptPasswordEncoder
BcryptPasswordEncoder는 위의 PasswordEncoder의 구현 클래스이며, Bcrypt 해시 함수를 사용해 비밀번호를 암호화한다.
BCrypt 해싱 함수(BCrypt hashing function)를 사용해서 비밀번호를 해싱해주는 encode() 메서드와 확인 요청된 비밀번호와 저장된 비밀번호의 일치 여부를 확인해주는 matches() 메서드를 제공한다.
encode()
@Override public String encode(CharSequence rawPassword) { if (rawPassword == null) { throw new IllegalArgumentException ("rawPassword cannot be null"); } String salt = getSalt(); return BCrypt.hashpw(rawPassword.toString(), salt); }
내부적으로 getSalt() 메서드를 통해 매번 새로운 salt 값을 생성한다.
사용자에게 입력받은 password 값과 이렇게 내부적으로 생성되는 salt 값을 가지고 BCrypt.hashpw() 메서드에서 최종적으로 해싱된 비밀번호 값을 얻게 된다.
matches()
@Override public boolean matches (CharSequence rawPassword, String encodedassword) { if (rawPassword == null) { throw new IllegalArgumentException ("rawPassword cannot be null"); if (encodedPassword == null || encodedPassword.Length() == 0) { this.logger.warn ("Empty encoded password"); return false; } if (!this.BCRYPT_PATTERN.matcher (encodedPassword) .matches()) { this.logger.warn("Encoded password does not look like BCrypt"); return false; } return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
matches() 메서드를 통해 입력한 password 와 저장된 password의 일치 여부를 확인한다.
'Spring' 카테고리의 다른 글
Spring의 @EventListener (0) 2023.01.30 Entity와 DTO 분리하기 (0) 2023.01.25 Setter 사용을 지양해야 하는 이유 (0) 2023.01.19 Spring Security : Web Ignore (0) 2023.01.19 @EqualsAndHashCode, equals, hashCode (0) 2023.01.18