본문 바로가기

개발/BACK

Spring 환경에서 RSA / AES로 데이터 암호화 하기 - 예제

728x90

Spring 애플리케이션에서 민감한 데이터를 보호하기 위해 RSAAES 암호화를 구현하는 방법을 알아보겠습니다.

RSA는 비대칭 암호화 방식으로 키 교환에 주로 사용되고, AES는 대칭 암호화 방식으로 대용량 데이터 암호화에 효율적입니다. 두 방식을 조합하여 사용하는 하이브리드 암호화 방법도 함께 살펴보겠습니다.


1. 암호화 알고리즘 개요

1.1 RSA (Rivest-Shamir-Adleman)

RSA는 공개키 암호화 알고리즘으로, 공개키로 암호화하고 개인키로 복호화합니다.

  • 비대칭 암호화: 공개키와 개인키 쌍 사용
  • 키 교환: 안전한 키 교환에 적합
  • 성능: 대용량 데이터에는 느림 (일반적으로 245바이트 이하 권장)
  • 용도: 디지털 서명, 키 교환, 소량 데이터 암호화

1.2 AES (Advanced Encryption Standard)

AES는 대칭키 암호화 알고리즘으로, 동일한 키로 암호화와 복호화를 수행합니다.

  • 대칭 암호화: 동일한 키로 암호화/복호화
  • 성능: 빠른 암호화/복호화 속도
  • 키 크기: 128, 192, 256비트 지원
  • 용도: 대용량 데이터 암호화, 파일 암호화

1.3 RSA vs AES 비교

항목 RSA AES
암호화 방식 비대칭 (공개키/개인키) 대칭 (동일 키)
성능 느림 (소량 데이터) 빠름 (대용량 데이터)
키 관리 복잡 (키 쌍 필요) 간단 (단일 키)
주요 용도 키 교환, 서명 데이터 암호화
💡 팁: 실제 프로덕션 환경에서는 하이브리드 암호화를 사용합니다. RSA로 AES 키를 암호화하여 전송하고, AES로 실제 데이터를 암호화합니다. 이렇게 하면 RSA의 안전성과 AES의 성능을 모두 활용할 수 있습니다.

2. 프로젝트 설정

2.1 build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
}

dependencies {
    // Spring Boot
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    
    // QueryDSL
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    implementation 'com.querydsl:querydsl-apt:5.0.0:jakarta'
    
    // 암호화 라이브러리 (Java 기본 제공, 별도 의존성 불필요)
    // java.security.* 패키지 사용
    
    // Base64 인코딩/디코딩 (Java 8+ 기본 제공)
    // java.util.Base64 사용
    
    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    // QueryDSL Annotation Processor
    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
    annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
}
⚠️ 중요: Java는 암호화 기능을 기본 제공하므로 별도의 외부 라이브러리가 필요하지 않습니다. java.securityjavax.crypto 패키지를 사용합니다.

3. RSA 암호화/복호화 구현

3.1 RSAKeyGenerator.java - 키 쌍 생성

package com.example.crypto;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;

public class RSAKeyGenerator {
    
    /**
     * RSA 키 쌍 생성 (2048비트)
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048); // 2048비트 키 크기
        return keyPairGenerator.generateKeyPair();
    }
    
    /**
     * 공개키를 Base64 문자열로 변환
     */
    public static String publicKeyToString(RSAPublicKey publicKey) {
        return Base64.getEncoder().encodeToString(publicKey.getEncoded());
    }
    
    /**
     * 개인키를 Base64 문자열로 변환
     */
    public static String privateKeyToString(RSAPrivateKey privateKey) {
        return Base64.getEncoder().encodeToString(privateKey.getEncoded());
    }
    
    /**
     * 키 생성 및 출력 예제
     */
    public static void main(String[] args) throws Exception {
        KeyPair keyPair = generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        
        System.out.println("=== RSA 키 쌍 생성 ===");
        System.out.println("공개키:");
        System.out.println(publicKeyToString(publicKey));
        System.out.println("\n개인키:");
        System.out.println(privateKeyToString(privateKey));
    }
}

3.2 RSAUtil.java - 암호화/복호화 유틸리티

