AES 암호화와 RSA 암호화

최서환·2024년 9월 1일
post-thumbnail

개요

  • AES와 RSA는 모두 현대 암호화 기술에서 널리 사용되는 암호화 알고리즘
  • 그러나 이 두 알고리즘은 서로 다른 방식으로 동작하며, 각기 다른 용도와 장단점을 가지고 있음
  • 해당 내용을 이해하기 위해 다음과 같은 순서로 알아보자
    1. 암호화 방식
    2. 암호화 알고리즘의 보안 속성
    3. 사용 사례
    4. 코드
    5. 성능
    6. 부록1. 초기화 벡터 (CBC 모드)
    7. 부록2. 해싱(Hashing)과 암호화(Encryption)의 차이점

1. 암호화 방식

AES (Advanced Encryption Standard) 암호화 방식

  • 대칭 키 암호화 방식
    • 암호화와 복호화에 동일한 키를 사용
    • 따라서 보안을 위해 이 키는 안전하게 관리되어야 함, 키가 유출되면 전반적으로 위험에 처할 수 있음

RSA (Rivest-Shamir-Adleman) 암호화 방식

  • 비대칭키 암호화 방식
    • RSA는 공개키와 비밀키라는 두 개의 키를 사용
    • 공개키로 암호화된 데이터는 비밀키로만 복호화할 수 있음
    • 반대로 비밀키로 서명된 데이터는 공개 키로만 검증할 수 있음
  • 공개키와 개인키
    • 공개키
      • 일반적으로 서버가 공개키를 소유하고, 이를 클라이언트에게 배포
      • 공개키는 누구에게나 공개할 수 있으며, 이를 통해 클라이언트가 서버로 데이터를 안전하게 보낼 수 있음
      • 역할
        • 암호화 ****
          • 클라이언트가 서버에 데이터를 보낼 때, 서버의 공개키를 사용해 데이터를 암호화
          • 암호화된 데이터는 서버의 개인키가 없이는 복호화할 수 없음
        • 인증
          • 클라이언트는 서버가 제공하는 공개키를 사용해 서버의 신원을 확인할 수 있음
          • 공개키가 인증서와 함께 제공되며, 이 인증서를 통해 서버의 신뢰성을 검증
    • 개인키
      • 개인키는 서버가 소유하며, 외부에 절대 공개되지 않아야 함. 개인키는 암호화된 데이터를 복호화하고, 서버의 신원을 증명하는 데 사용
      • 역할
        • 복호화
          • 클라이언트가 서버의 공개키로 암호화한 데이터를 서버는 자신의 개인키로 복호화할 수 있음
        • 디지털 서명
          • 서버는 개인키로 데이터를 서명하여, 클라이언트에게 자신이 신뢰할 수 있는 서버임을 증명할 수 있음
          • 클라이언트는 서버의 공개키로 이 서명을 검증할 수 있음

2. 암호화 알고리즘의 보안 속성

기밀성 (Confidentiality)

  • AES
    • AES는 주로 기밀성을 보장하는 데 사용됨
    • 대칭 키 암호화 알고리즘으로, 데이터를 암호화하여 무단 접근으로부터 보호
    • AES의 기밀성은 매우 강력
  • RSA
    • RSA도 기밀성을 제공할 수 있지만, 주로 작은 데이터나 키를 암호화하는 데 사용됨
    • 비대칭 키 암호화 알고리즘으로, 공개 키로 암호화된 데이터는 비밀 키로만 복호화할 수 있음
    • 하지만 RSA는 AES보다 속도가 느리기 때문에, 대량의 데이터를 암호화하는 데는 적합하지 않음
    • 세션 키와 같은 중요한 정보를 안전하게 교환하는 데 주로 사용
  • cf) 기밀성이란
    • 민감한 정보가 무단으로 노출되지 않도록 보호하는 것
    • 데이터를 볼 권한이 없는 사람이 데이터에 접근하지 못하게 하고, 데이터가 안전하게 유지되도록함
    • 예시
      • 암호화 : 데이터를 암호화하여 오직 복호화 키를 가진 사람만이 데이터를 읽을 수 있도록함
      • 접근제어 : 스템에서 사용자마다 접근 권한을 부여하여, 허가된 사용자만 특정 데이터에 접근할 수 있도록 하는 것
      • 익명성 : 용자의 신원을 숨겨서 불필요한 정보 노출을 방지하는 것

