반응형

SEED 알고리즘으로 데이터를 암복호화하는 기능까지 구현했다. 이제는 암호화된 데이터를 String 형태로 DB에 저장하고, DB에 암호화된 String 데이터를 복호화하여 보여주면 된다.

이를 위해 두 가지를 진행하면 된다.

1. 암호화된 데이터를 DB에 저장할 수 있는 형태로 변환 (base64나 hex로 인코딩하여 DB에 저장)

2. 암호화된 데이터의 타입과 길이로 테이블의 컬럼 변경 (타입별로 암호화된 데이터 길이 테스트)


이전 코드의 문제점

이전에 구현했던 SEED 클래스와 테스트 코드를 다시 살펴보고 문제점을 확인해보자.

public class Seed {
    private static final byte[] pbszUserKey = "testCrypt2020!@#".getBytes();
    private static final byte[] pbszIV = "1234567890123456".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);
    }
}
@Slf4j
public class SeedTest {

    private final byte[] pbszUserKey = "testCrypt2020!@#".getBytes();
    private final byte[] pbszIV = "1234567890123456".getBytes();

    @Test
    public void 암복호화_테스트() {
        // given
        String rawMessage = "테스트 데이터";
        log.info("원본 데이터 =>" + rawMessage);

        // when
        byte[] encryptedMessage = KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, rawMessage.getBytes(), 0, rawMessage.getBytes().length);
        log.info("암호화된 데이터1 =>" + new String(encryptedMessage));
        log.info("암호화된 데이터2 =>" + encryptedMessage.toString());

        byte[] decryptedMessage = KISA_SEED_CBC.SEED_CBC_Decrypt(pbszUserKey, pbszIV, encryptedMessage, 0, encryptedMessage.length);
        log.info("복호화된 데이터 =>" + new String(decryptedMessage));

        // then
        assertThat(rawMessage).isEqualTo(new String(decryptedMessage));
        assertThat(rawMessage).isNotEqualTo(encryptedMessage);
    }
}

테스트 코드를 실행해보면 암호화된 데이터가 묘하게 나오는 것을 볼 수 있다.

원본 데이터 =>테스트 데이터
암호화된 데이터1 =>Os�G�P,ga�Q/Og�~�'�	��ݽ�n��
암호화된 데이터2 =>[B@7a379e73
복호화된 데이터 =>테스트 데이터

암호화된 데이터1 : 한글이 깨진 것 처럼 출력되었다. binary data를 무작정 String으로 형변환을 하려고 했기 때문이다.

암호화된 데이터2 : 예상했던 것보다 짧은 길이의 암호화 값이 리턴되었다. 암호화된 데이터를 출력한 게 아니라, 그 데이터를 담고 있는 byte[] 객체의 주소를 출력했다.


1. 암호화된 데이터를 DB에 저장할 수 있는 형태로 변환

데이터베이스에 저장하려면 varchar에 넣을 수 있는 Java 변수 타입인 String으로 넣으면 되겠다고 단순하게 생각하고 접근했었다 (아무 생각 없이 들어간 늪). 그리고 생각보다 형변환이 쉽지 않아 한, 두 시간 "java byte[] to String"라는 검색어로 구글링을 했다. 내가 자바가 낯설어서 형변환에 미숙하기 때문에 암호화된 데이터를 제대로 확인하지 못한다고 생각했기 때문이다. (인코딩 문제라고는 생각 못했다)

참고로 구글링하여 byte[] 배열을 문자열로 변환하는 법에 대해 잘 정리된 포스팅을 발견했기 때문에 공유한다. (하지만 이게 해법은 아니다!)

Java Byte배열을 바이너리 문자열로 변환하기 : https://frontjang.info/entry/Java-Byte-배열과-바이너리문자열-상호-변환하기

그러다 문득 "암호화된 binary 값을 String으로 형변환하여 DB에 넣는 게 옳은가?" 라는 생각이 들었다. 다시 시작된 구글링. "java crypto byte to database", "java binary data to database"라는 검색어로 여러 글들을 읽어보았고, 내 상황에 적절한 글을 찾았다.

올바르게 byte -> string -> byte로 변환하는 방법은 무엇인가 : https://stackoverflow.com/questions/37388680/in-java-how-to-convert-correctly-byte-to-string-to-byte-again

그렇다. binary data는 String으로 형변환하지 않고, base64나 hex로 인코딩하면 데이터베이스에 String 형태로 넣어야 하는구나!

SEED 클래스를 변경해보고 테스트 코드를 통해 결과값을 확인해보자.

@Slf4j
public class Seed {

    private static final Charset UTF_8 = StandardCharsets.UTF_8;
    private static final byte[] pbszUserKey = "testCrypt2020!@#".getBytes();
    private static final byte[] pbszIV = "1234567890123456".getBytes();

    public static String encrypt(String rawMessage) {
        Encoder encoder = Base64.getEncoder();

        byte[] message = rawMessage.getBytes(UTF_8);
        byte[] encryptedMessage = KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, message, 0, message.length);

        return new String(encoder.encode(encryptedMessage), UTF_8);
    }

    public static String decrypt(String encryptedMessage) {
        Decoder decoder = Base64.getDecoder();

        byte[] message = decoder.decode(encryptedMessage);
        byte[] decryptedMessage = KISA_SEED_CBC.SEED_CBC_Decrypt(pbszUserKey, pbszIV, message, 0, message.length);

        return new String(decryptedMessage, UTF_8);
    }
}