package com.example.crypto;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAUtil {
    
    private static final String ALGORITHM = "RSA";
    private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";
    
    /**
     * Base64 문자열을 공개키로 변환
     */
    public static PublicKey getPublicKey(String base64PublicKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64PublicKey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePublic(spec);
    }
    
    /**
     * Base64 문자열을 개인키로 변환
     */
    public static PrivateKey getPrivateKey(String base64PrivateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64PrivateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePrivate(spec);
    }
    
    /**
     * 공개키로 암호화
     */
    public static String encrypt(String plainText, String publicKeyStr) throws Exception {
        PublicKey publicKey = getPublicKey(publicKeyStr);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        
        byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
        
        // RSA는 블록 단위로 암호화하므로 데이터를 분할해야 함
        // 2048비트 키의 경우 최대 245바이트까지 암호화 가능
        int maxBlockSize = 245;
        StringBuilder encrypted = new StringBuilder();
        
        for (int i = 0; i < plainTextBytes.length; i += maxBlockSize) {
            int endIndex = Math.min(i + maxBlockSize, plainTextBytes.length);
            byte[] block = new byte[endIndex - i];
            System.arraycopy(plainTextBytes, i, block, 0, block.length);
            
            byte[] encryptedBlock = cipher.doFinal(block);
            encrypted.append(Base64.getEncoder().encodeToString(encryptedBlock));
            if (endIndex < plainTextBytes.length) {
                encrypted.append(":");
            }
        }
        
        return encrypted.toString();
    }
    
    /**
     * 개인키로 복호화
     */
    public static String decrypt(String encryptedText, String privateKeyStr) throws Exception {
        PrivateKey privateKey = getPrivateKey(privateKeyStr);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        
        String[] blocks = encryptedText.split(":");
        StringBuilder decrypted = new StringBuilder();
        
        for (String block : blocks) {
            byte[] encryptedBytes = Base64.getDecoder().decode(block);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            decrypted.append(new String(decryptedBytes, StandardCharsets.UTF_8));
        }
        
        return decrypted.toString();
    }
}

3.3 RSA 사용 예제

package com.example.crypto;

import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

public class RSAExample {
    
    public static void main(String[] args) throws Exception {
        // 1. 키 쌍 생성
        KeyPair keyPair = RSAKeyGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        
        String publicKeyStr = RSAKeyGenerator.publicKeyToString(publicKey);
        String privateKeyStr = RSAKeyGenerator.privateKeyToString(privateKey);
        
        System.out.println("공개키: " + publicKeyStr);
        System.out.println("개인키: " + privateKeyStr);
        
        // 2. 암호화할 데이터
        String plainText = "안녕하세요! 이것은 암호화 테스트입니다.";
        System.out.println("\n원본 텍스트: " + plainText);
        
        // 3. 공개키로 암호화
        String encrypted = RSAUtil.encrypt(plainText, publicKeyStr);
        System.out.println("암호화된 텍스트: " + encrypted);
        
        // 4. 개인키로 복호화
        String decrypted = RSAUtil.decrypt(encrypted, privateKeyStr);
        System.out.println("복호화된 텍스트: " + decrypted);
        
        // 5. 검증
        System.out.println("\n암호화/복호화 성공: " + plainText.equals(decrypted));
    }
}
⚠️ 주의: RSA는 대용량 데이터를 직접 암호화하기에는 비효율적입니다. 2048비트 키의 경우 최대 245바이트까지만 암호화할 수 있습니다. 대용량 데이터는 하이브리드 방식(RSA로 AES 키 암호화)을 사용하세요.

4. AES 암호화/복호화 구현

4.1 AESUtil.java - AES 암호화/복호화 유틸리티

package com.example.crypto;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

public class AESUtil {
    
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
    private static final int KEY_SIZE = 256; // 256비트 키
    private static final int GCM_IV_LENGTH = 12; // GCM IV 길이 (12바이트)
    private static final int GCM_TAG_LENGTH = 16; // GCM 태그 길이 (16바이트)
    
