서버 없이 쉽고 빠르게 암복호화 API서비스를 만들어 봅니다.

지난해 우아한형제들은 정보보호관리체계(ISMS)인증 심사를 준비하며 개인정보 암호화 키를 물리적으로 분리하여 보다 안전하게 관리할 수 있는 AWS Key Management Service(이하 KMS) 도입을 결정했습니다. 그리고 운영 리소스를 최소화하고 트래픽에 유연하게 대처하기 위해 AWS 서버리스 서비스들을 적극적으로 활용하여 암복호화 API를 구현했습니다. API 서비스를 구축함에 있어 인프라 이슈를 배제하고 로직에 집중할 수 있다는 건 행복한 일이니까요 :)

암복호화 API 서비스를 만들기 위해서는 라우터 역할을 할 AWS API Gateway와 함수 실행을 위한 AWS Lambda 그리고 키 관리를 위한 KMS가 필요한데, 각 서비스들의 주요 기능과 장단점에 대한 설명은 생략하는 걸로…

Serverless 서비스들

잠깐!!! API 서비스 구축에 앞서,

Customer Master Keys (CMKs)를 가지고 직접 암복호화를 수행하는 것에 따른 보안 위험성과 이 때 발생하는 네트워크 로드를 줄이기 위해 봉투 암호화(Envelope Encryption) 방식을 사용하여 데이터 보안을 확보하고 Data Keys(DKs) 요청에만 네트워크 비용이 발생하도록 하는 것이 좋습니다. 이 때 사용되는 Key들과 AWS Encryption SDK에서 제시하는 암복호화 프로세스에 대해 간단하게 설명하겠습니다.

KMS Keys

  • Master Key
    Region별로 생성 가능하며 AWS에 의해 보관 관리되고 키값이 노출되지 않습니다. 어플리케이션에서는 암복호화를 위해 KEY ID값을 받급 받아 사용합니다.
  • 평문 Data key
    AWS에 의해 발급되며 암복호화 시 꼭 필요한 Key값이지만, 해당 시점에만 사용하고 별도로 보관해서는 안됩니다.
  • 암호화된 Data key
    평문 Data Key를 암호화한 Key값으로 복호화 시 평문 Key를 얻기 위해 사용합니다.

암호화 방식

Encryption

  1. 암호화를 위한 평문을 암호화 메소드에 전달합니다.
  2. Master key Provider를 통해 사용할 Master Key를 결정합니다.
  3. Master Key를 이용하여 Data Key를 생성합니다.
  4. 이때, 평문과 암호화된 2개의 Data Key가 생성됩니다.
  5. 평문 Data Key를 이용하여 평문을 암호화합니다. 암호화가 완료되면 평문 Data Key는 삭제합니다.
  6. 암호화된 문자열과 암호화된 Data Key를 반환합니다. 암호화된 Data Key는 복호화를 위해 필요한 정보입니다.

복호화 방식

Decryption

  1. 암호화된 문자열과 암호화된 Data Key를 복호화 메소드에 전달합니다.
  2. 암호화된 Data Key를 Master Key Provider에 전달합니다.
  3. Master Key Provider는 전달받은 암호화된 Data Key를 복호화하여 평문 Data Key를 반환합니다.
  4. 평문 Data Key를 이용하여 암호화된 문자열을 복호화하여 반환합니다. 복호화가 완료된 평문 Data Key는 삭제합니다.

이제부터 암복호화 API를 구축해 봅니다.

순서는 KMS Master Key를 생성하고 Lambda에서 암복호화 함수를 만들고 마지막으로 API Gateway에 Lambda를 연결해주고 테스트하면 끝납니다. 참 쉽죠~

KMS Master Key 생성

AWS관리 콘솔에 로그인합니다. 그리고 KMS라는 서비스가 있어야 하는데… 어랏 보이지 않습니다만,
고객님, 그땐 당황하지 마시고 IAM을 선택하고 진행하면 됩니다(메뉴조차 비밀스럽습니다). IAM 화면 좌측 메뉴 하단에 “Encryption Keys”를 클릭한 뒤 서울 Region을 먼저 선택하고 “Create Key”를 눌러 생성하면 됩니다. 시스템 관리자 권한으로 접근하여 생성할 수 있습니다.

Create KMS Marter Key

암복호화 Lambda 함수 만들기

Lambda 함수는 Python, NodeJs, Java가 지원됩니다(2016년 6월 시점). 우아한형제들에서는 Python을 사용해 구축했습니다.

Lambda 서비스를 선택한 뒤 “Create a Lambda function” 버튼을 클릭, 다음 화면에서 좌측 메뉴 “Configure function”을 선택하면 Lambda 함수를 설정할 수 있는 화면이 나타납니다. Lambda Configure function



본격적으로 함수 구현 스타트~

라이브러리와 변수 초기화

import base64
import boto3

from Crypto.Cipher import AES

self.client = boto3.client('kms')

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

Key Id 발급 및 Data Key 생성

self.key_id = 'arn:aws:kms:ap-northeast-2:123456789012:key/{Master Key}'