무결성 (Integrity)

  • AES
    • 기본적으로 무결성을 제공하지 않음
    • 기밀성을 제공하지만, 메시지가 도중에 변조되었는지 확인하는 기능이 없음
      • 네트워크를 통해 AES로 암호화된 파일을 전송할 때, 누군가가 암호화된 데이터를 조작하여 수신자가 이를 복호화하면, 잘못된 정보가 나오더라도 수신자는 이 사실을 알 방법이 없음
    • 따라서 무결성을 보장하기 위해서는 별도의 MAC(Message Authentication Code)나 HMAC(Hash-based Message Authentication Code)와 같은 기술과 결합하여 사용해야함
  • RSA
    • RSA는 디지털 서명에 사용될 때 무결성을 보장할 수 있음
    • RSA 비밀 키로 서명된 데이터는 공개 키로 검증할 수 있음
    • 데이터가 서명 이후 변경되지 않았음을 확인할 수 있음
  • cf) 무결성이란
    • 무결성은 데이터를 전송하거나 저장할 때, 그 데이터가 전송 과정에서 변조되거나 손상되지 않았음을 보장하는 속성
    • 예를 들어, 누군가가 암호화된 메시지의 일부를 수정하거나 바꿨을 때, 수신자는 이 변경 사실을 알아야만 함
    • 이를 위해 암호화된 데이터의 변조 여부를 확인하는 무결성 체크가 필요
    • 무결성이 중요한 이유는, 데이터가 암호화되었다고 해서 항상 안전한 것은 아니기 때문
      • 데이터가 암호화되었더라도, 중간에 누군가가 암호문을 조작하면 복호화 후에는 의미 없는 데이터가 될 수 있고, 더 나아가 공격자는 이 조작을 통해 의도된 결과를 만들 수도 있음
      • 은행 송금 시스템에서 암호화된 데이터를 조작하여 금액이나 계좌 정보를 변경하는 경우가 있을 수 있음

신뢰성 (Authenticity)

  • AES
    • AES 자체로는 신뢰성을 보장하지 않음
    • 데이터를 보낸 사람의 신원을 확인하는 데 사용할 수 없음
    • AES는 인증된 키 교환 프로토콜과 함께 사용될 때, 신뢰성을 보장할 수 있는 기반을 제공
  • RSA
    • 신뢰성을 보장하는 데 매우 중요한 역할을 함
    • RSA 공개 키 암호화는 디지털 서명과 결합되어 사용자의 신원을 확인할 수 있게해 줌
    • 사용자가 RSA 비밀 키로 데이터를 서명하면, 그 사용자의 신원을 확인할 수 있으며, 이는 신뢰성을 보장
    • 따라서 RSA는 인증서 기반의 시스템에서 중요한 역할
  • cf) 신뢰성 이란
    • 데이터를 전송하거나 제공한 주체가 실제로 신뢰할 수 있는 자원임을 보장하는 속성
    • 데이터나 메시지가 실제 발신자로부터 온 것임을 증명하는 것을 의미하며, 데이터를 수신하는 쪽에서 데이터의 출처를 검증할 수 있게 함
    • 인증(authentication)과 매우 밀접한 개념
    • 예시
      • 디지털 서명 : 발신자가 자신의 비밀 키로 메시지에 서명하여, 수신자는 서명된 메시지가 발신자에게서 온 것임을 확인할 수 있음
      • 인증서 : SSL/TLS에서 사용되는 디지털 인증서는 웹사이트의 신원을 검증하여 사용자가 신뢰할 수 있는 서버와 통신하고 있음을 보장
      • 2단계 인증 : 사용자가 시스템에 로그인할 때, 비밀번호 외에 추가적인 인증 요소를 요구하여 사용자의 신원을 더 확실하게 확인

