이번에 애플 로그인 로직을 추가하면서 애플 토큰의 암호화 및 테스트를 구현하려고 합니다.
애플 로그인 구현은 나중에 정리해서 올릴게용
AWS IAM 계정 생성
위의 링크에서 IAM 계정 생성 후 accessKey 생성 후 다운로드까지 완료해주세요
애플 KMS 콘솔
뒤에 숫자는 키의 비트를 나타냅니다. 비트 수가 커질 수록 보안성이 더 높다고 합니다.
이때 왜 비대칭키인 RSA를 사용했을 까요?
gpt가 해싱 알고리즘과 RSA의 키 차이를 잘 정리해줬어요. 해싱은 암호화 후 복호화가 불가능합니다. 그렇기 때문에 키 암호화를 사용해야돼요.
RSA
이때 대칭키와 비대칭키 중 비대칭키를 사용한 이유는 보안에 더 적합하다고 생각했기 때문입니다.
대칭키는 암호화와 복호화에 모두 같은 키를 사용합니다.
비대칭키는 공개키와 개인키를 생성해서, 암호호와 복호화에 각각 다른 키를 사용합니다.
별칭 기입 완료 후 키 관리 권한 정의에서 위에서 생성한 IAM 계정 선택
키 사용 권한 정의에서 위에서 생성한 IAM 계정 선택
키 정책 확인 후 완료 버튼 클릭
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")
#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을 복사해주세요!
// 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에서 정보를 설정해줬습니다.
// 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 변환 로직에 차이가 있습니다.
// 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의 암호화, 복호화 메서드를 활용하여 애플 로그인 시 받은 애플 토큰을 데이터베이스에 암호화하여 저장합니다. 추후 회원 탈퇴 시 이 토큰이 필요하면 복호화하여 사용합니다.
애플 로그인 및 탈퇴 코드는 이후 포스트에서 자세히 다루겠습니다.