프로젝트를 진행하며 개인정보를 암호화해야 할일이 생겼다.
개인정보 암호화엔 어떤 알고리즘을 사용해야 하며, 종류는 어떤게 있는지 정리하며 적어본다.
먼저 복호화가 가능한지에 따라 양방향 / 단방향으로 구분되고,
복호화할 때 사용하는 비밀키가 암호화할 때 그대로 사용되면 대칭키, 서로 다른 키를 사용하면 비대칭키가 된다.
암호화 알고리즘 유형별 대표 알고리즘
- 대칭키 알고리즘 (SEED, ARIA, LEA, HIGHT, AES, Blowfish, Camellia 등)
- 단방향(해쉬함수) 알고리즘 (SHA-2, SHA-3 등)
- 공개키 알고리즘 (RSA, EIGamal, ECC 등)
✔️ 어떤 알고리즘을 선택해야할까?
데이터의 성격에 따라 암복호화 알고리즘 유형이 다르며, 유형 내에서도 다양한 암복호화 알고리즘이 존재한다.
패스워드는 일방향(해쉬함수) 암호화, 개인 정보는 블록암호 알고리즘을 선택하기를 권고하고 있다.
암호화 알고리즘을 선택하는 것은 정부 기관 KISA 인터넷진흥원에서 제공하는 표준 안내서를 참고하였다.
데이터 암호화에 대한 가이드는 다른 출처에서도 찾아볼 수 있다. 하지만 KISA의 안내서를 선택한 이유는 KISA가 공신력이 있는 기관이고, 우리나라 법률을 반영한 안내서를 제공하고 있을 것이라는 생각이어서다. 참고로 개인정보에 대한 법률 조항은 기관마다 조금씩 다른데, 이 때는 모두 충족하는 것을 원칙으로 한다.
KISA에서 개정한 개인정보 암호화 안내서를 참고하여 SEED 알고리즘을 선택했다.
✔️ SEED를 선택한 이유
- 블록암호 대칭키 알고리즘이다.
- KISA에서 권고한 안전한 알고리즘
- 국가정보원 검증 대상 알고리즘
✔️ KISA SEED_CBC 알고리즘 적용 예제
SEED 알고리즘 내에서도 운영모드에 따라 알고리즘이 조금씩 달라진다. 일반적으로 많이 사용되는 CBC 운영모드를 선택하였다. KISA 인터넷진흥원 자료실에서 SEED 암복호화 알고리즘을 언어별로 다운로드할 수 있다.
다운로드 링크 : https://seed.kisa.or.kr/kisa/Board/17/detailView.do
압축 해제 후 아래 디렉토리에 위치한 java 파일을 프로젝트로 옮겨서 사용했다.
1. Seed 모듈 작성
public class Seed {
private static String privateKey="testhellocrypto2023";
private static String vectorKey="1234567890123456"; // 16bit 로구성
private static final byte[] pbszUserKey = privateKey.getBytes();
private static final byte[] pbszIV = vectorKey.getBytes();
public static byte[] encrypt(String rawMessage) {
byte[] message = rawMessage.getBytes(StandardCharsets.UTF_8);
byte[] encryptedMessage = KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, message, 0, message.length);
return encryptedMessage;
}
public static String decrypt(byte[] encryptedMessage) {
byte[] decryptedMessage = KISA_SEED_CBC.SEED_CBC_Decrypt(pbszUserKey, pbszIV, encryptedMessage, 0, encryptedMessage.length);
return new String(decryptedMessage, StandardCharsets.UTF_8);
}
}
- pbszUserKey는 암복호화하는 데 사용하는 비밀키이다. 현재 테스트를 위해서 같은 코드라인 내에 두었지만 따로 빼서 관리하는게 좋다.
- pbszIV는 초기화 벡터키로, 비밀키와 마찬가지로 16비트로 구성해야한다.
2. 테스트
public static void main(String[] args) {
String text = "헬로헬로양";
System.out.println("원문 :"+ text);
System.out.println("암호화 :"+ encrypt(text));
System.out.println("복호화 :"+ decrypt(encrypt(text)));
}
3. DB와 함께 적용
그럼 내가 직접 API 를 구현하며 DB 에 개인정보 저장을 할 경우, 어떻게 암/복호화를 할 수 있을까?
나는 JPA AttributeConverter를 사용하기로 결정했다.
✔️ JPA AttributeConverter
JPA에서 제공해주는 속성 변환기로, 아래 그림과 같이 Java Entity와 DB 사이에서 동작한다. 암복호화 기능 외에도 대소문자 처리나 포맷 변경을 위해서 사용하기도 한다.
장점
- 암복호화 대상이 Entity 클래스에 명확하게 드러나기 때문에 유지보수가 쉬울 것 같다.
- JPA에서 지원하기 때문에 코드가 간결하다.
- service나 view 단에서는 암복호화에 대해 신경을 쓰지 않아도 된다.
단점
- API 송신 시 데이터를 다시 복호화를 해야한다.
1. Convert 클래스 생성
@Converter
@Slf4j
public class CryptoConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
if (attribute == null) return null;
byte[] encByte = Seed.encrypt(attribute);
return ConvertUtils.encByteToStr(encByte);
}
@Override
public String convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
return Seed.decrypt(ConvertUtils.strToEncByte(dbData));
}
}
2. 암호화된 바이트 <-> 문자열 서로 변환하는 함수를 필수로 만들어줘야한다.
/**
* 암호화바이트 -> 문자열
* @param cipher
* @return
*/
public static String encByteToStr(byte[] cipher) {
String cipherString = Base64.getEncoder().encodeToString(cipher);
return cipherString;
}
/**
* 암호화문자열 -> 바이트
* @param cipherString
* @return
*/
public static byte[] strToEncByte(String cipherString){
byte[] cipherByte = Base64.getDecoder().decode(cipherString);
return cipherByte;
}
.toString() 이런거 말고 바이너리데이터를 표현하기 위해 Base64를 이용했다.
✔ Base64를 사용하는 이유
Base64를 사용하는 가장 큰 이유는 Binary 데이터를 텍스트 기반 규격으로 다룰 수 있기 때문이다. JSON과 같은 문자열 기반 데이터 안에 이미지 파일등을 Web에서 필요로 할때 Base64로 인코딩하면 UTF-8과 호환 가능한 문자열을 얻을 수 있다. 끝에 '='과 같은 패딩 기호가 있다면 이는 구분자로써 사용되므로 대부분 Base64로 생각할 수 있다.
기존 ASCII 코드는 시스템간 데이터를 전달하기에 안전하지 않다. 모든 Binary 데이터가 ASCII 코드에 포함되지 않으므로 제대로 읽지 못한다. 반면 Base64는 ASCII 중 제어문자와 일부 특수문자를 제외한 53개의 안전한 출력 문자만 이용하므로 데이터 전달에 더 적합하다.
3.Entity 에 적용
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "test")
public class TestEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int seq;
@Convert(converter = CryptoConverter.class)
private String mb_id;
private String mb_pw;
private String address;
private String mb_tell;
}
필드에 @Convert 어노테이션을 달면 JPA가 해당 컬럼에 데이터를 넣고 뺄 때, 내가 작성한 AttributeConverter가 적용된다
4. 등록
TestEntity entity = TestEntity.builder().mb_id(id)
.mb_pw(passwordEncoder.encode(pw))
.mb_tell("01022223333")
.address("으하하").build();
참고로 패스워드는 스프링 시큐리티에서 지원해주는 PasswordEncoder를 사용했다
PasswordEncoder는 BCrpyt 라는 단방향 알고리즘을 사용한다.
BCrypt는 같은 비밀번호를 암호화하더라도 해시 값은 매번 다른 값이 도출된다.
passwordEncoder.matches(검증할 비밀번호, 기존 암호화된 비밀번호)를 써서 검증할 수 있다.
확인
참고
https://bestinu.tistory.com/60
'web > SpringBoot' 카테고리의 다른 글
[WebClient] Block vs Subscribe vs Tuple (1) | 2023.12.04 |
---|---|
[SpringBoot] ConvertUtils 클래스 (0) | 2023.12.01 |
[MSA] Backing service - MOM (1) | 2023.11.28 |
[MSA] 아키텍처 구성 (1) | 2023.11.28 |
[MSA] Spring Cloud를 사용해보자(4)-로드밸런서 (1) | 2023.11.28 |