확장성

  • AES
    • 키 길이에 따라 확장할 수 있음
    • AES-128, AES-192, AES-256과 같이 다양한 키 길이에서 사용될 수 있음
  • RSA
    • 키 길이에 크게 의존
    • 2048비트, 4096비트 등으로 확장할 수 있음
    • 보안 요구사항에 따라 키 크기를 선택해야 함

3. 사용 사례

AES

  • 데이터 전송, 파일 암호화, VPN, 디스크 암호화 등 대량 데이터를 암호화해야 할 때 많이 사용
  • HTTPS 를 통해 전송되는 데이터를 암호화 하는데 AES가 자주 사용됨
  • 파일 및 디스크 암호화 및 암호화된 데이터 베이스, 클라우드 스토리지 보안, 스마트폰(Android, ios) 데이터 보안 등에 사용됨
  • 메세징 앱(WhatsApp, Telegram)과 같은 안전한 메시징 앱은 종단간 암호에 AES를 사용
  • VPN, 무선 통신(Wi-Fi) WPA2 보안 프로토콜 등에 사용

RSA

  • 작은 데이터 조각의 안전한 전송, 키 교환, 인증서 서명 등에 주로 사용됨

  • SSL/TLS 인증서에서 클라이언트와 서버 간의 키 교환 시 RSA가 사용됨

    ※ 한편 SSL/TLS 에서도 데이터를 암호화할때는 AES를 사용

4. 코드

AES

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.springframework.stereotype.Service;

@Service
public class AesEncryptionService {
  private static final String ALGORITHM = "AES";
  // 더 구체적으로 어떤 암호화 모드와 패딩 방식을 사용할지 지정
   private static final String TRANSFORMATION = "AES"; // "AES/CBC/PKCS5Padding"
  private static final String CUSTOM_SECRET_KEY = "mySecretKey!!";

  public SecretKey generateKey(int n) throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    keyGenerator.init(n);

    return keyGenerator.generateKey();
  }

  public String encrypt(String plainText, SecretKey key) throws Exception {
    Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());

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

  public String decrypt(String cipherText, SecretKey secretKey) throws Exception {
    Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(cipherText));

    return new String(decryptedBytes);
  }

  public String encryptJson(Object jsonObject, SecretKey secretKey) throws Exception {
    ObjectMapper objectMapper = new ObjectMapper();
    String plainText = objectMapper.writeValueAsString(jsonObject);

    return encrypt(plainText, secretKey);
  }

  public <T> T decryptJson(String jsonObject, SecretKey key, Class<T> valueType) throws Exception {
    String decryptedJson = decrypt(jsonObject, key);
    ObjectMapper objectMapper = new ObjectMapper();

    return objectMapper.readValue(decryptedJson, valueType);
  }

  public static void main(String[] args) throws Exception {
    // json 포맷의 평문 생성
    Map<String, Object> licenseData = new HashMap<>();
    licenseData.put("companyId", "company");
    licenseData.put("tenantId", new String[]{"tenant01", "tenant02"});
    licenseData.put("licenseCapacity", new int[]{100, 80, 200});
    licenseData.put("expiryDate", "2025-12-31");
    licenseData.put("issuedBy", "shchoice");

    // RSA 암호화를 위한 Secret Key 생성
    AesEncryptionService service = new AesEncryptionService();
    SecretKey secretKey = service.generateKey(128);

    // 암호화
    String encryptedText = service.encryptJson(licenseData, secretKey);
    System.out.println(encryptedText);
    // wg6GLFQbpj6jQhWfwViqPEddKrQ2e1Z3Ru4ibcTE8NJhKbhJ2Z6I5A8W2Yu0MfJZSeh+iEehQqMfqz0EUPh5kecDoDcWdqxPUOK51QaC4Tx+rGZi0NeG3Fmilwe3QOh/ljOwv66z9ShHD4C+5O1njhWLpqx6n1h1oADVOHYzsaoPTYI6U24qJnfG+jNQiYej

    // 복호화
    Map<String, Object> plainText = service.decryptJson(encryptedText, secretKey, HashMap.class);
    System.out.println(plainText);
    // {expiryDate=2025-12-31, companyId=KB, licenseCapacity=[100, 80, 200], issuedBy=Diquest, tenantId=[tenant01, tenant02]}
  }
}

