본문 바로가기
CS

AES-256 - Encrypt(암호화) For Java

by lucas_owner 2024. 5. 26.

얼마전, 외부 서비스 업체에서 제공하는 데이터를 DB 에 적재해야 하는 신규 기능 개발이 있었고,

필수 요구사항중에서 'AES256 암호화 필수 (Base64)' 항목이 존재 했고 설계,분석,개발 과정에서 알게된 내용을 정리한다. 

 

해당 글에서는 AES/CBC/PKCS5Padding 방식으로 Java 테스트 코드를 구현했다. 

 

  • 요구사항 및 Flow 
1. 외부 업체 -> 관리서버 API 요청 (필수 파라미터 3개, 전부 암호화) 
2. 요구 데이터 응답(암호화) 
3. 응답 데이터 기반, 데이터 생성 
4. Batch Task 로 데이터 Receive 및 DB 적재

 

해당 요구사항을 만족하기 위해 AES256(Base64) 암호화에 대해 알아보자. 

 

 

1. AES256 이란?

AES (Advanced Encryption Standard) 의 약자이며, 데이터 암호화의 종류중 하나이다.

뒤에 붙은 숫자 256은 256비트 길이 즉, 키의 길이를 말한다.  AES-256 이외에도, AES-128, AES-192 알고리즘이 존재한다.

AES256 의 특징으로는 빠른속도, 높은 안정성이며 대칭키 알고리즘을 사용한다는것이다.

 

대칭키 알고리즘이란? 암호화, 복호화에 사용되는 키가 동일하다는 의미이다.

반대로 비대칭키 알고리즘은 암,복호화에 사용되는 키가 각각 다르다.

 

대칭키 알고리즘을 사용하기 때문에, 요청하는쪽, 받는쪽은 서로 키를 알고있어야 한다. (공유해야한다)

* (128bit = 16byte), (192bit = 24byte), (256bit = 32byte) 이므로, 32byte 의 키를 생성해야 한다. 

 


 

1-1. AES-256 암복호화 흐름

 

* 암호화
Text -> bytes -> (Encrypt) -> Encrypted Byte -> Encrypted Base64 text

* 복호화
Encrypted Base64 text -> Encrypted Byte -> (Decrypt) -> bytes -> Text 

 

기본적인 흐름은 위와 같고 복호화의 경우, 암호화의 역순이다. 

Encrypted Base64 Text 의 경우 암호화된 byte 를 Base64 로 인코딩한 텍스트를 의미하는데, 

Base64 인코딩을 하는 이유는 다음과 같다.

 

1. AES256 암호화 데이터는, 이진 바이너리 형식으로 텍스트 기반 시스템에서는 다루기 어렵다.

또한 전송 및 저장시에 데이터의 손실이나 오류가 발생할 수 있다. 

 

2. Base64 인코딩은 대부분의 시스템에서 지원하기 때문에 안전하게 전송받고 저장 할 수 있다. 

해당 인코딩은 이진 바이너리 데이터를 ASCII 문자열로 변환하여 JSON, Email, XML, URL 등등 텍스트 기반 시스템에서

안정성을 높여주기 때문이다.

 


 

1-2. AES-256 의 필수 요소 

 

  1. Secret Key : 암호화, 복호화에 필요한 비밀키
  2. IV(Initalize Vector) : 초기화 벡터, 대칭키 암호화에서 사용되는 매개변수중 하나, 암호화 작업에서 첫 번째 블록 암호화 전에 사용되는 초기화 벡터.
  3. Cipher Mode(EBC, CBC ...) : 암호화 모드, 블록 암호를 사용하는 방식을 정의한다. 여러 블록을 어떻게 결합할건지 결정한다.
  4. Padding Mode(PKCS5, PKCS7 ...) : 마지막 블록이 블록크기보다 작을경우, 부족한 바이트를 채우기 위한 방법

 

Cipher Mode 란? 

Cipher Mode 는 암호화 모드이고, 블록 암호를 사용하는 방식을 정의한다. 블록 암호화는 데이터를 고정된 길이로 하나의 블록 단위로 쪼개서 처리하게 된다. 일반적으로 암호화 알고리즘에서는 블록 암호화 방식을 사용한다. 

이렇게 쪼개진 각 블록들을 연결하는 방식을 정하는 것을 Cipher Mode 라고 한다.

ECB(Electronic CodeBook), CBC(Cipher Block Chaining), CTR(Counter), CFB(Cipher FeedBack) ...

