개발하는 햄팡이

[Springboot, Mysql, Mybatis] PasswordEncoder구현 - 검색 효율 높인 게시판 구현 본문

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문 부분이 암호화 부분인데 자세하게 살펴보면

  1. byteDatum & 0xff
    • byte값을 0에서 255 사이의 양수로 변환
    • byte값은 8비트로 표현되고 이는 -128 ~ 127까지의 값을 갖는데 & 0xff는 8비트 값을 유지하면서 음수값을 양수로 변환한다.
    • 결과적으로 우리는 16진수 문자열로 받는게 목표이기 때문에 양수로 변환하는 것.
  2. (byteDatum & 0xff) + 0x100
    • 한 자리 16진수 값 처리 : 0x100을 더하여 항상 두 자리 16진수로 표현될 수 있게한다.
    •  &연산 이후 값은 0~255 사이의 값을 갖게 되는데, 16진수로 변환했을때 값이 한자리일 경우가 있다.
      • ex) 0x0A(10) -> "a"
    • 우리는 암호를 항상 16자리 문자열을 만들기 위해 앞에 0을 붙여 "0a"가 나오도록 해야된다.
    • 0x100을 더하면 266(256 + 10)이 연산되어 266이 되는데 이를 16진수로 변환하면 "10a"
  3. .substring(1)
    • 마지막 두 자리를 잘라와서 항상 두 자리의 16진 수 값 얻기
    • 바이트값이 두 자리일 경우에도 0x100을 더하기 때문에 최소 3자리가 나오게 된다. 이를 substring하여 최소 두자리 획득
    • 일관된 두자리 해시값을 얻기 위함.

 


 

위 코드를  chatGPT를 돌려서 리팩토링 요청을 해봤는데

개선점:

  1. StringBuffer 대신 StringBuilder 사용:
    • StringBuffer는 쓰레드 안전성을 보장하지만, 암호화 작업은 보통 단일 쓰레드에서 실행되므로 굳이 쓰레드 안전성을 고려할 필요가 없습니다.
    • 대신 더 가벼운 **StringBuilder**를 사용하는 것이 성능 면에서 유리합니다.
  2. 예외 처리:
    • NoSuchAlgorithmException 외에도 UnsupportedEncodingException 등이 발생할 수 있으므로, 적절하게 예외를 나눠서 처리하거나, 보다 구체적인 예외 처리가 필요할 수 있습니다.
  3. 암호화 메소드 이름:
    • encryptSHA256이라는 메소드 이름은 기술적으로 정확하진 않습니다. SHA-256은 **암호화(encryption)**가 아니라 **해싱(hash)**입니다. 해싱은 원래 데이터로 다시 되돌릴 수 없는 일방향성 변환이기 때문에 hashSHA256 또는 generateSHA256Hash 같은 이름이 더 적합합니다.
  4. 바이트 배열을 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;
    }
}

 

 

근데 아무리 봐도 비트연산은 이해가 안된다....

플젝끝나고 비트연산 공부해야지