본문 바로가기
백엔드/Spring

[Spring] AES 암호화를 CBC 모드로 구현하기

by 작은소행성 2024. 7. 30.

AES

대칭키 암호화 알고리즘 

 

 

CBC (Ciper Block Chanining) Mode

블록 암호화 알고리즘에서 데이터의 보안성을 높이기 위해 사용되는 운영모드 중 하나로 

블록 암호화 운영 모드 중 보안성이 제일 높은 암호화 방법이다.

CBC 주요 특징은 key 암호화 뿐만 아니라 초기화 벡터(IV) 라는 무작위 값과 XOR 연산을 수행해야 한다. 

암호문이 블록의 배수가 되기 때문에 입력되는 평문 데이터가 블록 크기와 맞지 않으면 Padding(패딩)을 추가해서 처리해야 한다. 

일반적으로 PKCS5Padding을 사용해 평문의 길이를 블록 크기의 배수로 맞춰준다. 

 

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

 

사용 코드

아래 코드는 AES CBC 모드를 사용하여 데이터를 암호화하고 복호화 하는 코드를 작성해두었다. 

Bearer 토큰을 통해 동적으로 키와 IV 생성을 하도록해 보안성을 높였다. 

 

makeKey : 32바이트 키 생성

makeIvKey : 16바이트 IV 생성 

extractAccessToken : Bearer 토큰 추출

cbcEncrypt : AES CBC 모드로 암호화

cbcDecrypt : AES CBC 모드로 복호화

@Component
public class EncryptionUtils {

    private static final String ALGORITHM = "AES";
    private static final String CBC_ALGORITHM = "AES/CBC/PKCS5Padding";

    //ASE256 key는 32byte
    private static final String KEY = "아무_문자로_32바이트_만들기";


    // 32바이트 추출을 위한 키 길이 조절
    private static String makeKey(String longWord) throws Exception {
        if (longWord == null) return KEY;
        if (longWord.length() < 32) {
            throw new Exception("longword size is short size");
        }
        return longWord.substring(longWord.length() - 32);
    }

    // 16바이트 추출을 위한 키 길이 조절
    private static String makeIvKey(String longWord) throws Exception {
        longWord = extractAccessToken(longWord);
        if (longWord == null) return KEY.substring(0, 16);
        if (longWord.length() < 16) {
            throw new Exception("longword size is short size");
        }
        return longWord.substring(0, 16);
    }

    // "Bearer " 문자열 이후 부분만 반환
    public static String extractAccessToken(String authHeader) {
        // "Bearer " 문자열이 존재하고, 이후에 토큰 값이 존재하는지 확인
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7); // "Bearer " 문자열 이후 부분만 반환
        } else {
            // 예외 처리 또는 기본 값 반환
            return null;
        }
    }
    
 
    
    // 암호화
    public static String cbcEncrypt(String data, String customKey) throws Exception {
        if (ObjectUtils.isEmpty(data)) {
            return null;
        }
        
        // AES 키 생성
        Key key = new SecretKeySpec(makeKey(customKey).getBytes(), ALGORITHM);

        // IV (Initialization Vector) 생성 (16byte)
        IvParameterSpec ivParameterSpec = new IvParameterSpec(makeIvKey(customKey).getBytes());

        Cipher cipher = Cipher.getInstance(CBC_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);

        // 암호화
        byte[] encryptedData = cipher.doFinal(data.getBytes()); // StandardCharsets.UTF_8
        return Base64.encodeBase64String(encryptedData);
    }

   
    // 복호화
    public static String cbcDecrypt(String data, String customKey) throws Exception {
        data = JsonParser.parseString(data).getAsJsonObject().get("enc").getAsString();
        if (ObjectUtils.isEmpty(data)) {
            return null;
        }

        // AES 키 생성
        Key key = new SecretKeySpec(makeKey(customKey).getBytes(), ALGORITHM);

        // IV (Initialization Vector) 생성
        IvParameterSpec ivParameterSpec = new IvParameterSpec(makeIvKey(customKey).getBytes());


        Cipher cipher = Cipher.getInstance(CBC_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
        // 복호화
        byte[] decryptedData = Base64.decodeBase64(data);
        decryptedData = cipher.doFinal(decryptedData);
        return new String(decryptedData); // StandardCharsets.UTF_8
    }
}

 

 

반응형