RSA

import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;

import org.springframework.stereotype.Service;

@Service
public class RsaEncryptionService {
  private static final String ALGORITHM = "RSA";
  private static final int KEY_SIZE = 2048;

  public KeyPair generateKeyPair(int n) throws Exception {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
    keyPairGenerator.initialize(KEY_SIZE);

    return keyPairGenerator.generateKeyPair();
  }

  public String encrypt(String plainText, PublicKey publicKey) throws Exception {
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());

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

  public String decrypt(String cipherText, PrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(cipherText));

    return new String(decryptedBytes);
  }

  public static void main(String[] args) throws Exception {
    // json 포맷의 평문 생성
    Map<String, Object> licenseData = new HashMap<>();
    licenseData.put("companyId", "company");
    licenseData.put("tenantId", new String[]{"tenant01", "tenant02"});
    licenseData.put("licenseCapacity", new int[]{100, 80, 200});
    licenseData.put("expiryDate", "2025-12-31");
    licenseData.put("issuedBy", "shchoice");
    ObjectMapper mapper = new ObjectMapper();
    String jsonData = mapper.writeValueAsString(licenseData);

    // RSA 암호화를 위한 Public Key 및 Private Key 생성
    RsaEncryptionService service = new RsaEncryptionService();
    KeyPair keyPair = service.generateKeyPair(2048);
    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();

    // Public Key를 통한 암호화
    String encrypted = service.encrypt(jsonData, publicKey);
    System.out.println("encrypted = " + encrypted);
    /* encrypted = Tw09O0BewgdUkydStdwjYoZBGnAeh8CVqc/CX5N5cdL+L1GKtUxlWkAoX96pwjhMpwjkuxYvvCUfXiDDwN9R0Wbrd5KjBk4ksJbORTREKtagyssRXc5lr9DTIxt4vpRhmfu
    812DaIhVFBe6SUt6O9eRgM6bGUmHlzyHcb91Hth2uRZZFb1ZYfW7q7n5hdkunZFTfJPEXeYcBmRmtkhqu/qwMTadJe4BAU5nP+0DFVMkd5nYar3t42+7xXdOXAaxM7IosgeFUUfzp+DsThPiG3
    4DGUnNZ4xHz8FMC1fyiYDoeTNXB8UjP5hPYAeJSdutI9flJSVBxE+c2rpnXSgE70Q==
    */

    // Private Key를 통한 암호화
    String decrypted  = service.decrypt(encrypted, privateKey);
    System.out.println("decrypted = " + decrypted);
    // decrypted = {"expiryDate":"2025-12-31","companyId":"KB","licenseCapacity":[100,80,200],"issuedBy":"Diquest","tenantId":["tenant01","tenant02"]}

    Map<String, Object> decodedLicenseData = mapper.readValue(decrypted, HashMap.class);
    System.out.println("Decoded JSON = " + decodedLicenseData);
    // Decoded JSON = {expiryDate=2025-12-31, companyId=KB, licenseCapacity=[100, 80, 200], issuedBy=Diquest, tenantId=[tenant01, tenant02]}
  }
}

5. 성능

AES

  • 하드웨어 가속이 가능하며, 특히 대량의 데이터를 빠르게 암호화하는 데 최적화되어 있음
  • 대칭 암호화 방식이기 때문에 상대적으로 연산이 간단하고 빠름
  • Time Complexity : O(n) (n은 데이터의 크기(블록 수))

