[AWS] - KMS

오동훈·2022년 12월 14일
2

AWS Cloud

목록 보기
4/8

1. KMS란

KMS는 key Management Service의 약자로, 데이터 암호화 할 때 사용되는 암호화 Key를 안전하게 보관할 수 있도록 관리해주는 서비스입니다.

KMS는 크게 3가지 방식의 관리 서비스가 제공됩니다.
(방금 다시 확인해봤는데 1가지 추가됐네요ㅎㅎ)

  1. AWS managed key
  2. Customer managed key
  3. Custom key stores

1. AWS managed key

AWS 서비스들이 KMS를 통해 Key를 서비스 받는 것은 동일하나, 내부적으로 자동으로 일어나 사용자가 직접적으로 제어가 불가능하다는 점이 특이점입니다.

2. Customer managed key

사용자가 직접 Key를 생성 및 관리할 수 있으며, IAM을 통해 CMK에 대한 제어 권한을 부여 받아 사용할 수 있습니다. 그리고 이번 블로그에 포스팅할 주제입니다.

3. Custom key stores

AWS에서 제공하는 또다른 Key 관리형 서비스인 CloudHSM을 활용한 Key 관리 형태입니다. 간략하게 얘기하면 CloudHSM이 조금 더 강력한 형태의 보안 안정성을 제공한다고 이해하면 됩니다.

2. 동작 방식

KMS의 동작 방식을 그려보면 아래의 사진과 같습니다.

1. IAM 발급 및 적용

AWS KMS 서비스를 이용하기 위해서는 IAM 발급이 필수입니다. 발급 과정은 운영체제마다 다르지만 다음과 같습니다.

  1. IAM 엑세스 키 발급
  2. 발급받은 엑세스 키 및 시크릿 키, region 입력

window

  1. AWS 엑세스 키 발급 받고
  2. MSI 설치 관리자 설치
  3. cmd 열어서 aws configure 명령어 입력 -> 해결 방법 참조

해결 방법
1. 엑세스 키 발급
2. MSI 설치 관리자 설치

Linux & CentOS

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
 
aws --version

위와 같이 설치를 진행해주고 아래의 명령어로 IAM 발급 키들을 입력해주면 됩니다.

aws configure

만약 이게 안된다면 직접 경로로 들어가 실행하면 됩니다.

cd /usr/local/bin

sudo ./aws configure

2. KMS - CMK 생성

우선 KMS 서비스를 이용해 키 관리를 하고, 저장되어 있는 키를 이용해 암복호화를 한다고 이야기 했습니다.
그러기 위해서는 첫 번째로 KMS 서비스에 CMK(Customer Master Key)를 생성해줍니다.

1. AWS KMS 진입

AWS KMS 서비스에 들어가게 되면 다음과 같은 화면이 보입니다. 그러면 로그인을 진행해주고 화면 중앙에 주황색으로 보이는 AWS Key Management Service 시작하기를 클릭합니다. 그 후 오른쪽 상단에 키 생성 버튼을 클릭해줍니다.

2. 키 구성

그러면 다음과 같은 화면 창이 뜨게됩니다. 여기서 각자 원하는 타입의 키 유형과 키 사용을 선택을 진행해주면 됩니다. 저는 대칭키와 암호화 및 해독을 이용해보겠습니다.

여기서 고급 옵션을 보게 되면 앞서 얘기했던 3가지에 추가적으로 1가지의 키 구성 방법이 존재합니다. 이 부분도 원하는 타입으로 골라주면 됩니다.

3. 레이블 추가

간단히 키의 이름 적고 넘어가주면 됩니다.

4. 키 관리 권한 정의

키 관리 권한을 관리할 유저나 역할을 추가해주고 넘어가주면 됩니다.

5. 키 사용 권한 정의

이번에는 KMS 키를 사용할 수 있는 IAM 사용자 및 역할을 지정해주면 됩니다.

6. 검토

지금까지의 내용을 검토 후에 키 생성해주면 됩니다.

3. Data Key

Data Key는 위에서 만든 CMK로부터 GenerateDataKey 라는 함수를 통해 생성되는데, 이 때 생성되는 Data Key는 크게 두 가지 종류로 Plaintext data keyEncrypted data key가 있습니다.

4. 암호화

암호화를 하기 위해서는 Plaintext data key를 필요로 합니다. 암호화 과정은 AWS에서 제공하는 Encryption SDK를 사용하면 되고, 데이터를 암호화 한 이후에는 해당 Plaintext data key는 폐기처리합니다.

5. 복호화

복호화를 하기 위해서는 Plaintext data key가 필요합니다. 하지만 암호화 한 후 Plaintext data key는 폐기처리 되었습니다. 이 과정에서 다시 Plaintext data key를 얻어야 하는데, 방법은 다음과 같습니다.

Encrypted data key를 이용해 Plaintext data key를 가져올 수 있습니다.
이 때 data key를 복호화하는 과정에 CMK가 다시 사용되며, plaintext로 변환된 data key를 이용해 복호화를 진행해주면 됩니다.

