메뉴

문서정보

목차

AWS KMS에 대해서

AWS KMS는 데이터의 암호화를 위해서 사용하는 관리형 서비스다. KMS는 고가용성의 키 저장소와 관리 감사 기능을 제공한다. 이를 이용해서 애플리케이션에서 사용하는 데이터를 암호화 할 수 있으며, AWS 서비스에 저장되는 데이터(예를들어 S3에 저장되는 파일)을 암호화하기 위한 키를 (안전하게)관리 할 수 있다. AWS KMS에서 생성한 마스터 키는 FIPS 140-2 검증 암호화 모듈의 보호를 받는다.

CMK

AWS KMS를 사용하면 키를 중앙 집중식으로 안전하게 관리 할 수 있다. 이러한 키를 CMK(고객마스터키)라고 한다. 사용자는 KMS나 AWS CloudHSM 클러스터로 부터 CMK를 생성 할 수 있다. 마스터키는 하드웨어 보안 모듈(HSM)에서 보호하기 때문에 안전하게 사용 할 수 있다. 사용자는 CMK를 이용해서 데이터를 암호화 하거나 복호화 할 수 있다.

CMK는 3가지 타입이 있다.
CMK 타입 보기 가능 관리 가능 내 AWS 계정에서만 사용
고객관리형 CMK Yes Yes Yes
AWS 관리형 CMK Yes No No
AWS 소유 CMK No No No
AWS 관리형 CMK와 AWS 소유 CMK는 AWS 서비스에서 자신의 데이터들을 암호화하기 위해서 사용하는 CMK다. 예를 들어 AWS RDS의 경우 데이터베이스를 암호화 할 수 있는데, 이때는 AWS 관리형 CMK를 사용한다. AWS에서 알아서 CMK를 관리해주기 때문에, 고객이 개입할 필요가 없다.

aws cli를 이용해서 CMK를 만들어보자.
# aws kms create-key
{
    "KeyMetadata": {
        "Origin": "AWS_KMS", 
        "KeyId": "71751c0b-66aa-42ea-9966-xxxxxxxxxxxx", 
        "Description": "", 
        "KeyManager": "CUSTOMER", 
        "Enabled": true, 
        "KeyUsage": "ENCRYPT_DECRYPT", 
        "KeyState": "Enabled", 
        "CreationDate": 1556635279.684, 
        "Arn": "arn:aws:kms:ap-northeast-2:52xxx:key/xxx-xxx-xxx", 
        "AWSAccountId": "5223xxxxxxx"
    }
}
list-keys로 CMK 목록을 확인 할 수 있다.
# aws kms list-keys
{
    "Keys": [
        {
            "KeyArn": "arn:aws:kms:ap-northeast-2:522xxxx:key/37f608e7-81e3-4a92-8759-xxxxx", 
            "KeyId": "37f608e7-81e3-4a92-8759-xxxxx"
        }, 
        {
            "KeyArn": "arn:aws:kms:ap-northeast-2:522xxxx:key/42ed6fd5-7ad0-4ce5-9c4b-xxxx", 
            "KeyId": "42ed6fd5-7ad0-4ce5-9c4b-xxxxx"
        }, 
        {
            "KeyArn": "arn:aws:kms:ap-northeast-2:522xxxx:key/71751c0b-66aa-42ea-9966-xxxx", 
            "KeyId": "71751c0b-66aa-42ea-9966-xxxxx"
        }, 
        {
            "KeyArn": "arn:aws:kms:ap-northeast-2:522xxxx:key/ccfecbb1-5bbb-490f-a24d-xxxx", 
            "KeyId": "ccfecbb1-5bbb-490f-a24d-xxxxx"
        }
    ]
}

