KMS는 key Management Service의 약자로, 데이터 암호화 할 때 사용되는 암호화 Key를 안전하게 보관할 수 있도록 관리해주는 서비스입니다.
KMS는 크게 3가지 방식의 관리 서비스가 제공됩니다.
(방금 다시 확인해봤는데 1가지 추가됐네요ㅎㅎ)
- AWS managed key
- Customer managed key
- Custom key stores
AWS 서비스들이 KMS를 통해 Key를 서비스 받는 것은 동일하나, 내부적으로 자동으로 일어나 사용자가 직접적으로 제어가 불가능하다는 점이 특이점입니다.
사용자가 직접 Key를 생성 및 관리할 수 있으며, IAM을 통해 CMK에 대한 제어 권한을 부여 받아 사용할 수 있습니다. 그리고 이번 블로그에 포스팅할 주제입니다.
AWS에서 제공하는 또다른 Key 관리형 서비스인 CloudHSM을 활용한 Key 관리 형태입니다. 간략하게 얘기하면 CloudHSM이 조금 더 강력한 형태의 보안 안정성을 제공한다고 이해하면 됩니다.
KMS의 동작 방식을 그려보면 아래의 사진과 같습니다.
AWS KMS 서비스를 이용하기 위해서는 IAM 발급이 필수입니다. 발급 과정은 운영체제마다 다르지만 다음과 같습니다.
- IAM 엑세스 키 발급
- 발급받은 엑세스 키 및 시크릿 키, region 입력
- AWS 엑세스 키 발급 받고
- MSI 설치 관리자 설치
- cmd 열어서
aws configure
명령어 입력 -> 해결 방법 참조
해결 방법
1. 엑세스 키 발급
2. MSI 설치 관리자 설치
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
우선 KMS 서비스를 이용해 키 관리를 하고, 저장되어 있는 키를 이용해 암복호화를 한다고 이야기 했습니다.
그러기 위해서는 첫 번째로 KMS 서비스에 CMK(Customer Master Key)를 생성해줍니다.
AWS KMS 서비스에 들어가게 되면 다음과 같은 화면이 보입니다. 그러면 로그인을 진행해주고 화면 중앙에 주황색으로 보이는 AWS Key Management Service 시작하기
를 클릭합니다. 그 후 오른쪽 상단에 키 생성
버튼을 클릭해줍니다.
그러면 다음과 같은 화면 창이 뜨게됩니다. 여기서 각자 원하는 타입의 키 유형과 키 사용을 선택을 진행해주면 됩니다. 저는 대칭키와 암호화 및 해독을 이용해보겠습니다.
여기서 고급 옵션을 보게 되면 앞서 얘기했던 3가지에 추가적으로 1가지의 키 구성 방법이 존재합니다. 이 부분도 원하는 타입으로 골라주면 됩니다.
간단히 키의 이름 적고 넘어가주면 됩니다.
키 관리 권한을 관리할 유저나 역할을 추가해주고 넘어가주면 됩니다.
이번에는 KMS 키를 사용할 수 있는 IAM 사용자 및 역할을 지정해주면 됩니다.
지금까지의 내용을 검토 후에 키 생성해주면 됩니다.
Data Key는 위에서 만든 CMK로부터 GenerateDataKey
라는 함수를 통해 생성되는데, 이 때 생성되는 Data Key는 크게 두 가지 종류로 Plaintext data key
와 Encrypted data key
가 있습니다.
암호화를 하기 위해서는 Plaintext data key
를 필요로 합니다. 암호화 과정은 AWS에서 제공하는 Encryption SDK를 사용하면 되고, 데이터를 암호화 한 이후에는 해당 Plaintext data key
는 폐기처리합니다.
복호화를 하기 위해서는 Plaintext data key
가 필요합니다. 하지만 암호화 한 후 Plaintext data key
는 폐기처리 되었습니다. 이 과정에서 다시 Plaintext data key
를 얻어야 하는데, 방법은 다음과 같습니다.
Encrypted data key
를 이용해 Plaintext data key
를 가져올 수 있습니다.
이 때 data key를 복호화하는 과정에 CMK
가 다시 사용되며, plaintext로 변환된 data key를 이용해 복호화를 진행해주면 됩니다.
지금까지 설명한 내용들을 바탕으로 살펴보겠습니다.
우선 아래의 코드를 실행하기 위해 boto3, pycryptodomex
라이브러리가 필요하므로 설치를 진행해 줍니다.
pip install boto3
pip install pycryptodomex
Boto3는 AWS에서 제공하는 Python용 SDK로 Low-level에 직접 접근할 수 있을 뿐 아니라 사용하기 쉬운 객체 지향 API를 제공합니다.
import base64
import boto3
from Crypto.Cipher import AES
client = boto3.client('kms')
Master Key를 이용하여 Lambda class 로드 시 암복호화에 필요한 Data key(Plain & Encrypt)
를 초기화합니다.
generate_data_key
를 이용해 plaintext_key
와 Encrypted_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')
평문 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
복호화 메소드에서는 암호화 된 문자열과 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에 저장해서 사용하기에도 큰 부담이 없습니다.
위의 방법을 토대로 작업하면 generate_data_key()
를 실행할 때마다 plaintext_key
, encrypted_key
가 다르게 나오고 있습니다. 그렇다면 들어오는 데이터마다 키가 다르다면 암복호의 의미가 사라지게 됩니다. 그래서 generate_data_key()
를 한 번만 사용하고 추출한 encrypted_key
를 보관하면서 암복호화를 진행하는지 찾아봤는데 명확하게 나와있는 솔루션이 없는 거 같습니다. 만약 그렇게 사용한다면 로컬에 두고 사용하는 것과 크게 어떤게 다른지, 이중화를 한다는 거에 초점을 둬야 하는건지 잘 모르겠습니다.
해당 에러는 로컬 시스템에 AWS CLI를 처음 설치한 경우 아래 주어진 명령을 사용해 AWS 자격 증명을 구성해야 합니다.
- AWS 엑세스 키 발급 받고
- MSI 설치 관리자 설치
- cmd 열어서
aws configure
명령어 입력 -> 해결 방법 참조
해결 방법
1. 엑세스 키 발급
2. MSI 설치 관리자 설치
다양한 이유가 있을 수 있겠지만 저같은 경우 위 과정에서 region 설정을 제대로 해주지 않아 일어났던 문제였습니다.
cmd 열어서
aws configure
명령어 입력
region 부분에서ap-northeast-2
/ 서울로 등록
encode하는 부분에서 일어났던 문제입니다. 해당 에러는 .encode() 부분을 pad(message)한 결과에 추가해주면 해결은 되는데 이게 맞는 방법인지는 아직 잘 모르겠습니다. 아시는 분 있으시면 도와주세요ㅠㅠ
이 에러는 Base64로 인코딩된 키와 임의 데이터를 디코딩하고 결과를 JSON 또는 텍스트로 다시 형식화하려고 할 때 일어나는 에러라고 합니다. 'utf-8' 이외의 다른 방법 ex) utf-16, cp949 등
을 사용하면 해결할 수 있다 하였는데 해결하지는 못했습니다. 제가 일어났던 원인은 애초에 잘못된 방법으로 암호화를 진행했기 때문이었습니다.
해당 에러는 말 그대로 bytes-like라는 오브젝트가 필요하니 str 타입 말고 bytes 타입의 변수를 넣으라는 뜻입니다. decode까지 마친 결과에 rstrip를 실행하려 할 때 아직 바이트 문자열로 되어 있어 일어났던 문제였습니다. 이 문제가 일어나면 해당 문자열이 어떻게 구성되어 있는지 우선 확인해보세요~!!
- str -> 인코딩 -> bytes
- bytes -> 디코딩 -> str
위의 방법과는 다른 방법입니다. AWS에서 암호화를 완벽하게 호환하는 Python용 SDK를 제공하고 있습니다.
pip install aws-encryption-sdk
이 모듈을 사용하기 위해서는 EncryptionSDKClient
클래스의 인스턴스를 만들어야 합니다.
import aws_encryption_sdk
from aws_encryption_sdk.identifiers import CommitmentPolicy
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)
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'
])
다음과 같이 사용해주면 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
)
aws-encryption-sdk
를 이용해 암호화를 하게 되면 아래 사진에 표시한 것처럼 550자 정도되는 바이트 문자가 출력되게 됩니다. 이거를 저장을 어떻게 하냐가 관건인데 다른 사람들은 어떻게 사용하고 계실지 잘 모르겠습니다. 저는 앞의 CBC 방법을 추천드립니다.
추가로 다른 별칭으로 작업하는 방법, 키로 작업하는 방법 등 여러가지 설명들이 공식 문서에 나와있으니 찾아서 적합한 환경에 맞게 사용하는 것을 추천드립니다.
자동(봉투화 암호 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 사용법
안녕하세요. 먼저 작성해 주신 내용으로 kms에 대한 이해도를 높일 수 있었습니다. 감사합니다.
작성해주신 내용을 참고하여 람다로 kms 암복호화 함수를 만들고 있는데요 .
복호화 부분에서 enc = str.encode(enc) 이 부분에서 에러가 발생해서. 해당 구문을 제외하고 하였더니, 복호화가 정상적으로 되지 않더라구요. str 을 위에서 따로 선언해주신걸까요???