[크루잉] AWS KMS를 이용한 토큰 암호화 및 복호화(애플 토큰)

Elmo·2024년 11월 13일
0

이번에 애플 로그인 로직을 추가하면서 애플 토큰의 암호화 및 테스트를 구현하려고 합니다.
애플 로그인 구현은 나중에 정리해서 올릴게용

애플 토큰 암호화

KMS 키 생성

  1. AWS IAM 계정 생성
    위의 링크에서 IAM 계정 생성 후 accessKey 생성 후 다운로드까지 완료해주세요

  2. 애플 KMS 콘솔

뒤에 숫자는 키의 비트를 나타냅니다. 비트 수가 커질 수록 보안성이 더 높다고 합니다.

이때 왜 비대칭키인 RSA를 사용했을 까요?

gpt가 해싱 알고리즘과 RSA의 키 차이를 잘 정리해줬어요. 해싱은 암호화 후 복호화가 불가능합니다. 그렇기 때문에 키 암호화를 사용해야돼요.

RSA

  • 공개키는 누구나 사용할 수 있으며, 데이터를 암호화하는 데 사용됩니다.
  • 비밀키는 해당 공개키로 암호화된 데이터를 복호화하는 데 사용됩니다. 비밀키는 공개되지 않으며, 오직 키 소유자만 알고 있어야 합니다.
  • 키를 생성할 때는 소수를 이용해서 생성하고, 암호화 및 복호화 시 이 소수를 이용하여 mod를 이용합니다.

이때 대칭키와 비대칭키 중 비대칭키를 사용한 이유는 보안에 더 적합하다고 생각했기 때문입니다.

대칭키는 암호화와 복호화에 모두 같은 키를 사용합니다.

  • 암호화에 필요한 키의 길이가 비교적 짧고 계산이 간단합니다.
  • 다만 클라이언트와 서버가 통신할 때 같은 키를 가지고 있어야하므로 서버에서 클라이언트에게 키를 전송해야합니다. 이때 암호화가 제대로 돼있지 않으면 키가 노출될 수 있어요(보안성이 떨어짐)
  • DES, AES

비대칭키는 공개키와 개인키를 생성해서, 암호호와 복호화에 각각 다른 키를 사용합니다.

  • 대칭키보다 키 분배 및 관리가 쉬워요
  • 다만 키 길이가 상대적으로 길고 계산이 복잡해요 리소스가 더 필요해요.
  • 보안성이 더 좋습니다.
  • RSA
  1. 별칭 기입 완료 후 키 관리 권한 정의에서 위에서 생성한 IAM 계정 선택

  2. 키 사용 권한 정의에서 위에서 생성한 IAM 계정 선택

  3. 키 정책 확인 후 완료 버튼 클릭

  4. dependency 추가

// build.gradle
implementation("org.zalando:spring-cloud-config-aws-kms:5.1.2")
implementation("com.amazonaws:aws-java-sdk-core:1.11.1019")
implementation("com.amazonaws:aws-java-sdk-kms:1.11.1019")
implementation("com.amazonaws:jmespath-java:1.11.1019")
  1. application.properties 설정
#Access Key(IAM)
cloud.aws.credentials.accessKey=
cloud.aws.credentials.secretKey=
cloud.aws.stack.auto=false
#kms
aws.kms.key-id=
aws.kms.encryption-algorithm=RSAES_OAEP_SHA_256

각각 비어있는 요소에 해당하는 값을 넣어주세요.
이때 aws.kms.key-id는 kms콘솔 - 왼쪽 고객 관리형 키 메뉴 - 키id 클릭 - ARN을 복사해주세요!

  1. kms config 설정
// KmsConfig.java
package com.crewing.common.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.AWSKMSClientBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@RequiredArgsConstructor
@Configuration
public class KmsConfig {
    @Value("${cloud.aws.region.static}")
    private String AWS_REGION;
    @Value("${cloud.aws.credentials.accessKey}")
    private String AWS_CREDENTIALS_ACCESS_KEY;
    @Value("${cloud.aws.credentials.secretKey}")
    private String AWS_CREDENTIALS_SECRET_KEY;