6. 사용법

지금까지 설명한 내용들을 바탕으로 살펴보겠습니다.

우선 아래의 코드를 실행하기 위해 boto3, pycryptodomex 라이브러리가 필요하므로 설치를 진행해 줍니다.

pip install boto3
pip install pycryptodomex

1. 라이브러리와 변수 초기화

Boto3는 AWS에서 제공하는 Python용 SDK로 Low-level에 직접 접근할 수 있을 뿐 아니라 사용하기 쉬운 객체 지향 API를 제공합니다.

import base64
import boto3

from Crypto.Cipher import AES

client = boto3.client('kms')

2. Key Id 발급 및 Data Key 생성

Master Key를 이용하여 Lambda class 로드 시 암복호화에 필요한 Data key(Plain & Encrypt)를 초기화합니다.
generate_data_key를 이용해 plaintext_keyEncrypted_key를 추출합니다.

key_arn = {'arn'} # kms key arn 별도 입력 필요
message = 'i want to back to home'

data_key = client.generate_data_key(
    KeyId=key_arn,
    KeySpec='AES_256'
)

plaintext_key = data_key.get('Plaintext')
encrypted_key = data_key.get('CiphertextBlob')

3. 암호화 메소드

평문 Data Key(plainText_key)를 이용해 암호화 객체를 생성하고 문자열을 암호화하고, base64로 인코딩 해주면 됩니다.

BS = 16
salt = "{16자리의 임의의 문자 or 기호 조합}".encode()
pad = lambda s: s+(BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s: s[0:-s[-1]]

def encrypt(raw):
    raw = raw.encode('utf-8')
    raw = base64.b64encode(raw)
    raw = raw.decode('utf-8')
    raw = pad(raw).encode('utf-8')
    cipher = AES.new(plainText_key, AES.MODE_CBC, salt)
    data = base64.b64encode(salt + cipher.encrypt(raw))
    return data
    
encrypt_data = encrypt(message)   

# 암호화 사용 후 plainText_key는 메모리에서 날려줍니다.
plainText_key = Null

4. 복호화 메소드

복호화 메소드에서는 암호화 된 문자열과 Data Key(Encrypted_key)로 평문 Data Key(plaintext_key)를 반환 받아 암호화 역순으로 암호화된 문자열을 base64로 디코딩한 뒤 복호화를 진행합니다.

plaintext_key = client.decrypt(encrypted_key).get('Plaintext')

def decrypt_str(self, enc):
    if enc is None:
        data = None
    else:
        unpad = lambda s: s[0:-s[-1]]
        enc = str.encode(enc)
        enc = base64.b64decode(enc)
        iv = enc[:16]
        cipher = AES.new(self.plainText_key, AES.MODE_CBC, iv)
        enc = unpad(cipher.decrypt(enc[16:]))
        enc = base64.b64decode(enc)
        data = enc.decode('utf-8')
    return data

이렇게까지 진행하면 처음 텍스트와 마지막 텍스트가 같게 나옵니다. 암호화를 하게 되면 88자 정도의 길이로 DB에 저장해서 사용하기에도 큰 부담이 없습니다.

5. 의문

위의 방법을 토대로 작업하면 generate_data_key()를 실행할 때마다 plaintext_key, encrypted_key가 다르게 나오고 있습니다. 그렇다면 들어오는 데이터마다 키가 다르다면 암복호의 의미가 사라지게 됩니다. 그래서 generate_data_key()를 한 번만 사용하고 추출한 encrypted_key를 보관하면서 암복호화를 진행하는지 찾아봤는데 명확하게 나와있는 솔루션이 없는 거 같습니다. 만약 그렇게 사용한다면 로컬에 두고 사용하는 것과 크게 어떤게 다른지, 이중화를 한다는 거에 초점을 둬야 하는건지 잘 모르겠습니다.

GeneratDataKey - AWS Docs

7. 에러 모음

Error1. window You must specify a region.

해당 에러는 로컬 시스템에 AWS CLI를 처음 설치한 경우 아래 주어진 명령을 사용해 AWS 자격 증명을 구성해야 합니다.

  1. AWS 엑세스 키 발급 받고
  2. MSI 설치 관리자 설치
  3. cmd 열어서 aws configure 명령어 입력 -> 해결 방법 참조

해결 방법
1. 엑세스 키 발급
2. MSI 설치 관리자 설치

Error2. Could not connect to the endpoint URL: "https://kms.None.amazonaws.com/"

다양한 이유가 있을 수 있겠지만 저같은 경우 위 과정에서 region 설정을 제대로 해주지 않아 일어났던 문제였습니다.

cmd 열어서 aws configure 명령어 입력
region 부분에서 ap-northeast-2 / 서울로 등록

Error 3. Object type <class 'str'> cannot be passed to C code

encode하는 부분에서 일어났던 문제입니다. 해당 에러는 .encode() 부분을 pad(message)한 결과에 추가해주면 해결은 되는데 이게 맞는 방법인지는 아직 잘 모르겠습니다. 아시는 분 있으시면 도와주세요ㅠㅠ

Error 4. 'utf-8' codec can't decode byte 0x9f in position 2: invalid start byte

이 에러는 Base64로 인코딩된 키와 임의 데이터를 디코딩하고 결과를 JSON 또는 텍스트로 다시 형식화하려고 할 때 일어나는 에러라고 합니다. 'utf-8' 이외의 다른 방법 ex) utf-16, cp949 등을 사용하면 해결할 수 있다 하였는데 해결하지는 못했습니다. 제가 일어났던 원인은 애초에 잘못된 방법으로 암호화를 진행했기 때문이었습니다.

