AWS KMS 키의 원리와 사용
회사에서 위치정보사업자를 준비하면서 단말 간 위치 데이터 전송을 암호화해야 하는 필요성이 생겼다. 원래도 암호화키로 암호화를 하고 있었지만 아래와 같이 추가적인 요구사항이 있었다.
- 암호화 Key 자동 관리 및 업데이트
- 키 자동 로테이션
- 키 접근 기록 유지 (감사 목적)
- 사용자별, 기기별 키 접근 제한 관리
이러한 요구사항을 AWS KMS 로 해결할 수 있겠다고 생각해서 찾아봤다. KMS는 암호화 키의 생성, 저장, 관리, 사용에 대한 접근 통제를 중앙에서 관리할 수 있게 해주는 서비스다.
하지만 KMS 를 처음 사용했을 때는, Key 가 요청할 때마다 달라져서 이게 대칭키로써 사용할 수 있는 것인가라는 의문이 들었다. 그래서 KMS 원리에 대한 이번 글을 정리하였다. 이번 글에서는 주로 AWS KMS의 핵심 구조를 **KEK(Key Encryption Key)**와 **DEK(Data Encryption Key)**라는 두 가지 키 관점에서 설명한다. 그리고GenerateDataKey API를 기반으로 한 봉투 암호화(Envelope Encryption)의 전체 흐름을 그림과 살펴본다.
1. AWS KMS란 무엇인가
간단하게 AWS KMS가 어떤 서비스인지 먼저 짚고 넘어가겠다. AWS Key Management Service 는 암호화 키를 손쉽게 생성하고 제어할 수 있게 해주는 관리형 서비스이다. KMS를 사용하면 애플리케이션에서 사용하는 암호화 키의 사용 정책을 중앙에서 관리하고 감사할 수 있다.
KMS의 주요 특징은 다음과 같다.
- 중앙 집중식 키 관리: 모든 암호화 키를 한곳에서 생성, 관리, 삭제하며 일관된 정책을 적용할 수 있다.
- 강력한 보안 및 규정 준수: FIPS 140-2 검증을 받은 하드웨어 보안 모듈(HSM)을 사용하여 키를 보호한다. 이를 통해 PCI-DSS, HIPAA 등 다양한 규정 준수 요구사항을 충족할 수 있다.
- AWS 서비스와의 통합: S3, EBS, RDS 등 다양한 AWS 서비스와 긴밀하게 통합되어 있어, 해당 서비스에 저장된 데이터를 손쉽게 암호화할 수 있다.
- 세분화된 접근 제어: AWS IAM(Identity and Access Management) 및 키 정책을 사용하여 어떤 사용자나 역할이 어떤 키를 어떤 조건에서 사용할 수 있는지 정밀하게 제어할 수 있다.
- 감사 추적: 모든 키 사용 기록은 AWS CloudTrail에 로그로 남는다. 누가, 언제, 어떤 리소스에 대해 키를 사용했는지 추적할 수 있어 보안 감사 및 사고 대응에 유리하다.
결론적으로 KMS는 단순히 키를 저장하는 장소가 아니라, 키의 전체 생명주기를 안전하게 통제하고 감사하는 플랫폼이라고 이해해야 한다.
2. KMS의 키는 사실 2종류다 - KEK와 DEK
KMS 원리의 첫번째는, KMS가 다루는 키가 단일 계층이 아니라 **두 개의 계층(KEK와 DEK)**으로 이루어져 있다는 점을 아는 것이다. (나도 많이 헷갈렸던 부분이다.)
2.1 KEK (Key Encryption Key) - 마스터 키 역할
우리가 흔히 AWS 관리 콘솔에서 "고객 관리형 키(CMK)" 또는 "KMS 키"라는 이름으로 생성하는 것이 바로 KEK에 해당한다. 이 키는 KMS 내부의 HSM에 안전하게 저장되며, 절대 평문 형태로 외부로 노출되지 않는다.
- 역할: 다른 키(DEK)를 암호화하거나 복호화하는 '마스터 키'의 역할을 수행
- 관리 주체: AWS KMS가 생성하고 보호하며, 사용자는 이 키에 대한 사용 권한만을 제어
- 예시: 콘솔에서 생성한
alias/my-app-key같은 별칭으로 식별되는 키
KEK는 데이터를 직접 암호화하는 데 사용할 수도 있지만(Encrypt/Decrypt API), 대용량 데이터나 빈번한 암호화 작업에는 성능 및 비용 문제로 잘 사용되지 않는다. 실전에서는 주로 아래에서 설명할 DEK를 감싸는(wrapping) 용도로 사용된다.
2.2 DEK (Data Encryption Key) - 실제 데이터 암호화 키
위치 정보, 로그, 개인 정보와 같은 실제 애플리케이션 페이로드를 암호화하는 데 사용되는 키가 바로 DEK 다. 일반적으로 AES-256 같은 강력한 대칭키 알고리즘을 사용한다.
- 역할: 실제 데이터를 빠르고 효율적으로 암호화하고 복호화
- 생성 및 수명: 보통 데이터 암호화가 필요할 때마다 매번 새로 생성되거나, 성능 최적화를 위해 아주 짧은 시간 동안만 재사용(캐시)된다. 일회성 키에 가깝다.
- 저장: DEK 자체는 매우 민감하므로 평문 형태로 디스크에 저장하거나 네트워크로 전송해서는 안 된다.
이 두 가지 키의 관계를 정리하면 다음과 같다.
실제 데이터는 DEK를 사용해 애플리케이션 레벨에서 빠르게 암호화하고, 사용한 DEK는 **KEK(KMS 키)**로 안전하게 암호화하여 데이터와 함께 저장하거나 전송한다.
이것이 바로 KMS를 활용하는 가장 대표적인 패턴인 '봉투 암호화(Envelope Encryption)'의 핵심 아이디어다.
| 구분 | KEK (Key Encryption Key) | DEK (Data Encryption Key) |
|---|---|---|
| 별칭 | KMS 키, 마스터 키, 고객 관리형 키(CMK) | 데이터 키, 봉투 키 |
| 주요 역할 | 데이터 키(DEK)를 암호화 및 복호화 | 실제 페이로드(데이터)를 암호화 및 복호화 |
| 저장 위치 | AWS KMS 내부의 HSM (외부 노출 불가) | 메모리에서 사용 후 폐기, 암호화된 형태로 데이터와 함께 저장 |
| 생명 주기 | 사용자가 생성/삭제, 반영구적 (로테이션 가능) | 필요시마다 생성, 매우 짧거나 일회성 |
| 주요 API | CreateKey, GenerateDataKey, Decrypt | 애플리케이션의 암호화 라이브러리 (예: AES) |
2. GenerateDataKey가 하는 일
"DEK를 만들고, KEK로 포장해서 같이 준다"
GenerateDataKey는 봉투 암호화를 위해 특별히 설계된 KMS의 핵심 API 이다. 이 API를 호출하면 KMS는 단순히 키 하나만 돌려주는 것이 아니라, 하나의 작업을 위해 필요한 두 가지 형태의 키를 동시에 생성해서 전달한다.
GenerateDataKey를 호출하면(이때 어떤 KEK를 사용할지 KeyId로 지정), 응답으로 다음 두 가지 중요한 정보를 받게 된다.
Plaintext: 랜덤하게 생성된 새로운 DEK의 평문. 바이트 배열 형태이며, 이 키를 사용해 송신 측 애플리케이션에서 즉시 데이터를 암호화해야 한다. 사용 후에는 즉시 메모리에서 삭제하는 것이 안전하다.CiphertextBlob: 위와 동일한 DEK를, 요청 시 지정한 KEK로 암호화한 결과물이다. 이 암호화된 DEK는 평문 데이터와 달리 디스크에 저장하거나 네트워크로 전송해도 안전하다. 나중에 수신 측에서 데이터를 복호화할 때 이 값을 KMS로 보내 원본 DEK를 얻는 데 사용된다.
즉, GenerateDataKey API 호출 한 번으로 일어나는 일을 요약하면 다음과 같다.
- KMS가 암호학적으로 안전한 **랜덤 데이터 키(DEK)**를 매번 새로 만든다. (이것이 API를 호출할 때마다 키 값이 달라지는 이유다.)
- 그리고 그 DEK를 사용자가 지정한 **고정적인 KMS 키(KEK)**로 감싸서
CiphertextBlob형태로 안전하게 포장해준다.
이처럼 GenerateDataKey는 봉투 암호화의 '봉투를 만드는' 과정을 원자적으로 처리해주는 API 이다.
3. 전체 흐름 - 봉투 암호화(Envelope Encryption)
이제 KEK, DEK, GenerateDataKey 개념을 모두 합쳐 전체 데이터 암호화 및 복호화 흐름을 살펴보자. 이 흐름은 데이터를 암호화하는 '송신 서버'와 이를 받아 복호화하는 '수신 서버'의 상호작용으로 구성된다.
송신 서버 (암호화 측)
- KMS 호출:
GenerateDataKeyAPI를 호출한다. 이때 사용할 마스터 키(KEK)의 ID(예:alias/location-key)를 파라미터로 전달한다. - 키 수신: KMS로부터 응답으로
{plaintext DEK, encrypted DEK}한 쌍을 받는다. - 로컬 암호화: 받은
plaintext DEK를 사용하여 실제 페이로드(payload)를 AES와 같은 대칭키 알고리즘으로 암호화한다. 이때 매번 새로운 IV(Initialization Vector) 또는 Nonce를 생성하는 것이 매우 중요하다. (패턴이 반복되면 해당 값이 노출될 수 있다.) - 데이터 구성 및 전송: 수신 측에 다음 세 가지 정보를 함께 전송(또는 저장)한다.
- 암호화된 페이로드 (ciphertext)
- 암호화된 데이터 키 (
encrypted_DEK, 즉CiphertextBlob) - 암호화에 사용된 IV/Nonce
- 평문 키 폐기: 암호화가 끝나면 메모리에 있던
plaintext DEK는 즉시 폐기해야 한다.
수신 서버 (복호화 측)
- KMS 호출: 전달받은
encrypted_DEK를 파라미터로 하여 KMS의DecryptAPI를 호출한다. - 키 수신: KMS는
encrypted_DEK를 내부의 KEK로 복호화하여 원본plaintext DEK를 응답으로 돌려준다. 이 과정에서 수신 서버가 해당 KEK에 대한kms:Decrypt권한이 있는지 확인한다. - 로컬 복호화: KMS로부터 받은
plaintext DEK와 함께 전달받은 IV/Nonce를 사용하여 암호화된 페이로드를 복호화한다. - 평문 키 폐기: 복호화가 끝나면 메모리에 있던
plaintext DEK는 즉시 폐기해야 한다.
이 흐름의 핵심 포인트는 다음과 같다.
평문 DEK(
Plaintext)는 절대 네트워크로 공유되거나 디스크에 저장되지 않아야 한다. 저장되거나 전송될 수 있는 것은 오직 KEK로 안전하게 감싸진encrypted_DEK뿐이다.
이를 통해 실제 데이터를 암호화하는 키(DEK)의 노출 위험을 최소화하면서, 중앙의 KMS를 통해 누가 그 키에 접근할 수 있는지를 통제할 수 있다.
4. 그럼 "키 로테이션"은 무엇을 바꾸는가?
키 로테이션도 내가 가장 많이 헷갈려 하는 개념 중 하나였다. "GenerateDataKey를 호출할 때마다 키가 바뀌는데, 로테이션은 또 뭘 바꾸는 건가?" 하는 의문이 들었다.
GenerateDataKey로 매번 바뀌는 것은 "DEK"다 앞서 설명했듯이, DEK는 본질적으로 '데이터 암호화용 일회성 키'에 가깝다. 보안 강화를 위해 매번 새로운 데이터를 암호화할 때마다 새로운 DEK를 생성하는 것이 권장되므로, API 호출 시마다 값이 달라지는 것은 정상적인 동작이다.
로테이션이 바꾸는 것은 "KEK의 backing key 버전"이다. 우리가 KMS 키라고 부르는 KEK는 사실 사용자에게 보이는 논리적인 식별자(ID, ARN, 별칭)일 뿐, 그 내부에는 실제 암호화 작업을 수행하는 '백킹 키(backing key)' 또는 '키 물질(key material)'이라는 것이 존재한다. 키 로테이션은 바로 이 내부의 백킹 키를 새로운 것으로 교체하는 작업이다.
로테이션 과정을 단계별로 살펴보자면,
- 처음 KEK를 생성하면, KMS는 이 KEK를 위한 첫 번째 백킹 키(버전 A라고 합시다)를 만든다. 이제 이 KEK는 버전 A를 '현재(current)' 버전으로 사용한다.
- 이후
GenerateDataKey를 호출하면, KMS는 새로운 DEK를 생성하고 이를 '현재' 백킹 키인 버전 A로 감싸서CiphertextBlob을 만든다. - 일정 시간(보통 1년)이 지나 자동 로테이션이 실행되면, KMS는 새로운 백킹 키(버전 B)를 생성하고, KEK의 '현재' 버전을 B로 변경한다.
- 중요한 점은, 이전 버전인 A를 삭제하지 않고 KMS 내부에 비활성 상태로 보관한다는 것이다.
- 이제부터 새로
GenerateDataKey를 호출하면, KMS는 새로운 DEK를 최신 백킹 키인 버전 B로 감싸서CiphertextBlob을 만든다. - 과거에 버전 A로 암호화되었던
CiphertextBlob에 대해Decrypt요청이 들어오면, KMS는 해당 암호문에 포함된 메타데이터를 보고 자동으로 이전 버전인 A를 찾아 복호화를 수행해준다.
결과적으로 사용자는 아무것도 신경 쓸 필요가 없다. 로테이션이 발생하더라도 과거에 암호화된 데이터를 문제없이 복호화할 수 있다. 로테이션은 미래에 생성될 암호문의 보안성을 강화하기 위한 조치이며, 하위 호환성은 KMS가 알아서 보장해준다.
4줄 요약
- KEK는 내부에 버전 관리되는 백킹 키(A)를 가진다.
GenerateDataKey는 랜덤 DEK를 만들고 현재 백킹 키(A)로 감싼다.- 로테이션이 되면 새 백킹 키(B)가 '현재' 버전이 되고, A는 이전 버전으로 남는다.
- 새 암호화는 B로 수행되고, 과거 A로 암호화된 데이터도 여전히 복호화 가능하다.
5. 암호화할 때마다 KMS를 호출해야 하나?
어떤 암호화 패턴을 사용하느냐에 따라 다르며, 크게 두 가지 패턴으로 나누어 볼 수 있다.
5.1 KMS로 페이로드를 직접 암호화하는 경우
KMS의 Encrypt API를 사용하여 데이터 페이로드 자체를 직접 암호화하는 방식이 있다. 이 경우, 암호화와 복호화가 필요할 때마다 매번 KMS API를 호출해야 한다.
장점: 구현이 매우 간단하다.
단점
- 데이터 크기 제한: KMS
EncryptAPI는 한 번에 4KB 이하의 데이터만 처리할 수 있다. 대용량 파일 암호화에는 부적합하다.
- 데이터 크기 제한: KMS
비용 및 지연: 모든 암/복호화 요청이 네트워크를 통해 KMS로 전달되므로, API 호출 비용과 네트워크 지연 시간이 발생한다. 트래픽이 많은 시스템에서는 부담이 될 수 있다.
이 방식은 설정 파일의 비밀번호처럼 크기가 작고 자주 바뀌지 않는 소량의 데이터를 암호화할 때 적합하다.
5.2 봉투 암호화 (GenerateDataKey + 로컬 AES)를 사용하는 경우
이 글에서 계속 설명한 패턴이다. 실제 데이터 암호화는 애플리케이션에서 로컬 암호화 라이브러리(예: OpenSSL, JCE)를 사용해 직접 처리한다.
장점
- 성능: 실제 데이터 암호화는 메모리 내에서 매우 빠르게 처리되므로 성능이 좋다. KMS 호출은 키를 얻어올 때만 필요하다.
- 대용량 데이터 처리: 데이터 크기에 제한이 없다.
- 비용 효율성: KMS API 호출 횟수를 크게 줄일 수 있다.
단점: 직접 암호화 로직을 구현해야 하므로 상대적으로 복잡하다.
대부분의 실시간 데이터 처리 및 대용량 데이터 암호화에서는 봉투 암호화 방식이 표준으로 사용된다.
추가 최적화: 데이터 키 캐싱(Data Key Caching)
봉투 암호화 방식에서도 극도의 성능이 요구되는 경우, GenerateDataKey로 발급받은 DEK를 짧은 시간 동안 메모리에 캐시하여 재사용하는 전략을 고려할 수 있다. 예를 들어, "최대 30초 또는 1,000개의 메시지를 암호화할 때까지 같은 DEK를 재사용한다"와 같은 정책을 설정할 수 있다.
이 방식은 KMS API 호출을 줄여주지만, 캐시 유지 시간이 길어질수록 평문 DEK가 메모리에 머무는 시간이 늘어나 보안상 위험이 커질 수 있다. 따라서 캐시의 TTL(Time-To-Live)이나 사용 횟수/용량 제한을 신중하게 설계하여 보안과 성능 사이의 트레이드오프를 조절해야 한다.
6. 운영에서 확인해야 할 것
KMS를 실제 운영 환경에서 사용할 때 보안 사고를 방지하고 안정성을 높이기 위해 반드시 점검해야 할 몇 가지 포인트가 있다.
6.1 IV/Nonce는 절대 고정 값을 사용하면 안 된다
AES와 같은 블록 암호 알고리즘을 운영 모드(예: CBC, GCM)와 함께 사용할 때 IV(Initialization Vector) 또는 Nonce(Number used once)가 필요하다. 특히 AES-GCM 모드에서 Nonce를 재사용하는 것은 암호화 키가 노출될 수 있는 치명적인 보안 취약점으로 이어진다. 매 암호화마다 암호학적으로 안전한 랜덤 값을 생성하여 IV/Nonce로 사용하고, 이를 암호문과 함께 저장/전송해야 한다.
6.2 평문 DEK는 가능한 짧게 사용하고 폐기하자
GenerateDataKey로 받은 평문 DEK는 매우 민감한 정보다. 필요한 암호화/복호화 작업을 마치는 즉시 메모리에서 안전하게 제거(예: 0으로 덮어쓰기)해야 한다. 위에서 언급한 키 캐싱을 사용하더라도, 캐시의 생명주기를 가능한 한 짧게 설정하여 평문 키의 노출 시간을 최소화해야 한다.
6.3 접근 통제는 KMS Decrypt 권한으로 강제된다
봉투 암호화 모델의 가장 큰 장점 중 하나다. 수신 서버가 암호화된 데이터를 복호화하려면, 반드시 KMS에 접근하여 암호화된 DEK(CiphertextBlob)를 복호화해야 한다. 이 말은 즉, 해당 KEK에 대한 kms:Decrypt IAM 권한이 있는 주체(사용자, 역할)만이 최종적으로 원본 데이터에 접근할 수 있다는 의미다. "누가 데이터를 복호화할 수 있는가"에 대한 통제가 IAM 및 키 정책을 통해 중앙에서 명확하게 관리된다.
6.4 모든 키 사용 내역은 CloudTrail에 기록된다
GenerateDataKey, Encrypt, Decrypt 등 KMS 키를 사용하는 모든 API 호출은 AWS CloudTrail에 이벤트 로그로 기록된다. 이를 통해 "언제, 누가, 어떤 IP에서, 어떤 키를, 어떤 목적으로 사용했는지" 상세하게 추적할 수 있다. 이는 정기적인 보안 감사 요구사항을 충족하고, 만일의 보안 사고 발생 시 원인을 분석하고 대응하는 데 사용될 수 있다.