    @Bean
    public AWSKMS amazonKMS() {
        BasicAWSCredentials awsCredit = new BasicAWSCredentials(AWS_CREDENTIALS_ACCESS_KEY, AWS_CREDENTIALS_SECRET_KEY);
        return AWSKMSClientBuilder.standard()
                .withRegion(AWS_REGION)
                .withCredentials(new AWSStaticCredentialsProvider(awsCredit))
                .build();
    }
}

@Value 어노테이션으로 properties에서 설정한 aws 정보를 기입하고, AWSKMS config에서 정보를 설정해줬습니다.

  1. KmsUtil 생성
// KmsUtil.java
package com.crewing.common.util;

import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;


@Slf4j
@Service
@RequiredArgsConstructor
public class KmsUtil {
    private final AWSKMS kmsClient;
    @Value("${aws.kms.key-id}")
    private String KEY_ID;
    // 암호화 메서드
    public String encrypt(String token) {
        try {
            EncryptRequest request = new EncryptRequest();
            request.withKeyId(KEY_ID);
            request.withPlaintext(ByteBuffer.wrap(token.getBytes(StandardCharsets.UTF_8)));
            request.withEncryptionAlgorithm(EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256);

            EncryptResult result = kmsClient.encrypt(request);
            ByteBuffer ciphertextBlob = result.getCiphertextBlob();

            return new String(Base64.encodeBase64(ciphertextBlob.array()));
        } catch (Exception e) {
            throw new RuntimeException("Encryption failed", e);
        }
    }

     // 복호화 메서드
    public String decrypt(String token) {
        try {

            DecryptRequest request = new DecryptRequest();
            request.withCiphertextBlob(ByteBuffer.wrap(Base64.decodeBase64(token)));
            request.withKeyId(KEY_ID);
            request.withEncryptionAlgorithm(EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256);
            ByteBuffer plainText = kmsClient.decrypt(request).getPlaintext();
            byte[] bytes = new byte[plainText.remaining()];
            plainText.get(bytes);
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("Decryption failed", e);
        }
    }


}

암호화 요청 객체를 생성해서 키 아이디, token 문자열을 UTF-8 인코딩 방식으로 바이트 배열 변환 후 ByteBuffer 객체로 래핑하여 전달, 암호화 알고리즘을 기입합니다.

그리고 kmsClient 객체를 이용하여 요청을 보내고 받은 암호화된 데이터를 Base64 인코딩을 이용하여 문자열로 변환합니다.

일반적인 인코딩 : 어떤 형식의 변환으로써 보통 사람이 입력한 데이터 -> 바이너리 데이터로 변환
Base64 인코딩 : 바이너리 데이터 -> 문자열로 변환

헷갈리지 마세용

암호화와 비슷한 흐름으로 복호화도 진행합니다. 다만 복호화할 때는 이미 Base64.encoding()으로 인코딩된 토큰이기 때문에 반대로 decoding으로 다시 바이너리 데이터로 변환 후 복호화 요청을 합니다.
복호화 결과는 바이트 버퍼 형태의 평문 데이터로 받기 때문에 String 변환 로직에 차이가 있습니다.

  1. Jnit5 테스트
// Test.java
    @Test
    @DisplayName("토큰 암호화 후 복호화 검증")
    void tokenDecryption_After_Encryption() throws Exception {
        String token = "dfjalkdsjflaskdfjlj.test.test.testestest"; // 임의의 토큰
        String encryption = kmsUtil.encrypt(token);
        String decryption = kmsUtil.decrypt(encryption);

        Assertions.assertThat(decryption).isEqualTo(token);
        System.out.println(encryption);
    }

임의의 토큰을 만들어 테스트한 결과 암호화, 복호화가 아주 잘됐습니다 ㅎ

이제 이 KmsUtil의 암호화, 복호화 메서드를 활용하여 애플 로그인 시 받은 애플 토큰을 데이터베이스에 암호화하여 저장합니다. 추후 회원 탈퇴 시 이 토큰이 필요하면 복호화하여 사용합니다.

애플 로그인 및 탈퇴 코드는 이후 포스트에서 자세히 다루겠습니다.

profile
엘모와 도지는 즐거워

0개의 댓글