Password Encoder로 비밀번호 암호화 처리해보기
암호화의 종류는 크게 2가지가 있다.
단방향 암호화와 양방향 암호화가 있다.
단방향 암호화
- 단방향 암호화는 평문을 암호화 할 수는 있지만 암호화된 문자를 다시 평문으로 복호화가 불가능한 방식이다.
- 주로 해시 알고리즘을 이용하여 단방향 암호화를 구현한다.
- 단방향 암호화를 사용하는 주된 이유는 메시지 또는 파일의 무결성(integrity)을 보장하기 위해서다. 원본의 값이 1bit라도 달라지게 된다면, 해시 알고리즘을 통과한 후의 해시값은 매우 높은 확률로 달라진다.
- 이를 통해 메시지나 파일의 원본 여부를 파악할 수 있다. 해시의 무결성을 보장하는 특징을 이용하여 저자 서명, 파일 또는 데이터의 식별자, 사용자의 비밀번호, 블록체인 등에서 활용되고 있다.
- 대표적인 해시 알고리즘으로는 MD5, SHA 등이 있다.
양방향 암호화(대칭키)
- 양방향 암호화는 암호화된 값을 암호화 하기 이전 값으로 복호화 할 수 있다.
- 단방향 암호화와는 반대 개념으로 볼 수 있다. 양방향 암호화는 암호화 알고리즘과 키(Key)을 이용해서 암호화를 진행 하는데, 이 키를 통해 암호화된 값을 보호할 수 있다.
- 대칭키 방식은 암호화를 진행시 사용하는 키와, 암호문으로부터 평문을 복호화 할 때 사용하는 키가 동일한 시스템이다. 따라서 암호화를 진행할 때 사용한 키를 모른다면 해당 암호문은 다시 복호화 할 수 없다.
- 대표적인 대칭키 양방향 알고리즘으로는 AES가 있다. AES는 128, 192, 256 비트의 정해진 길이의 키를 사용한다. 암호화 진행시 키의 길이가 길어져 복잡할수록 암호화도 복잡하게 구성된다. 그만큼 컴퓨터의 자원이 많이 필요하기 때문에 적절한 길이의 키를 사용해야 된다.
비밀번호의 경우 단방향 암호화가 일반적으로 사용되어 진다.
spring에서 제공하는 PasswordEncoder
단방향으로 암호화 된 경우는 복호화가 불가능하기 때문에 암호를 알아내기 어렵다.
단방향 암호화의 경우 암호화를 진행할 때에 해시 알고리즘을 사용한다고 했는데, 해시 알고리즘은 동일한 평문에 대해서 항상 동일한 해시값을 가진다. 따라서 특정 해시 알고리즘에 대해서 특정 평문이 어떤 해시 값을 가지는지 알아낼 수가 있다.
이런 특징을 이용하여 해시 함수의 해시값들을 대량으로 정리한 테이블이 존재하는데 이를 레인보우 테이블이라고 한다. 그리고 이 테이블을 사용해 사용자의 정보를 해킹하는 공격을 레인보우 공격이라고 한다.
이렇듯 단방향 암호화를 사용할지라도 해킹에 대한 가능성이 존재한다.
BcryptPasswordEncoder의 Bcrypt 방식 Salting & Key Stretching
위 레인보우 공격을 방지하기 위해서 PasswordEncoder에서는 Bcrypt 방식의 암호화 방식을 많이 사용한다.
PasswordEncoder에서 제공하는 BcryptPasswordEncoder를 사용하면 이 암호화에 대한 결과값이 항상 다르게 나온다. PasswordEncoder의 경우는 해시 알고리즘을 사용할 뿐만 아니라 salt(바이트 단위의 임의의 문자열)을 추가로 사용해서 평문 암호를 encode하기 때문에 매번 다른 encode값이 나오도록 서비스를 제공한다. (PasswordEncoder는 다른 암호화 방식들도 제공한다.)
Salting
단방향 해시 암호화를 진행할 때 본래 데이터에 추가적으로 랜덤한 데이터를 더하는 방식.
원래 데이터에 추가 데이터가 포함되었기 때문에 이전의 해시값과 달라진다.
Key Stretching
단방향 해쉬값을 계산 한 후, 그 해쉬값을 또 다시 해시하고 또 이를 반복하는 방식이다.
최근 일반적인 장비로도 1초에 50억 개 이상의 해시값을 비교할 수 있다. 하지만 키 스트레칭을 적용하면 동일 장비에서 1초에 5번 정도만 비교할 수 있다. GPU(Graphics Processing Unit)를 사용하더라도 수백에서 수천 번 정도만 비교할 수 있다.
이런 방식들을 사용한 Bcrypt 단방향 암호화 방식을 사용하여 복호화가 불가능하도록 기능을 제공해준다.
실습 (spring boot 3.x 버전)
진행 중인 프로젝트에 암호화를 적용시켜 보았다.
gradle (의존성 추가)
// passwordEncoder 관련 의존성 주입
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security'
PasswordEncoder를 사용하기 위해 빈 등록
package com.renew.timenglish.config;
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();
}
}
회원 가입 시 회원가입 폼에 입력한 비밀번호를 암호화(encode 사용)
public ResponseEntity<String> signup(TeacherDto teacherDto) {
teacherDto.setPassword(pwEncoder.encode(teacherDto.getPassword()));
teacherDto.setImgUrl(defaultImg);
Teacher teacher = teacherDto.toEntity();
teacherRepository.save(teacher);
return new ResponseEntity("success", HttpStatus.OK);
}
postman으로 테스트
로그인 시 해당 사용자의 비밀번호가 맞는지 확인하는 검증(matches 사용)
public ResponseEntity<TeacherDto> login(LoginDto loginDto) {
Optional<Teacher> teacherOp = teacherRepository.findByEmail(loginDto.getEmail());
if (teacherOp.isEmpty()) {
return new ResponseEntity<>(new TeacherDto(), HttpStatus.NO_CONTENT);
}
if (!pwEncoder.matches(loginDto.getPassword(), teacherOp.get().getPassword())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
TeacherDto teacherDto = teacherOp.get().toDto();
return new ResponseEntity<>(teacherDto, HttpStatus.OK);
}
postman으로 로그인 테스트
참고 블로그
https://velog.io/@yenicall/%EC%95%94%ED%98%B8%ED%99%94%EC%9D%98-%EC%A2%85%EB%A5%98%EC%99%80-Bcrypt