RSA

  • 연산이 매우 복잡하며, 대량 데이터를 처리하는 데 적합하지 않음
  • 키의 길이가 길어질수록 연산 복잡도가 기하급수적으로 증가하여 더 느려짐
    • AES는 단순한 대칭 키 연산을 사용하여 데이터 블록을 암호화하는 반면, RSA는 큰 소수(prime number)와 모듈러 산술(modular arithmetic)을 사용하여 복잡한 수학적 연산을 수행하기 때문
  • 주로 소량의 데이터나 세션 키를 암호화하는 데 사용됨
  • Time Complexity : O(k^3) (k는 RSA의 크기(비트 수))

부록1. AES 암호화의 초기화 벡터 (Initialization Vector, IV)

목적

  • 임의성 추가
    • AES 암호화에서 특히 CBC 모드와 같은 블록 암호화 모드에서 사용됨
    • 초기화 벡터는 동일한 평문과 키를 사용하더라도 매번 다른 암호문을 생성할 수 있도록 임의성을 추가함
  • 보안 강화
    • 초기화 벡터는 암호화된 데이터의 보안성을 높이는 데 중요한 역할을 하며, 보통 각 암호화 작업마다 새로운 IV를 생성하여 사용
  • IV는 암호화된 데이터와 함께 저장되거나 전송되며, 복호화 시에도 동일한 IV를 사용하여 원본 데이터를 복구함

