🗂️ Etc

[Java] 원본 값의 길이와 암호화 된 값의 길이가 같은 암호화 방식

loose 2023. 3. 8. 00:47
반응형

요구조건

사내에서 보안 장비에 탐지되는 개인정보를 가리는 목적으로 암호화를 하는 작업이 필요했다.
걸림돌이 있다면 기존에 사용하던 값의 Format이 10자리로 이루어진 값이었다.
그렇기에 Format 형식과 일치 시키려면 암호화 된 값의 길이도 10자리여야 한다는 특징이 존재했다.
결론만 말하자면 XOR 암호화 방식을 이용하면 된다.
우여곡절 끝에 알아냈기 때문에 기록해보려고 한다.
 

AES

 
AES는 위 요구조건의 암호화가 불가능하다.
AES 암호화 방식은 대칭키 암호화 방식(암복호화에 같은 키 사용)이다.
AES128, AES192, AES256에서 뒤의 숫자는 암호화 된 길이(비트)를 뜻한다. 
128비트(16 바이트), 256비트(32 바이트)를 뜻한다.
이 말은 암호화 된 결과가 무조건 16 바이트라는 뜻이다.
 
즉, 내가 "1234567890"을 암호화 한다 치더라도 "fjeo48374jrkskej" 처럼 무조건 16자리로 암호화가 된다.
물론 이 16자리를 압축하는 것도 불가능하다.
암호화 된 값을 압축한다는 것이 불가능하다는 뜻이 아니다. 압축하면 데이터가 손실 될 가능성이 있다.
기본적으로 암호화 된 값을 압축한다는 것은 압축 비율이 낮을 때 손실이 미미하지만 압축 비율이 높아질 수록 데이터가 손실될 우려가 있다.
10자리로 이루어진 값을 압축한다면 손실율이 얼마나 크겠는가?
 

RSA

RSA 역시 위 요구조건의 암호화가 불가능하다.
RSA 암호화 방식은 비대칭키 암호화 방식(암복호화에 다른 키 사용 / 공개키와 비밀키)이다.
 
일단 RSA는 일반적으로 암호화에 사용된 키 값의 길이와 암호화 된 값의 길이가 동일하다. 하지만 동일하더라도 이 기능을 구현하는 것은 불가능에 가깝다.
왜냐면 기본적으로 RSA는 키 값으로 1024비트(128 바이트), 2048비트(256 바이트)의 길이를 가지고 있고 java.security 패키지에서 제공하는 KeyPairGenerator에서도 최소가 512비트이기 때문에 내가 원하는 10자리의 숫자가 다시 10자리로 암호화되는 것은 불가능했다.
그렇다고 RSA Key 값을 강제로 줄여서 구현한다면 RSA를 직접 다 구현해야되고 Key 값의 길이를 줄인다는 것은 보안에 취약하기 때문에 안쓰느니만 못하다.
 

Base64

Base64 방식은 그 자체로 불가능하다.
왜냐면 Base64는 원본 데이터 길이보다 평균 33%가 증가된 길이로 늘어나기 때문이다.
더불어 Base64는 암호화 방식이 아니라 데이터의 무결성을 검증하기 위한 인코딩 방식이기 때문에 적절하지 않다.
 

XOR

대칭키 암호화 방식을 사용하며 암복호화에 같은 키를 사용한다.
이 방식을 이용한다면 위 요구조건에 맞는 암호화가 가능하다.

더보기

XOR과 AES는 모두 대칭키 암호화 방식이고 Key가 유추가 가능하다는 특징이 있지만 XOR은 단순 비트 연산으로 이루어져 있어서 패턴이 존재하기 때문에 Key 값이 유추가 가능하다. 
AES 암호화 방식에서는 복잡한 라운드 함수를 수행하기 때문에 암호화된 데이터를 분석하여 원본 데이터를 추적하기 어렵다. 그렇기 때문에 일반적인 암호화 방식에서는 AES 암호화 방식을 쓰는 것이 좋다.

XOR 암호화 방식은 단순히 두 비트열을 서로 비교하여 같으면 0을, 다르면 1을 반환하는 방식이다.
코드는 아래와 같다.

public static void main(String[] args) {
    String input = "123456";
    String key = "randomkey";
    String encrypted = encrypt(input, key);
    System.out.println("Encrypted: " + encrypted);
    String decrypted = decrypt(encrypted, key);
    System.out.println("Decrypted: " + decrypted);
}

public static String encrypt(String input, String key) {
    byte[] bytes = input.getBytes();
    byte[] keyBytes = key.getBytes();
    byte[] encryptedBytes = new byte[bytes.length];
    for (int i = 0; i < bytes.length; i++) {
        encryptedBytes[i] = (byte) (bytes[i] ^ keyBytes[i % keyBytes.length]);
    }
    return new String(encryptedBytes);
}

public static String decrypt(String encrypted, String key) {
    byte[] encryptedBytes = encrypted.getBytes();
    byte[] keyBytes = key.getBytes();
    byte[] decryptedBytes = new byte[encryptedBytes.length];
    for (int i = 0; i < encryptedBytes.length; i++) {
        decryptedBytes[i] = (byte) (encryptedBytes[i] ^ keyBytes[i % keyBytes.length]);
    }
    return new String(decryptedBytes);
}

XOR 암호화 방식은 위처럼 개발자가 지정한 key를 넣어서 사용할 수 있다.
원본데이터가 "123456" 이면 암호화된 값 또한 6글자로 출력이 된다.
 
다만, XOR 암호화 방식은 Key 값이 항상 고정되어있다는 특징으로 인해 보안에 위험이 존재한다.
그렇기에 Key 값이 랜덤인 형태인 One Time Pad 방식도 존재하지만 해당 방식은 실제로 구현하기에는 까다로운 점이 있다. 왜냐하면 암호화를 랜덤한 Key로 암호화 시에 복호화 시 기존에 암호화 했던 Key를 그대로 가져와야 하기 때문이다. 
 
Session을 이용한 웹 애플리케이션을 구축한다면 One Time Pad를 쓰는 것이 가능하다.
로그인한 사용자를 Session을 통해 관리한다면 Session에 랜덤 Key 값을 넣어두고 세션이 소멸하기 전까지 해당 Session에 등록된 랜덤 Key 값을 이용해서 XOR 암호화 방식을 쓴다면 사용자마다 다른 Key 값을 사용할 수 있기 때문에 One Time Pad로 구현이 가능하다.
 
반대로 REST API에서는 서버가 사용자 정보를 들고있지 않아 무상태성을 유지하기 때문에 One Time Pad 구현이 불가능하다.

728x90