본문 바로가기
Infra/보안

개인 정보 암호화 (AES - GCM)

by hongdor 2023. 5. 31.
728x90

2가지 타입의 암호화 기술

  1. 복호화 가능
    1. symmetric (대칭키)
      보내는 사람과 받는 사람이 같은 key를 가져야 한다.
      속도가 빠르다.
      key가 어느쪽이든 탈취당할 경우 위험하다.
    2. asymmetric (비대칭키)
      public key와 private key가 존재한다.
      두 key는 수학적으로 연관이 된 key다.
      public key는 누구나 사용 가능하고 private key는 메세지를 해독하는 수신자만 가지고 있는다.
  2. 복호화 불가능
    1. hashing
      데이터마다 고유한 해시를 생성한다.
      암호화된 데이터는 복원할 수 없다.
      그렇기 때문에 이전과 같은 데이터 였는지 검증하는 용도로만 사용할 수 있다.
      SHA, HMAC 등은 해시를 생성하는 방법들이다.

 

대표적인 암호화 기술

  • AES
    symmetric이다.
    세계적으로 가장 많이 사용되는 symmetric 암호화 알고리즘이다.
    미국과 다양한 단체에서 사용한다.
    128bit의 data를 암호화 한다.
    key는 128-bit 가 가장 효율적이지만 192, 256bit의 key도 사용된다 
    (data와 key를 혼동하면 안된다)
  • Triple DES
    symmetric이다.
    산업 현장에서 가장 많이 쓰였던 symmetric 였다.
    56-bit key를 사용한다.
    UNIX 비밀번호와 ATM PIN 등에 보통 사용된다.
    현재 일부는 더 안전한 AES로 전환되고 있는 추세이다.
  • RSA
    asymmetric 이다.
    인터넷 전송 데이터 암호화의 표준이다.
    대용량 데이터를 암호화 하는 경우 속도가 느려진다.

 

개인정보 암호화를 위한 기술 선택

  • AES 사용
    단일 application 에서 사용하므로 private key를 사용할 이유가 없다.
    symmetric 방법 중 Triple DES 보다 보안이 뛰어난 AES를 사용
  • 256bit key
    AES 의 128bit과 256bit key 사용 중 당연히 256bit더 안전하지만 둘다 충분히 안전하기 때문에 속도가 더 빠른 128bit을 사용할 것을 권장한다.
    256bit 가 128bit 보다 40% 시간이 더 소요된다.
    ex) 128bit 가 1초 걸린다면, 256bit 는 1.4초 소모
    하지만 좀더 안전한 256bit을 사용하는 것이 좋을 것 같다. (심리적 안정감 ㅎㅎ)
  • AES-CBC
    첫 128bit 단위 데이터는 IV와 xor 한다
    두번째 128bit 단위 데이터부터는 이전 데이터를 사용해서 xor한다
    결과적으로 데이터를 복호화 하려면 이전 128bit 단위 데이터를 알아야만 한다.
    이런 방법으로 128bit보다 큰 데이터를 함께 암호화할 수 있다.
    이렇게 128bit 한계를 뛰어넘어 블록들을 연계시켜 더 큰 데이터를 암호화 하는 것을 Block cipher mode of operation
    라고 하며 CBC 는 그 중 한 방법이다. 
  • AES-GCM vs AES-CBC
    암호화 과정을 이해하진 못했지만 GCM이 더 안전하고 CBC와 다르게 데이터 병렬 암호화 처리가 가능하여 빠르다고 한다.
  • Initialization vector(IV) 의 재사용 X
    재사용 하지 말아야함. unique해야 함
  • Initialization vector(IV)를 저장하는 방법
    IV와 Key가 있어야 복호화가 가능하므로 IV도 함께 저장해야 한다. IV는 노출되어도 된다.
    [IV][ciphertext] 방식으로 저장한다.
    IV : 1234
    ciphertext : 5678
    DB : 12345678
  • AES-GCM의 IV 길이
    96bit를 권장한다.
    추가적인 계산이 필요없는 길이 중 가장 긴 길이이기 때문이다.
  • Initialization vector(IV) 를 random 으로 사용해도 되는가?
    랜덤을 사용해도 되지만, 중복 가능성이 매우 낮은 랜덤을 사용해야 한다.
    The primary purpose of the IV is to be a nonce, that is, to be distinct for each invocation of the encryption operation for a fixed key. It is acceptable for the IV to be generated randomly, as long as the distinctness of the IV values for each key is highly likely
  • GCM Tag 길이
    128bit 이 선호 된다

아래는 Java Spring 구현코드

import jakarta.annotation.PostConstruct;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

@Component
public class EncryptionUtil {

    //    @Value("${encrypt.key}")
    private String key = "v8y/B?E(H+MbQeThWmZq3t6w9z$C&F)J";
    public static final int GCM_IV_LENGTH = 12;
    public static final int GCM_TAG_LENGTH = 128;
    private static final SecureRandom secureRandom = new SecureRandom();
    private SecretKeySpec keySpec;

    @PostConstruct
    private void initializeKeySpec() {
        this.keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
    }

    public String encrypt(String plainText) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            // Generate Random IV
            byte[] IV = new byte[GCM_IV_LENGTH];
            this.secureRandom.nextBytes(IV);

            // Create GCMParameterSpec
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, IV);

            // Initialize Cipher for ENCRYPT_MODE
            cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, gcmParameterSpec);

            // Perform Encryption
            byte[] cipherText = cipher.doFinal(plainText.getBytes());

            return Base64.encodeBase64String(combineBytes(IV, cipherText));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    byte[] combineBytes(byte[] a, byte[] b) throws IOException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(a);
        outputStream.write(b);

        byte[] result = outputStream.toByteArray();

        return result;
    }

    public String decrypt(String encrypted) {
        try {
            // Get Cipher Instance
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            // Separate IV and cipherText
            byte[] IV = Base64.decodeBase64(encrypted.substring(0, 16));
            byte[] cipherText = Base64.decodeBase64(encrypted.substring(16));

            // Create GCMParameterSpec
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, IV);

            // Initialize Cipher for DECRYPT_MODE
            cipher.init(Cipher.DECRYPT_MODE, this.keySpec, gcmParameterSpec);

            // Perform Decryption
            byte[] decryptedText = cipher.doFinal(cipherText);

            return new String(decryptedText);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}
728x90

'Infra > 보안' 카테고리의 다른 글

Oauth 2.0 (feat. Oauth 1.0)  (0) 2023.05.21
cookie vs localstorage  (0) 2023.02.01
SSL 이란?  (0) 2022.04.24

댓글