사용방법

  1. IV를 암호문에 포함하는 방법
  • 설명

    • 이 방법에서는 암호화할 때 생성된 IV를 암호문에 포함하여 저장하거나 전송
    • 일반적으로, IV는 암호문 앞에 추가되며, 암호화된 데이터와 함께 하나의 문자열로 결합
  • 장점

    • 단순화: IV를 따로 관리할 필요가 없으므로, 복잡한 데이터베이스 설계나 추가적인 IV 관리 로직이 필요하지 않음
    • 일관성: 모든 필요한 정보(암호문과 IV)가 하나의 데이터 블록으로 결합되므로, 복호화할 때 필요한 모든 정보가 함께 제공됨
    • 재사용 가능성: 데이터가 이동하거나 다른 시스템으로 전송될 때, 암호문과 IV가 함께 이동하므로 복호화 과정에서 누락되는 것을 방지할 수 있음
  • 단점

    • 약간의 오버헤드: 암호문에 IV를 추가하면 데이터의 길이가 늘어날 수 있음. 그러나 일반적으로 IV의 크기(예: 16바이트)는 상대적으로 작으므로 큰 문제가 되지 않음
  • 사용 사례

    • 웹 애플리케이션: HTTPS와 같은 프로토콜에서 암호화된 메시지와 함께 IV를 전송하는 경우
    • 파일 암호화: 암호화된 파일 자체에 IV를 포함시켜 저장하는 경우
    • 일반적인 API 암호화: API 응답이나 요청에서 암호화된 데이터와 IV를 함께 반환하는 경우
  • 코드

    • 암호화할 때 : IV를 생성하고, 암호화된 데이터 앞에 IV를 추가하여 저장함

      public String encrypt(String plainText, SecretKey key) throws Exception {
          byte[] ivBytes = new byte[16];
          new SecureRandom().nextBytes(ivBytes); // 새로운 IV 생성
          IvParameterSpec iv = new IvParameterSpec(ivBytes);
      
          Cipher cipher = Cipher.getInstance(TRANSFORMATION);
          cipher.init(Cipher.ENCRYPT_MODE, key, iv);
          byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
      
          // IV와 암호문을 결합하여 하나의 문자열로 저장
          byte[] ivAndCipherText = new byte[ivBytes.length + encryptedBytes.length];
          System.arraycopy(ivBytes, 0, ivAndCipherText, 0, ivBytes.length);
          System.arraycopy(encryptedBytes, 0, ivAndCipherText, ivBytes.length, encryptedBytes.length);
      
          return Base64.getEncoder().encodeToString(ivAndCipherText);
      }
    • 복호화할 때 : 암호문에서 IV를 추출하고, 이를 사용하여 복호화

      public String decrypt(String cipherText, SecretKey key) throws Exception {
          byte[] ivAndCipherText = Base64.getDecoder().decode(cipherText);
      
          // IV를 추출
          byte[] ivBytes = new byte[16];
          System.arraycopy(ivAndCipherText, 0, ivBytes, 0, ivBytes.length);
          IvParameterSpec iv = new IvParameterSpec(ivBytes);
      
          // 암호문 추출
          byte[] encryptedBytes = new byte[ivAndCipherText.length - ivBytes.length];
          System.arraycopy(ivAndCipherText, ivBytes.length, encryptedBytes, 0, encryptedBytes.length);
      
          Cipher cipher = Cipher.getInstance(TRANSFORMATION);
          cipher.init(Cipher.DECRYPT_MODE, key, iv);
          byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
      
          return new String(decryptedBytes);
      }
  1. IV를 별도로 저장하는 방법
  • 설명

    • 이 방법에서는 IV를 암호문과 별도로 저장하거나 전송
    • 데이터베이스에 별도의 컬럼으로 IV를 저장하거나, 암호화된 데이터와는 독립적으로 IV를 관리
  • 장점

    • 유연성: IV를 별도로 관리하면, 암호문과 IV를 분리하여 저장하거나 전송할 수 있는 유연성을 제공
    • 효율성: 암호문 자체에 추가적인 데이터(IV)를 포함시키지 않으므로, 암호문 자체의 크기를 줄일 수 있음
  • 단점

    • 관리의 **복잡성**: IV를 별도로 관리해야 하므로, 추가적인 데이터베이스 설계와 관리 로직이 필요
    • 동기화 문제: 암호문과 IV가 분리되어 저장될 경우, 복호화 시 정확히 일치하는 IV를 찾는 데 오류가 발생할 가능성이 있음
  • 사용 사례

    • 고급 보안 애플리케이션: 매우 정밀한 데이터 관리가 필요한 경우, 암호문과 IV를 분리하여 관리
    • 분산 시스템: 여러 서버 또는 시스템에서 데이터를 처리할 때, IV를 별도로 관리하여 보안을 강화할 수 있음
  • 코드

    • 암호화할 때 : 새로운 IV를 생성하고, 암호화된 데이터와 함께 DB에 저장

      public void saveEncryptedData(int userId, String plainText) throws Exception {
          SecretKey secretKey = getFixedKey(); // 고정된 비밀키
          byte[] ivBytes = new byte[16];
          new SecureRandom().nextBytes(ivBytes); // 새로운 IV 생성
          IvParameterSpec iv = new IvParameterSpec(ivBytes);
      
          // 데이터 암호화
          String encryptedData = encrypt(plainText, secretKey, iv);
      
          // DB에 암호화된 데이터와 IV를 저장
          String sql = "INSERT INTO user_data (user_id, encrypted_data, iv) VALUES (?, ?, ?)";
          try (PreparedStatement ps = connection.prepareStatement(sql)) {
              ps.setInt(1, userId);
              ps.setString(2, encryptedData);
              ps.setBytes(3, ivBytes);
              ps.executeUpdate();
          }
      }
    • 복호화할 때 : 해당 데이터를 복호화할 때, DB에서 저장된 IV와 암호화된 데이터를 함께 불러와 복호화

      public String loadDecryptedData(int userId) throws Exception {
          String sql = "SELECT encrypted_data, iv FROM user_data WHERE user_id = ?";
          try (PreparedStatement ps = connection.prepareStatement(sql)) {
              ps.setInt(1, userId);
              try (ResultSet rs = ps.executeQuery()) {
                  if (rs.next()) {
                      String encryptedData = rs.getString("encrypted_data");
                      byte[] ivBytes = rs.getBytes("iv");
      
                      IvParameterSpec iv = new IvParameterSpec(ivBytes);
                      SecretKey secretKey = getFixedKey(); // 고정된 비밀키
      
                      return decrypt(encryptedData, secretKey, iv);
                  }
              }
          }
          return null;
      }
    • DB 테이블 구조

      CREATE TABLE user_data (
          user_id INT PRIMARY KEY,
          encrypted_data TEXT,  // 암호화된 데이터를 저장
          iv VARBINARY(16)  // 초기화 벡터를 저장, 16바이트 고정 길이
      );

