사실 비밀번호 암호화는 인증과 깊이 관련이 있다.
아래와 같은 사진으로 회원가입, 로그인 등 인증처리가 필요한 view가 있는데 이런 요청에서 비밀번호는 필수이고 db에 들어가기 때문에 사용자의 신용정보로서 암호화가 필수이다.
평문을 알아볼 수 없게 바꿔 놓음
=> 단방향, 양방향의 개념이 있다.
단방향
- hash 방식, 단뱡향은 복호화가 안된다는 뜻!양방향
- 복호화 가능 (대칭키, 공개키 방식)✅ 패스워드 암호화 요구조건
즉, hash && salt를 쓰는 이유를 살펴보면 된다!
: 똑같은 문자열의 패스워드라도 hash 값이 다르게 할 수 있기 때문이다!
현재는 hash를 사용하는 Bcrypt 암호화 알고리즘을 많이 사용하는 추세이다.
암호화, 복호화에 사용하는 키가 동일! -> private 키
장점: 속도가 빠르다.
단점: 키가 탈취될 염려가 있어 인터넷이라는 공간안에서 쓰기 힘들다.
대칭키 알고리즘 : DES, AES 등
암호화, 복호화에 사용하는 키가 2개! -> private(개인) 키 & public(공개) 키
public키: 모든 사람이 접근 가능 -> 암호화
private키: 각 사용자만이 가지고 있는 키 -> 복호화
예를 들어, A가 B에게 데이터를 보낸다고 할 때, A는 B의 공개키로 암호화한 데이터를 보내고 B는 본인의 개인키로 해당 암호화된 데이터를 복호화해서 보기 때문에 암호화된 데이터는 B의 공개키에 대응되는 개인키를 갖고 있는 B만이 볼 수 있게 되는 것이다.
1) B 공개키/개인키 쌍 생성
2) 공개키 공개(등록), 개인키는 본인이 소유
3) A가 B의 공개키를 받아옴
4) A가 B의 공개키를 사용해 데이터를 암호화
5) 암호화된 데이터를 B에게 전송
6) B는 암호화된 데이터를 B의 개인키로 복호화 (개인키는 B만 가지고 있기 때문에 B만 볼 수 있음)
중간 공격자가 B의 공개키를 얻는다고 해도 B의 개인키로만 복호화가 가능하기 때문에 기밀성을 제공하며 개인키를 가지고있는 수신자만이 암호화된 데이터를 복호화할 수 있으므로 일종의 인증기능도 제공한다
좋은 영상 남긴다.
https://www.youtube.com/watch?v=uBwj-BjtNnc
전자서명의 좋은 예시가 AWS EC2의 .pem
파일 인 것 같다
✅ PEM 파일은 개인키(Private Key) 역할을 수행
✅ SSH 접속 시, 서버는 공개키(Public Key) 기반으로 클라이언트를 인증
✅ 클라이언트는 PEM 파일(개인키)를 이용해 서버의 인증 요청에 대응
✅ 즉, 서버가 보낸 인증 요청을 개인키로 처리하여 사용자가 권한이 있는지 확인하는 과정이 포함됨
1️⃣ AWS EC2에서 SSH 공개키(Public Key)를 서버에 등록
2️⃣ 사용자가 SSH 접속 시 개인키(Private Key, .pem
파일)를 이용하여 인증
3️⃣ 서버는 사용자의 공개키와 비교하여 접근을 허용
.pem
파일 사용)ssh -i my-key.pem ec2-user@your-server-ip
✅ -i my-key.pem
→ 개인키를 사용하여 접속
✅ ec2-user@your-server-ip
→ 서버의 사용자 및 IP 지정
PEM 파일(개인키)은 데이터를 복호화하는 것이 아니라, SSH 인증 과정에서 암호화된 메시지를 서명하여 신원을 증명하는 역할을 합니다!
즉, 암호 해독을 위한 키가 아니라 서버 접근 권한을 인증하는 데 사용되는 키이다!
설정
// crypto
implementation group: 'org.springframework.security', name: 'spring-security-crypto', version: '5.7.3'
implementation group: 'commons-logging', name: 'commons-logging', version: '1.2'
Utils
public class EncodePasswordUtils {
/* 순환참조 안될려면 이렇게 */
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Service
@Override
@Transactional
public void register(BoardVO board) {
validateEntity(board);
// 비밀번호 암호화 처리
String encodedPassword = passwordEncoder().encode(board.getPassword());
board.setPassword(encodedPassword);
long result = boardMapper.registerWithSelectKey(board);
log.info("register result: {}", result);
if (board.getAttachList() != null && board.getAttachList().size() != 0) {
board.getAttachList().forEach(attach -> {
attach.setBoardId(board.getId());
attachMapper.insert(attach);
});
boardMapper.registerFileYN(board.getId());
}
}
설정
// BCrypt
implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.3m'
1) 인터페이스 주입 방식
public interface Encryptor {
String encrypt(String origin);
boolean isMatch(String origin, String hashed);
}
public class BCryptEncryptor implements Encryptor {
@Override
public String encrypt(String origin) {
return BCrypt.hashpw(origin, BCrypt.gensalt());
}
@Override
public boolean isMatch(String origin, String hashed) {
try {
return BCrypt.checkpw(origin, hashed);
} catch (Exception e) { // 여러 예외가 있다.
return false;
}
}
}
2) static 메서드 주입방식 : 내가 자주 사용하는 방식
public class Encryptor {
public static String encrypt(String origin) {
return BCrypt.hashpw(origin, BCrypt.gensalt());
}
public static boolean isMatch(String origin, String hashed) {
try {
return BCrypt.checkpw(origin, hashed);
} catch (Exception e) { // 여러 예외가 있다.
return false;
}
}
}
User
...
public static User of(SignUpRequest signUpRequest) {
return User.builder()
.email(signUpRequest.getEmail())
.name(signUpRequest.getName())
.nickname(signUpRequest.getNickname())
// User 엔티티에 패스워드 암호화하면 테스트시 편해짐!
.password(encrypt(signUpRequest.getPassword()))
.build();
}
Service
@Override
@Transactional(readOnly = true)
public LoginDto getByEmailAndPassword(String email, String password) {
// 파라미터 password가 hash값이다.
User user = userRepository.findByEmail(email)
.map(u -> Encryptor.isMatch(u.getPassword(), password) ? u : null)
.orElseThrow(() -> new WSApiException(ErrorCode.NOT_FOUND_USER));
log.info("getByEmailAndPassword user : {}", user);
return LoginDto.mapToDto(user);
}