Base64 클래스를 통해 인코딩과 디코딩하는 부분이 추가되었다. 아래는 테스트코드와 결과값이다.

@Slf4j
class SeedTest {

    private final byte[] pbszUserKey = "testCrypt2020!@#".getBytes();
    private final byte[] pbszIV = "1234567890123456".getBytes();

    @Test
    public void SEED_Custom_암복호화_테스트() {
        // given
        String rawMessage = "테스트 데이터";
        log.info("원본 데이터 =>" + rawMessage);

        // when
        String encryptedMessage = Seed.encrypt(rawMessage);
        log.info("암호화 데이터 =>" + encryptedMessage);

        String decryptedMessage = Seed.decrypt(encryptedMessage);
        log.info("복호화 데이터 =>" + decryptedMessage);

        // then
        assertThat(rawMessage).isEqualTo(decryptedMessage);
        assertThat(rawMessage).isNotEqualTo(encryptedMessage);
    }
}
원본 데이터 =>테스트 데이터
암호화 데이터 =>T3OAR9RQLGdh5BdRL08PZ9x++CcC+QmrErjdvc9u7uY=
복호화 데이터 =>테스트 데이터

암복호화도 정상적으로 되고, 암호화된 값 또한 정상적으로 표시되었다.

그리고 다시 한 번 말하지만, pbzsUserKey와 pbszIV는 코드상에 노출하면 안된다.


2. 암호화된 데이터의 타입과 길이로 테이블의 컬럼 변경

하하, 암호화된 데이터를 DB에 저장할 수 있도록 인코딩도 했겠다. 이제 테이블 컬럼의 길이를 변경하면 수월하게 마무리 될 것 같다. 암호화된 데이터들은 모두 길이가 똑같겠지? :)

입력한 데이터의 타입, 길이에 따라 암호화된 데이터의 길이가 다르다는 것을 확인했다. KISA에서 제공하는 SEED 암호화 알고리즘 메뉴얼을 열어보았다. 알고리즘의 원리와 사용법, 운영모드에 대한 복합한 설명이 가득했다. 닫았다.

프로젝트 도메인 내에서 입력할 법한 데이터에 따라 암호화 데이터 길이가 어떻게 변하는 지 알아보자. 내가 하는 것은 귀납적 추론일 것이다(하핫)

테스트 코드
@Slf4j
class SeedTest {
    ...

    @Ignore
    public void SEED_암호화길이_String() {
        log.info("!@@ String 일반적인 범위 44, 넉넉하게는 64");
        String message;
        byte[] base64Message;
        Base64.Encoder encoder = Base64.getEncoder();

        message = "20200505";
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, message.getBytes(StandardCharsets.UTF_8), 0, message.getBytes().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = "가나다라마바사아자차카타파하";
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, message.getBytes(StandardCharsets.UTF_8), 0, message.getBytes().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = "2020.06.20";
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, message.getBytes(StandardCharsets.UTF_8), 0, message.getBytes().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = "2020년 06월 20일";
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, message.getBytes(StandardCharsets.UTF_8), 0, message.getBytes().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());
    }

    @Ignore
    public void SEED_암호화길이_Double() {
        log.info("!@@ Double 최대길이 24");
        Double message;
        byte[] base64Message;
        Base64.Encoder encoder = Base64.getEncoder();
        ByteBuffer byteBuffer = ByteBuffer.allocate(Double.BYTES);

        // 도메인 내 최소값
        message = 0.0;
        byteBuffer.clear();
        byteBuffer.putDouble(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        // 도메인 내 일반적인 값
        message = 153.45;
        byteBuffer.clear();
        byteBuffer.putDouble(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        // 최소값
        message = 4.9E-324;
        byteBuffer.clear();
        byteBuffer.putDouble(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        // 최대값
        message = 1.7976931348623157E308;
        byteBuffer.clear();
        byteBuffer.putDouble(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());
    }

    @Ignore
    public void SEED_암호화길이_Integer() {
        log.info("!@@ Integer 최대길이 24");
        Integer message;
        byte[] base64Message;
        Base64.Encoder encoder = Base64.getEncoder();
        ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES);

        message = 0;
        byteBuffer.clear();
        byteBuffer.putInt(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = 1;
        byteBuffer.clear();
        byteBuffer.putInt(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = 155;
        byteBuffer.clear();
        byteBuffer.putInt(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = -2147483648;
        byteBuffer.clear();
        byteBuffer.putInt(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());

        message = 2147483647;
        byteBuffer.clear();
        byteBuffer.putInt(message);
        base64Message = encoder.encode(KISA_SEED_CBC.SEED_CBC_Encrypt(pbszUserKey, pbszIV, byteBuffer.array(), 0, byteBuffer.array().length));
        log.info(message + " 길이 : " + new String(base64Message, StandardCharsets.UTF_8).length());
    }
}

 

 

Integer의 최소값, 최대값을 넣었을 때 암호화된 길이는 24, Double의 경우도 24가 나왔다.

String은 입력 데이터에 따라 길이의 변화가 크기 때문에 각자 상황에 맞게 입력 데이터를 넣어보고 확인하길 바란다.


이번 포스팅에서는 DB에 넣을 수 있도록 암호화된 binary 데이터를 인코딩하고, 암호화된 데이터의 길이를 알아보았다.

다음 포스팅에서는 암복호화 시점과 주체를 정하고, 이에 따라 시스템이 변화에 어떻게 반응할 지 생각해본 내용을 공유하겠다.

반응형