describe-key로 CMK에 대한 세부 정보를 가져올 수 있다. ccfecbb1-5bbb-490f-a24d-xxxxx CMK 정보를 읽어봤다.
aws> kms describe-key --key-id ccfecbb1-5bbb-490f-a24d-xxxxx
{
    "KeyMetadata": {
        "Origin": "AWS_KMS", 
        "KeyId": "ccfecbb1-5bbb-490f-a24d-d9ddc4bf98eb", 
        "Description": "Default master key that protects my Lightsail signing keys when no other key is defined", 
        "KeyManager": "AWS", 
        "Enabled": true, 
        "KeyUsage": "ENCRYPT_DECRYPT", 
        "KeyState": "Enabled", 
        "CreationDate": 1540744470.072, 
        "Arn": "arn:aws:kms:ap-northeast-2:522373083963:key/ccfecbb1-5bbb-490f-a24d-d9ddc4bf98eb", 
        "AWSAccountId": "522373083963"
    }
}
Lightsail에서 사용하는 AWS관리형 키라는 것을 확인 할 수 있다.

데이터 키(Data Keys)

데이터 키는 많은 양의 데이터를 암호화하기 위해서 사용하는 암호화 키다. AWS KMS를 이용하면 CMK를 만들어서 사용 할 수 있지만, 데이터 키를 저장,관리 하지는 않는다. 따라서 데이터키는 KMS 밖에(S3 같은) 저장하고 사용해야 한다.

데이터 키는 GenerateDataKey로 만들 수 있다. AWS KMS는 지정된 CMK를 이용해서 데이터키를 만든다. 이때 (암호화되지 않은)일반 텍스트 사본과 CMK로 암호화된 데이터 키의 사본을 반환한다.

 데이터 키 생성

데이터 키를 만들어보자.
# aws kms generate-data-key --key-id 71751c0b-66aa-42ea-9966-77xxxxx --key-spec AES_256
{
    "Plaintext": "Q77P3coMIQjyVMWSwppsQY/xxxx....xxxxxxx=", 
    "KeyId": "arn:aws:kms:ap-northeast-2:52xxxxxxx:key/71751c0b-66aa-42ea-9966-77xxxxx", 
    "CiphertextBlob": "AQIDAHi1OnBYq3xxxxx...............xxxxxxxxxxxx=="
}

CMK를 이용한 데이터 암&복호화

AWS 사용자는 KMS를 이용해서 애플리케이션 마다 여러 개의 CMK를 만들 수 있다. 이 CMK는 고객 관리형 CMK로 사용자가 완전한 권한을 가진다. 사용자는 이 CMK를 이용해서 데이터를 암복호화 할 수 있다.

aws cli를 이용한 암&복호화

앞서 만든 CMK를 이용해서 데이터의 암복호화를 수행해보자. 테스트를 위해서 mydata 라는 파일을 만들었다.
# cat mydata
hello world
테스트를 위해서 아래와 같은 Makefile을 만들었다.
# CMK 아이디
KEY_ID=71751c0b-66aa-42ea-9966-77xxxxx

# 암/복호화 테스트에 사용할 파일
SECRET_BLOB_PATH=fileb://my-secret-blob

# 암/복호화 테스트에 사용할 텍스트
SECRET_TEXT="my secret text"

# 암호화파일 이름
ENCRYPTED_SECRET_AS_BLOB=encrypted_secret_blob
# 복호화 파일 이름
DECRYPTED_SECRET_AS_BLOB=decrypted_secret_blob  # Result of decrypt-blob target

encrypt-text:
    aws kms encrypt --key-id ${KEY_ID} --plaintext ${SECRET_TEXT} --query CiphertextBlob --output text \
        | base64 --decode > ${ENCRYPTED_SECRET_AS_BLOB}

decrypt-text:
    aws kms decrypt --ciphertext-blob fileb://${ENCRYPTED_SECRET_AS_BLOB} --query Plaintext --output text \
        | base64 --decode

encrypt-blob:
    aws kms encrypt --key-id ${KEY_ID} --plaintext ${SECRET_BLOB_PATH} --query CiphertextBlob --output text \
        | base64 --decode > ${ENCRYPTED_SECRET_AS_BLOB}

decrypt-blob:
    aws kms decrypt --ciphertext-blob fileb://${ENCRYPTED_SECRET_AS_BLOB} --query Plaintext --output text \
| base64 --decode > ${DECRYPTED_SECRET_AS_BLOB}

Go를 이용한 암/복호화