의 종류가 있으며, 사용하고자 하는 알고리즘에 따라 적절하게 선택하는것이 중요하다. 

일반적으로는 CBC 방식을 많이 사용한다.

 

IV (Initalize Vector) 

CBC Mode 를 사용하게 된다면, IV 값이 필요하게 된다. 그 이유는 CBC 방식은 각각 연결된 블록들은 이전 암호화 블록과 평문을 XOR 연산하여 암호화 블록을 생성하게 되는데, 첫번째 블록은 비교할 대상 블록이 존재하지 않기 때문이다. 그렇기에 첫번째 블록은 IV 값으로 XOR 연산 후 암호화를 진행 하게 된다. 

참고: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

 

이때 AES/CBC 는 각 블록을 128bit 단위로 암호화 하게 되는데, 첫번째 블록을 암호화 하기 위한 
IV 값 또한 128bit = 16byte 크기로 정의해야 한다. 

 

 

2. Key generate 

SecretKey, IV Key 의 경우 32byte 에 맞춰 생성해도 되지만, GCP(Google Cloud Platform) 에서 암호화 키 생성에 대한 문서를 제공 하고 있다. 

https://cloud.google.com/storage/docs/samples/storage-generate-encryption-key?hl=ko#storage_generate_encryption_key-java

 

* SecretKey 

package etc;

import java.security.SecureRandom;
import java.util.Base64;


/**
 * Google Cloud Platform (GCP) - Generate AES-256 Encryption Key Example
 */
public class Generate_256SecretKey {

    public static void main(String[] args) {
        String key = generateEncryptionKey();
        System.out.println("Generated Base64-encoded AES-256 encryption key: " + key);
    }

    public static String generateEncryptionKey() {
        byte[] key = new byte[32];
        new SecureRandom().nextBytes(key);
		return Base64.getEncoder().encodeToString(key);
    }
}

 

 

* IV Key

package etc;

import java.security.SecureRandom;
import java.util.Base64;

public class Generate_IVKey {

    public static void main(String[] args) {
        String ivKey = generateIVKey();
        System.out.println("Generated Base64-encoded IV: " + ivKey);
    }

    public static String generateIVKey() {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);

        return Base64.getEncoder().encodeToString(iv);
    }
}

 

 

3. AES-256 간단 적용 

- 해당 예제 에서는 보편적으로 사용되는 AES/CBC/PKCS5Padding 방식을 사용했다. 

또한 테스트를 위해, 실제 적용 코드와는 다르다.

package etc;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES256_Sample {
    public static void main(String[] args) {
        Generate_256Key genKey = new Generate_256Key();
        Generate_IVKey genIV = new Generate_IVKey();

        String key = genKey.generateEncryptionKey();
        String iv = genIV.generateIVKey();
        String plainText = "Hello, World!";

        String encryptedText = encrypt(key, iv, plainText);
        System.out.println("Encrypted Text: " + encryptedText);

        String decryptedText = decrypt(key, iv, encryptedText);
        System.out.println("Decrypted Text: " + decryptedText);
    }

    // Encryption
    public static String encrypt(String key, String iv, String plainText) {
        // AES-256 Encryption
        try {
            byte[] decodedKey = Base64.getDecoder().decode(key);
            byte[] decodedIV = Base64.getDecoder().decode(iv);

            SecretKeySpec secretKeySpec = new SecretKeySpec(decodedKey, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(decodedIV);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());

            return Base64.getEncoder().encodeToString(encryptedBytes);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // Decryption
    public static String decrypt(String key, String iv, String encryptedText) {
        // AES-256 Decryption
        try {
            byte[] decodedKey = Base64.getDecoder().decode(key);
            byte[] decodedIV = Base64.getDecoder().decode(iv);
            byte[] decodedEncryptedText = Base64.getDecoder().decode(encryptedText);

            SecretKeySpec secretKeySpec = new SecretKeySpec(decodedKey, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(decodedIV);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

            byte[] decryptedBytes = cipher.doFinal(decodedEncryptedText);

            return new String(decryptedBytes);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

해당 코드에서는, 테스트를 위해 실행시 마다 SecretKey 와, IV Key 가 바뀌게 되지만, 실제 운영환경에서는

고정된 값으로 통신하고자 하는 대상과 키를 공유해야 한다. 

 

해당 예제 코드에서는 단순하게 Text 를 암,복호화 테스트만 진행해 보았고 다음글에서 파라미터와 응답데이터에 암,복호화를 테스트 해보도록 하겠다.

 

Result

반응형

댓글