    /**
     * AES 키 생성 (256비트)
     */
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        keyGenerator.init(KEY_SIZE);
        return keyGenerator.generateKey();
    }
    
    /**
     * Base64 문자열을 SecretKey로 변환
     */
    public static SecretKey getKeyFromString(String keyStr) {
        byte[] decodedKey = Base64.getDecoder().decode(keyStr);
        return new SecretKeySpec(decodedKey, ALGORITHM);
    }
    
    /**
     * SecretKey를 Base64 문자열로 변환
     */
    public static String keyToString(SecretKey key) {
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }
    
    /**
     * AES로 암호화
     */
    public static String encrypt(String plainText, String keyStr) throws Exception {
        SecretKey key = getKeyFromString(keyStr);
        return encrypt(plainText, key);
    }
    
    /**
     * AES로 암호화 (SecretKey 사용)
     */
    public static String encrypt(String plainText, SecretKey key) throws Exception {
        // IV(Initialization Vector) 생성
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        
        // Cipher 초기화
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
        
        // 암호화 수행
        byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
        byte[] encryptedBytes = cipher.doFinal(plainTextBytes);
        
        // IV와 암호화된 데이터를 결합하여 Base64로 인코딩
        byte[] combined = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, combined, iv.length, encryptedBytes.length);
        
        return Base64.getEncoder().encodeToString(combined);
    }
    
    /**
     * AES로 복호화
     */
    public static String decrypt(String encryptedText, String keyStr) throws Exception {
        SecretKey key = getKeyFromString(keyStr);
        return decrypt(encryptedText, key);
    }
    
    /**
     * AES로 복호화 (SecretKey 사용)
     */
    public static String decrypt(String encryptedText, SecretKey key) throws Exception {
        // Base64 디코딩
        byte[] combined = Base64.getDecoder().decode(encryptedText);
        
        // IV 추출
        byte[] iv = new byte[GCM_IV_LENGTH];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        
        // 암호화된 데이터 추출
        byte[] encryptedBytes = new byte[combined.length - GCM_IV_LENGTH];
        System.arraycopy(combined, GCM_IV_LENGTH, encryptedBytes, 0, encryptedBytes.length);
        
        // Cipher 초기화
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
        
        // 복호화 수행
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

4.2 AES 사용 예제

package com.example.crypto;

import javax.crypto.SecretKey;

public class AESExample {
    
    public static void main(String[] args) throws Exception {
        // 1. AES 키 생성
        SecretKey key = AESUtil.generateKey();
        String keyStr = AESUtil.keyToString(key);
        System.out.println("AES 키: " + keyStr);
        
        // 2. 암호화할 데이터
        String plainText = "안녕하세요! 이것은 AES 암호화 테스트입니다. 대용량 데이터도 빠르게 암호화할 수 있습니다.";
        System.out.println("\n원본 텍스트: " + plainText);
        
        // 3. 암호화
        String encrypted = AESUtil.encrypt(plainText, keyStr);
        System.out.println("암호화된 텍스트: " + encrypted);
        
        // 4. 복호화
        String decrypted = AESUtil.decrypt(encrypted, keyStr);
        System.out.println("복호화된 텍스트: " + decrypted);
        
        // 5. 검증
        System.out.println("\n암호화/복호화 성공: " + plainText.equals(decrypted));
    }
}
💡 팁: AES-GCM 모드는 인증된 암호화(Authenticated Encryption)를 제공하여 데이터 무결성도 보장합니다. IV(Initialization Vector)는 매번 랜덤하게 생성하여 같은 평문도 다른 암호문을 생성하도록 합니다.

5. 하이브리드 암호화 (RSA + AES)

하이브리드 암호화는 RSA의 안전한 키 교환과 AES의 빠른 암호화 성능을 결합한 방식입니다.

5.1 하이브리드 암호화 과정

1. AES 키 생성 (랜덤)
   ↓
2. AES 키를 RSA 공개키로 암호화
   ↓
3. 실제 데이터를 AES 키로 암호화
   ↓
4. 암호화된 AES 키 + 암호화된 데이터 전송
   ↓
5. 수신 측에서 RSA 개인키로 AES 키 복호화
   ↓
6. 복호화된 AES 키로 데이터 복호화

5.2 HybridCryptoUtil.java

package com.example.crypto;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class HybridCryptoUtil {
    
    /**
     * 하이브리드 암호화 (RSA + AES)
     * @param plainText 암호화할 평문
     * @param rsaPublicKey RSA 공개키 (Base64)
     * @return "암호화된AES키:암호화된데이터" 형식의 문자열
     */
    public static String encrypt(String plainText, String rsaPublicKey) throws Exception {
        // 1. AES 키 생성
        SecretKey aesKey = AESUtil.generateKey();
        String aesKeyStr = AESUtil.keyToString(aesKey);
        
        // 2. AES 키를 RSA 공개키로 암호화
        String encryptedAESKey = RSAUtil.encrypt(aesKeyStr, rsaPublicKey);
        
        // 3. 실제 데이터를 AES 키로 암호화
        String encryptedData = AESUtil.encrypt(plainText, aesKey);
        
        // 4. 암호화된 AES 키와 암호화된 데이터를 결합
        return encryptedAESKey + ":" + encryptedData;
    }
    
    /**
     * 하이브리드 복호화 (RSA + AES)
     * @param encryptedText "암호화된AES키:암호화된데이터" 형식의 문자열
     * @param rsaPrivateKey RSA 개인키 (Base64)
     * @return 복호화된 평문
     */
    public static String decrypt(String encryptedText, String rsaPrivateKey) throws Exception {
        // 1. 암호화된 AES 키와 암호화된 데이터 분리
        String[] parts = encryptedText.split(":", 2);
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid encrypted text format");
        }
        
        String encryptedAESKey = parts[0];
        String encryptedData = parts[1];
        
        // 2. RSA 개인키로 AES 키 복호화
        String decryptedAESKey = RSAUtil.decrypt(encryptedAESKey, rsaPrivateKey);
        
        // 3. 복호화된 AES 키로 데이터 복호화
        String decryptedData = AESUtil.decrypt(encryptedData, decryptedAESKey);
        
        return decryptedData;
    }
}