Go AWS SDK를 이용해서 암/복호화 테스트 애플리케이션을 만들어봤다.
package main

import (
    "fmt"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/kms"
    "os"
)

func main() {
    text := "hello world"
    sess, err := session.NewSession(&aws.Config{
        Region: aws.String("ap-northeast-2"),
    })
    
    svc := kms.New(sess)
    keyId := "arn:aws:kms:ap-northeast-2:522373083963:key/71751c0b-66aa-42ea-9966-7725186dd87d"
    
    result, err := svc.Encrypt(&kms.EncryptInput{
        KeyId:     aws.String(keyId),
        Plaintext: []byte(text),
    })
    
    if err != nil {
        fmt.Println("Got error encrypting data: ", err)
        os.Exit(1)
    }
    fmt.Println("Encrypt Text ===")
    fmt.Println(result.CiphertextBlob)
    
    fmt.Println("Decrypt Text ===")
    decText, err := svc.Decrypt(&kms.DecryptInput{CiphertextBlob: result.CiphertextBlob})
    fmt.Println(string(decText.Plaintext))
}   
CMK를 이용해서 암호화하고 복호화하는 예제다. 사용자는 암/복호화에 사용할 CMK의 KeyID를 선택하기만 하면 된다.

데이터베이스 개인 정보 필드 암호화를 위한 암호화 키 저장하기

데이터베이스에 이름, 전화번호, 주소 등 개인정보를 저장해야 하는 경우가 있다. 이들 개인 정보를 암호화/복호화 하기 위한 키를 소스코드나 설정파일에 넣는 것은 좋은 방법이 아니다. 아래와 같이 KMS를 이용해서 암호화 키를 관리하는 시스템을 만들어보기로 했다.

 KMS를 이용한 데이터 암호화

  1. CMK를 이용해서 데이터 키를 가져올 수 있다.
  2. 데이터 키는 CMK를 통해서 암호화돼 있으며, CMK를 이용해서만 복호화 할 수 있다.
  3. 이 데이터 키를 S3와 같은 안전한 장소에 보관한다.
  4. 애플리케이션은 S3로 부터 데이터 키를 읽어온다.
  5. CMK를 이용해서 데이터 키를 복호화하고, 복호화된 키로 애플리케이션 데이터를 암호화 해서 저장한다.
데이터 키가 노출된다고 하더라도 CMK가 없으면 (복호화가 불가능 하므로)사용 할 수가 없다. CMK와 S3에 대한 접근은 롤(role)로 설정할 수 있기 때문에, 각 인스턴스(혹은 컨테이너)별로 접근을 제어 할 수 있으므로 안전하게 키를 관리할 수 있다.

마스터키로 데이터를 암호화 해서 전달하고, 다시 마스터키로 암호화된 데이터를 복호화 하는 이 방식을 봉투 암호화하고 한다. 사용하려는 데이터를 밀봉된 봉투에 넣어서 전달하는 느낌이다.

암호화된 데이터 키 복호화

암호화된 데이터 키가 있다고 가정해보자. 애플리케이션에서 사용하려면 데이터 키를 읽어서 복호화 해야 한다. 테스트를 위해서 CMK를 이용해서 새로운 데이터키를 만들었다.
$ aws kms generate-data-key --key-id 71751c0b-66aa-42ea-9966-77xxxxxxxxxxx --key-spec AES_256
{
    "Plaintext": "/4yebJbydtN+4ADZWxO7PWzI1cbQgIaJR+JiUcLXbaY=", 
    "KeyId": "arn:aws:kms:ap-northeast-2:522373083963:key/71751c0b-66aa-42ea-9966-7725186dd87d", 
    "CiphertextBlob": "AQIDAHi1OnBYq3jLHngLHbUFhJoXj9KTLE2ybwzsHVlA00GpFAHjFB/5ItHMuftTXmha3GtgAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvwNSr+MTb9RWPQt6AgEQgDteob+0Y+h+sbmRfjxuYwcvdocAtUrM7uDbIzP6vvsvatHa6GoUpfd//9LFlJWUxOwktGsoL0hWWMqdLw=="
}
데이터 키를 얻었다. 애플리케이션은 Plaintext를 암/복호화 키로 사용한다. 이 키는 외부에 노출되면 안되므로, 애플리케이션은 CiphertextBlob를 복호화해서 사용한다. 테스크코드를 만들어봤다.
package main

