사용자 CI 나 주민번호 및 핸드폰 번호와 같은 개인정보는 DB에 저장할 때 뿐만아니라 조회되는 데이터도 암호화되어 보여져야 한다. 이를 위해 JAVA의 AES CBC 와 KISA_SEED_CBC를 활용하여 DB에 들어갈 데이터는 AES로 암호화하고 외부로 보여지는 데이터는 KISA_SEED_CBC로 암호화하여 보여지도록 해보자
외부 -> DB
BASE64(SEED128 CBC(평문)) 으로 암호화된 데이터를 SEED128 CBC로 복호화하고
복호화된 데이터를 AES로 암호화하여 DB에 저장
DB -> 외부
AES로 암호화된 DB의 데이터를 AES/CBC/PKCS5Padding 로 복호화하고
복호화된 데이터를 SEED128 CBC로 암호화여 외부에 노출
활용 스펙 및 기술 :
Spring boot
JAVA 11
AES
Base64
KISA_SEED_CBC
KISA SEED CBC - Encrypt
KISA SEED 암호화 알고리즘 적용을 위해 https://seed.kisa.or.kr/kisa/Board/17/detailView.do 에서 사용하고자 하는 언어 및 운영모드에 해당하는 파일을 다운받자
나는 KISA_SEED_CBC.java 파일을 프로젝트에 import 해주었다.
public String encrypt(String masterKey, String msg) throws Exception {
try {
byte[] sha256 = getSHA256(masterKey);
byte[] key = Arrays.copyOfRange(sha256, 0, 16);
byte[] iv = Arrays.copyOfRange(sha256, 16, 32);
byte[] data = KISA_SEED_CBC.SEED_CBC_Encrypt(key, iv, msg.getBytes(), 0, msg.getBytes().length);
return Base64.getEncoder().encodeToString(data);
} catch (Exception e) {
log.error("SEED_ENCRYPT_ERROR");
throw new Exception(e);
}
}
private byte[] getSHA256(String msg) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
return md.digest(msg.getBytes());
}
임의의 길이 메시지를 256 비트(bits)의 축약된 메시지로 만들어내는 sha-256 해시 알고리즘을 활용하여 master key를 encoding 해준다.
KISA SEED CBC - Decrypt
public String decrypt(String masterKey, String base64Str) throws Exception {
try {
byte[] sha256 = getSHA256(masterKey);
byte[] key = Arrays.copyOfRange(sha256, 0, 16);
byte[] iv = Arrays.copyOfRange(sha256, 16, 32);
byte[] decode = Base64.getDecoder().decode(base64Str);
byte[] dec = KISA_SEED_CBC.SEED_CBC_Decrypt(key, iv, decode, 0, decode.length);
return new String(dec);
} catch (Exception e) {
log.error("SEED_DECRYPT_ERROR");
throw new Exception(e);
}
}
JAVA AES CBC - Encrypt
final private String cryptoDbIv = "나의 IV";
final private String cryptoDbKey = "나의 key";
암복호화를 위한 key와 초기화벡터(IV) 를 상수로 설정한다
public String encrypt(String msg) throws Exception {
try {
return encryptAes(msg, cryptoDbKey, cryptoDbIv);
} catch (Exception e) {
log.error("CRYPTO_ENCRYPT_ERROR");
throw new Exception(e);
}
}
private String encryptAes(String plainText, String inputKey, String inputIV) throws Exception {
byte[] IV = DatatypeConverter.parseHexBinary(inputIV);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(inputKey.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV));
byte[] result = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(result);
}
암호화된 데이터는 hex로 encoding하여 string으로 리턴된다.
JAVA AES CBC - Decrypt
public String decrypt(String msg) throws Exception {
try {
return decryptAes(msg, cryptoDbKey, cryptoDbIv);
} catch (Exception e) {
log.error("CRYPTO_DECRYPT_ERROR");
throw new Exception(e);
}
}
private String decryptAes(String text, String inputKey, String inputIV) throws Exception {
byte[] cipherText = DatatypeConverter.parseHexBinary(text);
byte[] IV = DatatypeConverter.parseHexBinary(inputIV);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(inputKey.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV));
return new String(cipher.doFinal(cipherText), StandardCharsets.UTF_8);
}
적용
seedUtil의 encrypt 함수를 호출하여 평문을 SEED128 CBC로 암호화 하자
public String toSEEDCBCEncrypt(String plainText) throws Exception{
return seedUtil.encrypt(masterkey, plainText);
}
위에서 리턴된 SEED128 CBC 암호화 값은 다음과 같이 AES CBC암호화 값으로 디비에 insert 될 것이다
public String toDb(String input) throws Exception {
String plainText = seedUtil.decrypt(masterkey, input);
log.info("toDb plain text : {}", plainText);
return cryptoDbUtil.encrypt(plainText);
}
디비에 들어가 있는 암호화 값이 외부에서 조회될 때는 다음과 같이 SEED128 CBC로 암호화 되어 보여진다
이 암호화 값은 encrypt-text api 를 호출해서 받았던 결과값과 동일하다
public String toExternal(String input) throws Exception {
String plainText = cryptoDbUtil.decrypt(input);
log.info("toExternal plain text : {}", plainText);
return seedUtil.encrypt(masterkey, plainText);
}
복호화 될 때 plain text 또한 동일한 부분 log로 확인 가능하다
끄읏 -!
'Spring' 카테고리의 다른 글
[SPRING] 다중 Database 및 분산 Transaction 설정 예제 ChainedTransactionManager vs JtaTransactionManager (0) | 2024.01.22 |
---|---|
[JPA] @CreatedDate @LastModifiedDate 오류 해결 (0) | 2024.01.13 |
[SPRING] QueryDSL 복잡한 정렬 custom order by 설정 OrderSpecifier CaseBuilder (0) | 2024.01.05 |
[SPRING] RequestMapping URL pathVariable에 null 값 허용하는 방법 (0) | 2023.11.22 |
[SPRING] postman 으로 API 호출 시 401 Unauthorized 오류 해결 (0) | 2023.08.01 |