5.3 하이브리드 암호화 사용 예제

package com.example.crypto;

import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

public class HybridCryptoExample {
    
    public static void main(String[] args) throws Exception {
        // 1. RSA 키 쌍 생성
        KeyPair keyPair = RSAKeyGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        
        String publicKeyStr = RSAKeyGenerator.publicKeyToString(publicKey);
        String privateKeyStr = RSAKeyGenerator.privateKeyToString(privateKey);
        
        // 2. 암호화할 대용량 데이터
        String plainText = "이것은 하이브리드 암호화 테스트입니다. " +
                          "RSA의 안전성과 AES의 성능을 모두 활용하여 " +
                          "대용량 데이터를 효율적으로 암호화할 수 있습니다. " +
                          "실제 프로덕션 환경에서 가장 많이 사용되는 방식입니다.";
        
        System.out.println("원본 텍스트: " + plainText);
        System.out.println("원본 길이: " + plainText.length() + " 바이트");
        
        // 3. 하이브리드 암호화
        String encrypted = HybridCryptoUtil.encrypt(plainText, publicKeyStr);
        System.out.println("\n암호화된 텍스트: " + encrypted);
        
        // 4. 하이브리드 복호화
        String decrypted = HybridCryptoUtil.decrypt(encrypted, privateKeyStr);
        System.out.println("복호화된 텍스트: " + decrypted);
        
        // 5. 검증
        System.out.println("\n암호화/복호화 성공: " + plainText.equals(decrypted));
    }
}

6. Spring Service로 통합

6.1 CryptoService.java

package com.example.service;

import com.example.crypto.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

@Slf4j
@Service
public class CryptoService {
    
    @Value("${crypto.rsa.public-key:}")
    private String rsaPublicKey;
    
    @Value("${crypto.rsa.private-key:}")
    private String rsaPrivateKey;
    
    @Value("${crypto.aes.key:}")
    private String aesKey;
    
    /**
     * RSA 암호화
     */
    public String encryptWithRSA(String plainText) {
        try {
            return RSAUtil.encrypt(plainText, rsaPublicKey);
        } catch (Exception e) {
            log.error("RSA 암호화 실패", e);
            throw new RuntimeException("RSA encryption failed", e);
        }
    }
    
    /**
     * RSA 복호화
     */
    public String decryptWithRSA(String encryptedText) {
        try {
            return RSAUtil.decrypt(encryptedText, rsaPrivateKey);
        } catch (Exception e) {
            log.error("RSA 복호화 실패", e);
            throw new RuntimeException("RSA decryption failed", e);
        }
    }
    
    /**
     * AES 암호화
     */
    public String encryptWithAES(String plainText) {
        try {
            return AESUtil.encrypt(plainText, aesKey);
        } catch (Exception e) {
            log.error("AES 암호화 실패", e);
            throw new RuntimeException("AES encryption failed", e);
        }
    }
    