self.pad = lambda s: s + (32 - len(s) % 32) * ' '

# data key 생성
data_key = client.generate_data_key(KeyId=self.key_id, KeySpec='AES_256')
self.plaintext_key = data_key.get('Plaintext')
self.ciphertext_blob = data_key.get('CiphertextBlob')

Master Key를 이용하여 Lambda class 로드 시, 암복호화에 필요한 Data Key(plaintext_key, ciphertext_blob)를 초기화합니다.

암호화 메소드

def encrypt_data(self, plaintext_message):
  crypter = AES.new(self.plaintext_key)
  encrypted_data = base64.b64encode(crypter.encrypt(self.pad(plaintext_message)))

  return encrypted_data, self.ciphertext_blob

평문 Data Key를 이용하여 암호화 객체를 생성, 문자열을 암호화하고 base64로 인코딩 후 이미 생성된 암호화된 Data Key(ciphertext_blob)와 함께 반환합니다. 이때 반환되는 암호화된 Data Key는 복호화에 필요한 평문 Data Key를 얻기 위해 꼭 필요한 정보이므로 어플리케이션에서 관리되어야 합니다.

복호화 메소드

def decrypt_data(self, encrypted_data, ciphertext_blob):
  decrypted_key = self.client.decrypt(ciphertext_blob).get('Plaintext')
  crypter = AES.new(decrypted_key)

  return crypter.decrypt(base64.b64decode(encrypted_data)).rstrip()

전달 받은 암호화된 문자열(encrypted_data)과 Data Key(ciphertext_blob)로 평문 Data Key를 반환 받아 암호화 역순으로 암호화된 문자열을 base64로 디코딩한 뒤 복호화해서 반환합니다.

lambda_handler 구현

def lambda_handler(req_type, context):
  try:
    # TODO implement
  except Exception as e:

어플리케이션에서 요청(req_type)한 유형에 따라 위 구현 메소드를 수행할 처리 로직 및 예외상황에 대해 구현해 줍니다.

마지막으로 API Gateway 설정하여 Lambda와 연결

AWS Lambda에서 구현한 함수 실행을 위해 API를 생성하고 설정합니다.
API 리소스 생성을 위해 “Create API”를 버튼을 클릭한 뒤 “Actions”의 Create Resource를 선택해 이름을 지정하고 생성합니다.

create_resource


Rest 메소드 추가하기 위해 다시 “Actions”의 Create Method를 통해 리소스에 연결될 HTTP 메서드(예: POST)를 선택한 뒤 Region과 이미 생성해 놓은 Lambda 함수를 지정합니다.

create method


API 배포를 위해 생성한 메소드에서 “Actions”를 눌러 Deploy API를 선택합니다. 설정 화면에서 “[New Stage]”를 선택한 뒤 배포할 스테이지 정보(예: test, prod 등)를 입력합니다.

api Deploy


위 작업까지 완료됐다면 생성된 배포용 API 호출 URL을 확인할 수 있습니다.

invoke url


추가로 API 호출 시 메소드에 보안 설정을 구성하고 URL이 외부에 유출되더라도 불필요한 유입을 차단하도록 API Key를 설정할 수 있습니다.

API Key 설정

  • “Usage Plans”를 선택 후 임계치에 값을 설정하고 API Stage를 추가합니다.
  • “API Keys”를 선택한 뒤 “Actions”의 Create API Key를 선택하여 생성하고 Add to Usage Plan를 클릭하여, “Usage Plans”에서 생성한 Plan을 입력하여 설정합니다.
  • 설정이 완료된 API Key는 좌측 메뉴 “API Keys” 에서 확인 가능합니다.


자! 이제 모든 설정이 끝났으므로 암복호화를 테스트만 해보면 되겠네요.

curl -d '{"req_type":"encrypt","context":"암호화할 문자열"}' \
  -H "x-api-key : {API Key}" https://{API 호출 URL}/prod/encrypt

curl -d '{"req_type":"decrypt","context":"복호화할 문자열 + 복호화를 위한 암호화된 Data Key"}' \
  -H "x-api-key : {API Key}" https://{API 호출 URL}/prod/encrypt

아직 아쉬운 부분… Limit.

AWS에서 제공하는 대부분의 서비스들은 기본적으로 Limit이 존재합니다. 클라우드 서비스에서 리소스를 효율적으로 관리하기 위해 당연하겠지만, API Gateway, Lambda에 대해 거의 무제한으로 사용 가능한 다른 Region에 비해 서울 Region에서는 아직까진 계정당 최대 Limit이 제한되어 있어 트래픽이 많은 서비스를 구현하기엔 부담스럽다는 부분입니다.
Limit을 고려하지 않을 경우, 임계치에 도달하여 API가 정상적으로 동작하지 않거나 응답 속도를 보장 받을 수 없습니다. 따라서 상황에 따라 계정을 분리하여 사용하거나 AWS 서비스 도입 전 구축 서비스의 트래픽을 잘 가늠하여 진행하는 것을 권장합니다.

참고링크