import (
    "encoding/base64"
    "fmt"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/kms"
    "os"
)

func main() {
    plaintext := "/4yebJbydtN+4ADZWxO7PWzI1cbQgIaJR+JiUcLXbaY="
    sess, err := session.NewSession(&aws.Config{
        Region: aws.String("ap-northeast-2"),
    })

    svc := kms.New(sess)

    if err != nil {
        fmt.Println("Got error encrypting data: ", err)
        os.Exit(1)
    }

    fmt.Println("Decrypt Text ===")
    ciphertext_regular := "AQIDAHi1OnBYq3jLHngLHbUFhJoXj9KTLE2ybwzsHVlA00GpFAHjFB/5ItHMuftTXmha3GtgAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvwNSr+MTb9RWPQt6AgEQgDteob+0Y+h+sbmRfjxuYwcvdocAtUrM7uDbIzP6vvsvatHa6GoUpfd//9LFlJWUxOwktGsoL0hWWMqdLw=="
    ciphertext_blob, err := base64.StdEncoding.DecodeString(ciphertext_regular)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    decText, err := svc.Decrypt(&kms.DecryptInput{CiphertextBlob: ciphertext_blob})
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    a := base64.StdEncoding.EncodeToString(decText.Plaintext)
    if a == plaintext {
        fmt.Println("Decrypt Success : ", plaintext)
    }

}
코드를 실행해보자.
# go run main.go 
Decrypt Text ===
Decrypt Success :  /4yebJbydtN+4ADZWxO7PWzI1cbQgIaJR+JiUcLXbaY=
암호화된 데이터 키를 성공적으로 복호화 했다. 이제 복호화된 키를 애플리케이션 암호화 키로 사용하면 된다. 데이터 키에는 자신을 암호화한 CMK KeyID 정보도 있기 때문에, 코드에 CMK KeyID를 하드코딩할 필요가 없다.

정리

CMK를 이용한 암호화

CMK를 이용해서 암호화를 할 수 있다. 하지만 4KB(4096바이트)를 초과하는 데이터의 암호화에는 사용 할 수 없다. 암호나 RSA 키 등 소량의 데이터를 암호화하는 목적으로 사용 할 수 있지만, 애플리케이션 데이터의 암호화에는 적당한 방법이 아니다.

데이터의 크기가 작다고 하더라도 CMK를 암호화/복호화에 이용하는 건 권장하지 않는다.

KMS를 이용해서 데이터 암호화키를 안전하게 사용하기

KMS를 이용해서 데이터를 안전하게 암호화하는 과정을 정리했다.
  1. 애플리케이션을 만들었다. 이 애플리케이션은 고객 정보를 사용하기 때문에, 데이터에 대한 암/복호화가 필요하다.
  2. KMS에 애플리케이션을 위한 CMK를 요청한다.
  3. CMK를 이용해서 애플리케이션이 사용할 암호화된 데이터 키를 만든다.
  4. 암호화된 데이터키는 S3에 저장한다.
  5. 이 S3는 애플리케이션에서만 접근 할 수 있도록 IAM 롤이 설정된다.
IAM 설정까지 끝났으므로 데이터 키는 관리자가 허가한 애플리케이션만 사용 할 수 있다. 데이터 키가 노출된다고 하더라도 암호화 돼 있기 때문에 사용 할 수 없다. 또한 CMK에 대한 접근은 감사 할 수 있기 때문에, 노출됐다고 생각되면 기존 키를 전부 파기하고 새로 만들면 된다.

애플리케이션은 아래와 같이 데이터 키를 사용해서 암/복호화를 할 수 있다.
  1. S3로 부터 데이터 키를 가져온다.
  2. KMS API를 호출 데이터 키를 복호화 한다.
  3. 데이터 키를 이용해서 데이터를 암호화/복호화 한다.