    /**
     * AES 복호화
     */
    public String decryptWithAES(String encryptedText) {
        try {
            return AESUtil.decrypt(encryptedText, aesKey);
        } catch (Exception e) {
            log.error("AES 복호화 실패", e);
            throw new RuntimeException("AES decryption failed", e);
        }
    }
    
    /**
     * 하이브리드 암호화 (RSA + AES)
     */
    public String encryptHybrid(String plainText) {
        try {
            return HybridCryptoUtil.encrypt(plainText, rsaPublicKey);
        } catch (Exception e) {
            log.error("하이브리드 암호화 실패", e);
            throw new RuntimeException("Hybrid encryption failed", e);
        }
    }
    
    /**
     * 하이브리드 복호화 (RSA + AES)
     */
    public String decryptHybrid(String encryptedText) {
        try {
            return HybridCryptoUtil.decrypt(encryptedText, rsaPrivateKey);
        } catch (Exception e) {
            log.error("하이브리드 복호화 실패", e);
            throw new RuntimeException("Hybrid decryption failed", e);
        }
    }
    
    /**
     * RSA 키 쌍 생성
     */
    public KeyPair generateRSAKeyPair() {
        try {
            return RSAKeyGenerator.generateKeyPair();
        } catch (Exception e) {
            log.error("RSA 키 쌍 생성 실패", e);
            throw new RuntimeException("RSA key pair generation failed", e);
        }
    }
    
    /**
     * AES 키 생성
     */
    public SecretKey generateAESKey() {
        try {
            return AESUtil.generateKey();
        } catch (Exception e) {
            log.error("AES 키 생성 실패", e);
            throw new RuntimeException("AES key generation failed", e);
        }
    }
}

6.2 application.yml 설정

crypto:
  rsa:
    # RSA 공개키 (Base64)
    public-key: YOUR_RSA_PUBLIC_KEY_HERE
    # RSA 개인키 (Base64) - 절대 공개하지 마세요!
    private-key: YOUR_RSA_PRIVATE_KEY_HERE
  aes:
    # AES 키 (Base64) - 절대 공개하지 마세요!
    key: YOUR_AES_KEY_HERE
⚠️ 보안 주의: 실제 운영 환경에서는 키를 환경 변수나 보안 저장소에 저장하고, application.yml에 직접 저장하지 마세요. Spring Cloud Config, AWS Secrets Manager 등을 사용하세요.

7. 실제 사용 예제

7.1 CryptoController.java

package com.example.controller;

import com.example.service.CryptoService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/crypto")
@RequiredArgsConstructor
public class CryptoController {
    
    private final CryptoService cryptoService;
    
    /**
     * RSA 암호화
     */
    @PostMapping("/rsa/encrypt")
    public ResponseEntity<Map<String, String>> encryptRSA(@RequestBody Map<String, String> request) {
        String plainText = request.get("text");
        String encrypted = cryptoService.encryptWithRSA(plainText);
        
        Map<String, String> response = new HashMap<>();
        response.put("encrypted", encrypted);
        return ResponseEntity.ok(response);
    }
    
    /**
     * RSA 복호화
     */
    @PostMapping("/rsa/decrypt")
    public ResponseEntity<Map<String, String>> decryptRSA(@RequestBody Map<String, String> request) {
        String encrypted = request.get("encrypted");
        String decrypted = cryptoService.decryptWithRSA(encrypted);
        
        Map<String, String> response = new HashMap<>();
        response.put("decrypted", decrypted);
        return ResponseEntity.ok(response);
    }
    
    /**
     * AES 암호화
     */
    @PostMapping("/aes/encrypt")
    public ResponseEntity<Map<String, String>> encryptAES(@RequestBody Map<String, String> request) {
        String plainText = request.get("text");
        String encrypted = cryptoService.encryptWithAES(plainText);
        
        Map<String, String> response = new HashMap<>();
        response.put("encrypted", encrypted);
        return ResponseEntity.ok(response);
    }
    
    /**
     * AES 복호화
     */
    @PostMapping("/aes/decrypt")
    public ResponseEntity<Map<String, String>> decryptAES(@RequestBody Map<String, String> request) {
        String encrypted = request.get("encrypted");
        String decrypted = cryptoService.decryptWithAES(encrypted);
        
        Map<String, String> response = new HashMap<>();
        response.put("decrypted", decrypted);
        return ResponseEntity.ok(response);
    }
    