실무에서는 IV를 암호문에 포함하는 방법이 더 많이 아용되는 것으로 보임 → 암호화된 데이터와 IV를 함께 관리할 수 있어 편리하고, 복호화 과정에서 필요한 정보를 한 곳에서 쉽게 접근할 수 있기 때문으로 생각됨

IV를 별도로 저장하는 방법은 특정한 요구 사항이 있을 때나 매우 정밀한 보안 관리가 필요한 경우에 사용되지만, 관리의 복잡성 때문에 일반적으로는 덜 사용되는 것으로 생각됨

부록2. 해싱(Hashing)과 암호화(Encryption)의 차이점

Hashing 의 특징

  • 일방향성 : 해시 함수는 입력 값을 고정된 크기의 해시 코드로 변환하며, 이를 역으로 원래 데이터를 복구할 수 없음
  • 고정 길이 출력 : 입력 데이터의 길이와 상관없이 항상 고정된 길이의 해시 값을 출력
  • 충돌 회피 : 서로 다른 입력이 동일한 해시 값을 가지지 않도록 설계되어 있음
  • 빠른 연산 : 해시 함수는 빠른 속도를 자랑하며 대량의 데이터 처리에 적합, Time Complexity O(1)

암호화와 해싱의 차이점

특성해싱암호화
목적데이터 무결성 검증, 빠른 검색, 비밀번호 저장데이터 보호 및 기밀성 유지
복호화 가능 여부불가능가능
일방향성있음없음
출력 길이고정 길이가변 길이

해싱의 사용 사례

  • 데이터 무결성 검증
    • 파일 무결성 체크
      • 파일의 해시 값을 미리 계산하여 저장해두고, 이후에 파일이 변경되었는지 확인하기 위해 다시 해시 값을 계산하여 비교할 수 있음
    • 메시지 무결성 검증
      • 네트워크 통신에서 메시지 다이제스트(MD5, SHA-256 등)를 생성하여 메시지와 함께 전송하고, 수신 측에서 동일한 해시 알고리즘으로 메시지의 해시 값을 계산해 비교
  • 비밀번호 저장
    • 비밀번호 해싱
      • 사용자 비밀번호를 데이터베이스에 저장할 때, 해시 함수를 사용하여 비밀번호를 해시 처리한 후 저장
      • DB가 해킹당하더라도 실제 비밀번호를 알 수 없게됨
      • 사용자가 로그인할 때 입력한 비밀번호를 같은 해시 함수로 변환해 저장된 해시 값과 비교
      • 일반적으로 해시 값에 추가적인 안전성을 위해 솔트(Salt)를 추가하여 해시를 생성
  • 해시 테이블
    • 빠른 데이터 검색 : 키-값 쌍의 데이터를 저장하고 검색하는 데 사용
    • 캐싱 시스템 : 해시 테이블은 메모리 캐싱 시스템에서도 사용됨
  • 디지털 서명 및 인증서
    • 디지털 서명
      • 서명할 데이터의 해시 값을 계산하고, 그 해시 값을 개인 키로 암호화하여 서명
      • 수신 측은 공개 키로 서명을 복호화하여 해시 값을 얻고, 동일한 해시 함수를 사용해 데이터의 해시 값을 계산하여 비교
    • 디지털 인증서 : SSL/TLS 인증서와 같은 디지털 인증서에서도 해시 함수가 사용됨, 인증서의 내용을 해시한 값을 사용하여 인증서의 무결성을 검증

부록 3. AES 를 사용할 때 Base64 디코딩을 사용하는 이유