Error 5. TypeError: a bytes-like object is required, not ‘str’

해당 에러는 말 그대로 bytes-like라는 오브젝트가 필요하니 str 타입 말고 bytes 타입의 변수를 넣으라는 뜻입니다. decode까지 마친 결과에 rstrip를 실행하려 할 때 아직 바이트 문자열로 되어 있어 일어났던 문제였습니다. 이 문제가 일어나면 해당 문자열이 어떻게 구성되어 있는지 우선 확인해보세요~!!

  • str -> 인코딩 -> bytes
  • bytes -> 디코딩 -> str

3. aws-encryption-sdk

위의 방법과는 다른 방법입니다. AWS에서 암호화를 완벽하게 호환하는 Python용 SDK를 제공하고 있습니다.

aws-encryption-sdk 사용법

사용법

1. 설치

pip install aws-encryption-sdk

2. 라이브러리 호출

이 모듈을 사용하기 위해서는 EncryptionSDKClient 클래스의 인스턴스를 만들어야 합니다.

import aws_encryption_sdk
from aws_encryption_sdk.identifiers import CommitmentPolicy


client = aws_encryption_sdk.EncryptionSDKClient(
    commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)

3. StrictAwsKmsMasterKeyProvider

StrictAwsKmsMasterKeyProvider는 데이터를 암호화하고 복호화에 사용할 AWS CMS CMK의 목록으로 구성됩니다.
암호 해독 시 구성된 CMK ARN 중 하나와 일치하는 CMK로 래핑된 암호문 암호 해독만 시도합니다.

import aws_encryption_sdk

kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[
    'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222',
    'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333'
])

4. 암복호화

다음과 같이 사용해주면 arn의 정보만 가지고 간단하게 암복호화가 가능합니다.

my_plaintext = b'This is some super secret data!  Yup, sure is!'

my_ciphertext, encryptor_header = client.encrypt(
    source=my_plaintext,
    key_provider=kms_key_provider
)

decrypted_plaintext, decryptor_header = client.decrypt(
    source=my_ciphertext,
    key_provider=kms_key_provider
)

5. 결론

aws-encryption-sdk를 이용해 암호화를 하게 되면 아래 사진에 표시한 것처럼 550자 정도되는 바이트 문자가 출력되게 됩니다. 이거를 저장을 어떻게 하냐가 관건인데 다른 사람들은 어떻게 사용하고 계실지 잘 모르겠습니다. 저는 앞의 CBC 방법을 추천드립니다.

추가로 다른 별칭으로 작업하는 방법, 키로 작업하는 방법 등 여러가지 설명들이 공식 문서에 나와있으니 찾아서 적합한 환경에 맞게 사용하는 것을 추천드립니다.


Etc. KMS 자동(봉투 암호화) vs 수동

자동(봉투화 암호 ex) aws-encryption-sdk)와 같은 경우 rotate key 갑이 달라져도 암복호화 하는데 이상 없지만, 수동(generate key 이용하는 방식)은 generate로 키 값이 달라지면 암복호화가 불가능한 방법입니다. 그래서 generate를 이용해 작업하는 경우 Data key를 local에 저장해두고 사용하고, 이 값은 만료 기한은 따로 없는 것으로 알고 있습니다.

자동 (봉투화 암호) - 암호화 길이는 길지만 암복호화가 더 유연하게 된다는 점에서 매력적
수동 - 키를 local에 가지고 있어야 해서 보안상 아쉬움이 있지만, 암호화 길이가 88자로 매력적


참고자료 📩

AWS KMS python 사용법
배달의 민족 KMS 구성 설명
KMS 설명 잘해준 블로그
Error1 문제 해결 방법
Error1.1. 엑세스 키 발급
Error1.2. MSI 설치 관리자 설치
AWS KMS 사용법
aws-encryption-sdk 사용법

profile
삽질의 기록들🐥

2개의 댓글

comment-user-thumbnail
2024년 1월 10일

안녕하세요. 먼저 작성해 주신 내용으로 kms에 대한 이해도를 높일 수 있었습니다. 감사합니다.

작성해주신 내용을 참고하여 람다로 kms 암복호화 함수를 만들고 있는데요 .
복호화 부분에서 enc = str.encode(enc) 이 부분에서 에러가 발생해서. 해당 구문을 제외하고 하였더니, 복호화가 정상적으로 되지 않더라구요. str 을 위에서 따로 선언해주신걸까요???

1개의 답글