    /**
     * 하이브리드 암호화
     */
    @PostMapping("/hybrid/encrypt")
    public ResponseEntity<Map<String, String>> encryptHybrid(@RequestBody Map<String, String> request) {
        String plainText = request.get("text");
        String encrypted = cryptoService.encryptHybrid(plainText);
        
        Map<String, String> response = new HashMap<>();
        response.put("encrypted", encrypted);
        return ResponseEntity.ok(response);
    }
    
    /**
     * 하이브리드 복호화
     */
    @PostMapping("/hybrid/decrypt")
    public ResponseEntity<Map<String, String>> decryptHybrid(@RequestBody Map<String, String> request) {
        String encrypted = request.get("encrypted");
        String decrypted = cryptoService.decryptHybrid(encrypted);
        
        Map<String, String> response = new HashMap<>();
        response.put("decrypted", decrypted);
        return ResponseEntity.ok(response);
    }
}

7.2 API 사용 예제

RSA 암호화/복호화:

# RSA 암호화
curl -X POST http://localhost:8080/api/crypto/rsa/encrypt \
  -H "Content-Type: application/json" \
  -d '{"text": "안녕하세요!"}'

# RSA 복호화
curl -X POST http://localhost:8080/api/crypto/rsa/decrypt \
  -H "Content-Type: application/json" \
  -d '{"encrypted": "암호화된_문자열"}'

AES 암호화/복호화:

# AES 암호화
curl -X POST http://localhost:8080/api/crypto/aes/encrypt \
  -H "Content-Type: application/json" \
  -d '{"text": "대용량 데이터 암호화 테스트"}'

# AES 복호화
curl -X POST http://localhost:8080/api/crypto/aes/decrypt \
  -H "Content-Type: application/json" \
  -d '{"encrypted": "암호화된_문자열"}'

하이브리드 암호화/복호화:

# 하이브리드 암호화
curl -X POST http://localhost:8080/api/crypto/hybrid/encrypt \
  -H "Content-Type: application/json" \
  -d '{"text": "하이브리드 암호화 테스트입니다."}'

# 하이브리드 복호화
curl -X POST http://localhost:8080/api/crypto/hybrid/decrypt \
  -H "Content-Type: application/json" \
  -d '{"encrypted": "암호화된_문자열"}'

8. 보안 주의사항

8.1 키 관리

  • 개인키 보호: RSA 개인키와 AES 키는 절대 공개하지 마세요
  • 환경 변수 사용: 키를 코드나 설정 파일에 하드코딩하지 마세요
  • 키 로테이션: 정기적으로 키를 교체하세요
  • 키 저장소: AWS Secrets Manager, HashiCorp Vault 등 사용

8.2 암호화 모드 선택

상황 권장 방식
소량 데이터 (< 245바이트) RSA
대용량 데이터 AES 또는 하이브리드
키 교환이 필요한 경우 하이브리드 (RSA + AES)
단일 서버 내부 암호화 AES

8.3 추가 보안 고려사항

  • HTTPS 사용: 전송 중 데이터 보호를 위해 HTTPS 필수
  • Salt 사용: 패스워드 암호화 시 Salt 추가
  • IV 관리: AES 사용 시 매번 새로운 IV 생성
  • 에러 처리: 암호화 실패 시 민감한 정보 노출 방지
  • 로깅: 키나 평문을 로그에 남기지 않기
✅ 핵심 요약:
  • RSA는 비대칭 암호화, AES는 대칭 암호화
  • RSA는 소량 데이터, AES는 대용량 데이터에 적합
  • 하이브리드 암호화는 RSA + AES를 결합한 방식
  • Java는 암호화 기능을 기본 제공 (별도 라이브러리 불필요)
  • 키 관리는 보안의 핵심 - 절대 공개하지 마세요
  • 실제 운영 환경에서는 키를 안전한 저장소에 보관

 

카카오톡 오픈채팅 링크

https://open.kakao.com/o/seCteX7h

 

 

 

추가로 궁금한 점이 있으시면 댓글로 남겨주세요! 🚀

 

 


참고 자료

 

728x90