디코딩(Decoding) 이란

  • 복호화 가능: 디코딩은 일반적으로 인코딩된 데이터를 원래의 형식으로 복원하는 과정입니다. 인코딩이 가역적(되돌릴 수 있는)이라는 점에서 해싱과 다릅니다.
  • 데이터 복구: 디코딩의 목적은 인코딩된 데이터를 원래의 형식으로 되돌리는 것입니다. 예를 들어, Base64로 인코딩된 문자열을 디코딩하면 원래의 바이너리 데이터나 텍스트로 복구할 수 있습니다.
  • 인코딩/디코딩: 인코딩은 데이터를 특정 형식으로 변환하는 것이며, 디코딩은 그 데이터를 다시 원래의 형식으로 되돌리는 것입니다. 이는 데이터를 전송하거나 저장할 때 특정 형식이 요구될 때 사용됩니다.
  • 예시: Base64, URL 인코딩, UTF-8 등 다양한 인코딩 방식이 있으며, 이들의 디코딩 과정도 가능합니다.

사용 이유

  • 이진 데이터를 텍스트로 변환
    • AES 암호화의 결과는 텍스트가 아니라 이진 데이터로 사람이 읽을 수 없으며, 이메일 전송, URL, JSON, XML, 로그 파일 등 텍스트 기반의 시스템에서 제대로 처리되지 않을 수 있음
    • Base64 인코딩은 이 이진 데이터를 안전한 ASCII 텍스트로 변환하여 이러한 시스템에서 안전하게 다룸
  • 전송 과정에서 데이터 손상 방지
    • 이진 데이터는 전송 과정에서 텍스트 기반의 프로토콜(예: 이메일, HTTP, 웹 소켓 등)을 통해 전송될 때 손상될 수 있는데, Base64로 인코딩된 데이터는 이러한 문제를 피할 수 있음
    • 예를 들어, 이메일 전송 시 SMTP 프로토콜은 7비트 ASCII 텍스트만 안전하게 전송할 수 있기 때문에, 이진 데이터를 Base64로 인코딩하여 텍스트로 전환해 전송
  • 플랫폼 독립성
    • Base64 인코딩된 텍스트는 어떤 시스템에서든 동일하게 인식되고 처리될 수 있음
  • 데이터 저장 및 출력의 용이성
    • AES로 암호화된 이진 데이터를 로그 파일이나 데이터베이스에 저장할 때, 이진 데이터를 직접 저장하면 데이터 손상 또는 오류가 발생할 수 있는데, Base64로 인코딩하면 이러한 데이터를 안전하게 저장하고 나중에 디코딩하여 원래의 이진 데이터로 복원할 수 있음

결론

AES와 RSA는 각각의 장점과 단점을 가지고 있으며, 서로 상호 보완적인 암호화 기술이다. AES는 대칭 키 암호화 방식으로, 대량의 데이터를 빠르게 암호화하는 데 적합하며, 기밀성과 성능 측면에서 우수한 선택이다. 반면에, RSA는 비대칭 키 암호화 방식으로, 작은 데이터나 세션 키의 안전한 전송, 그리고 디지털 서명과 같은 신뢰성 보장이 필요한 상황에서 중요한 역할을 한다.

두 기술은 각각의 강점을 발휘하여, 현대 암호화 시스템에서 함께 사용된다. 예를 들어, SSL/TLS 프로토콜에서는 RSA를 사용하여 초기 세션 키를 안전하게 교환하고, 이후의 데이터 전송은 AES를 사용하여 빠르게 암호화하는 방식이 채택된다. 이와 같은 방식으로, AES와 RSA는 보안과 성능을 모두 만족시키는 강력한 암호화 체계를 구축하는 데 필수적인 요소이다.

따라서, AES와 RSA는 단독으로 사용되기보다는 서로 보완하며 사용될 때, 더욱 안전하고 효율적인 암호화 솔루션을 제공한다. 이 둘의 조화는 현대의 보안 시스템이 직면한 다양한 요구 사항을 충족시키는 데 중요한 역할을 한다.

profile
소프트웨어 엔지니어

0개의 댓글