Back-End/Spring
[Springboot, Mysql, Mybatis] PasswordEncoder구현 - 검색 효율 높인 게시판 구현
hampangee
2024. 10. 23. 11:01
SpringSecurity를 한 번 해봤다는 이유로 프로젝트에서 로그인 회원가입구현만 4번째...
매번 그냥 라이브러리에 있는 BCryptPasswordEncoder클래스를 가져와서 썼지만 이번에는 공부도 할겸해서 직접 PasswordEncoder를 구현했다.
코드 - PasswordEncoder Class
package com.hampang.boardserver.utils;
import java.security.MessageDigest;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class SHA256Util {
public static final String ENCRYPTION_KEY = "SHA-256";
public static String encryptSHA256(String str) {
String SHA = null;
MessageDigest sh;
try {
// key값으로 인스턴스 만들기. MessageDigest : 메세지 해시값을 계산
sh = MessageDigest.getInstance(ENCRYPTION_KEY);
// str.getBytes() 문자열을 바이트 배열로 변환
// 해시 알고리즘은 바이트 단위로 데이터를 처리하므로, 문자열을 먼저 바이트로 변환
sh.update(str.getBytes());
// digest(): 바이트 배열로 변환된 데이터를 해시 값으로 변환
byte[] byteData = sh.digest();
// 바이트 배열을 헥사값으로 변환
StringBuffer sb = new StringBuffer();
for (byte byteDatum : byteData) {
sb.append(Integer.toString((byteDatum & 0xff) + 0x100, 16).substring(1));
}
SHA = sb.toString();
} catch (Exception e) {
log.error("Encrypt Error - NoSuchAlgorithmException", e);
return null;
}
return SHA;
}
}
for문 부분이 암호화 부분인데 자세하게 살펴보면
- byteDatum & 0xff
- byte값을 0에서 255 사이의 양수로 변환
- byte값은 8비트로 표현되고 이는 -128 ~ 127까지의 값을 갖는데 & 0xff는 8비트 값을 유지하면서 음수값을 양수로 변환한다.
- 결과적으로 우리는 16진수 문자열로 받는게 목표이기 때문에 양수로 변환하는 것.
- (byteDatum & 0xff) + 0x100
- 한 자리 16진수 값 처리 : 0x100을 더하여 항상 두 자리 16진수로 표현될 수 있게한다.
- &연산 이후 값은 0~255 사이의 값을 갖게 되는데, 16진수로 변환했을때 값이 한자리일 경우가 있다.
- ex) 0x0A(10) -> "a"
- 우리는 암호를 항상 16자리 문자열을 만들기 위해 앞에 0을 붙여 "0a"가 나오도록 해야된다.
- 0x100을 더하면 266이 (256 + 10)이 연산되어 266이 되는데 이를 16진수로 변환하면 "10a"
- .substring(1)
- 마지막 두 자리를 잘라와서 항상 두 자리의 16진 수 값 얻기
- 바이트값이 두 자리일 경우에도 0x100을 더하기 때문에 최소 3자리가 나오게 된다. 이를 substring하여 최소 두자리 획득
- 일관된 두자리 해시값을 얻기 위함.
위 코드를 chatGPT를 돌려서 리팩토링 요청을 해봤는데
개선점:
- StringBuffer 대신 StringBuilder 사용:
- StringBuffer는 쓰레드 안전성을 보장하지만, 암호화 작업은 보통 단일 쓰레드에서 실행되므로 굳이 쓰레드 안전성을 고려할 필요가 없습니다.
- 대신 더 가벼운 **StringBuilder**를 사용하는 것이 성능 면에서 유리합니다.
- 예외 처리:
- NoSuchAlgorithmException 외에도 UnsupportedEncodingException 등이 발생할 수 있으므로, 적절하게 예외를 나눠서 처리하거나, 보다 구체적인 예외 처리가 필요할 수 있습니다.
- 암호화 메소드 이름:
- encryptSHA256이라는 메소드 이름은 기술적으로 정확하진 않습니다. SHA-256은 **암호화(encryption)**가 아니라 **해싱(hash)**입니다. 해싱은 원래 데이터로 다시 되돌릴 수 없는 일방향성 변환이기 때문에 hashSHA256 또는 generateSHA256Hash 같은 이름이 더 적합합니다.
- 바이트 배열을 16진수 문자열로 변환하는 더 간결한 방법:
- Integer.toString(...).substring(1) 방식도 동작하지만, String.format("%02x", byteDatum)을 사용하면 더 간단하고 읽기 쉬운 방식으로 2자리 16진수 문자열을 얻을 수 있습니다.
요렇게 답이 왔다...
확실히 리팩토링은 잘해주는 것 같다. 더 깔끔해보이는 느낌,,
리팩토링 된 코드
package com.hampang.boardserver.utils;
import java.security.MessageDigest;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class SHA256Util {
public static final String ENCRYPTION_KEY = "SHA-256";
public static String hashSHA256(String str) {
String result = null;
try {
// SHA-256 인스턴스 생성
MessageDigest sh = MessageDigest.getInstance(ENCRYPTION_KEY);
// 문자열을 바이트 배열로 변환하고 해시 계산
byte[] byteData = sh.digest(str.getBytes());
// 바이트 배열을 16진수 문자열로 변환
StringBuilder sb = new StringBuilder();
for (byte byteDatum : byteData) {
sb.append(String.format("%02x", byteDatum)); // 2자리 16진수로 포맷팅
}
result = sb.toString();
} catch (Exception e) {
log.error("Hashing Error - NoSuchAlgorithmException", e);
return null;
}
return result;
}
}
근데 아무리 봐도 비트연산은 이해가 안된다....
플젝끝나고 비트연산 공부해야지