

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 데이터 키 캐싱
<a name="data-key-caching"></a>

*데이터 키 캐싱*은 [데이터 키](concepts.md#DEK) 및 [관련 암호화 자료](data-caching-details.md#cache-entries)를 캐시에 저장합니다. 데이터를 암호화하거나 해독할 때는 캐시에서 일치하는 데이터 키를 AWS Encryption SDK 찾습니다. 일치하는 데이터 키를 찾으면 새 키를 생성하는 대신 캐시된 데이터 키를 사용합니다. 데이터 키 캐싱은 성능을 개선하고 비용을 절감하며 애플리케이션 확장에 따른 서비스 한도 내에서 유지하는 데 도움이 됩니다.

다음과 같은 경우 애플리케이션에서 데이터 키 캐싱의 이점을 누릴 수 있습니다.
+ 데이터 키를 재사용할 수 있습니다.
+ 수많은 데이터 키를 생성합니다.
+ 암호화 작업은 용납할 수 없을 정도로 느리거나, 비용이 많이 들거나, 제한적이거나, 리소스 집약적입니다.

캐싱은 AWS Key Management Service ()와 같은 암호화 서비스의 사용을 줄일 수 있습니다AWS KMS. [AWS KMS 초당 요청 수 한도](https://docs.aws.amazon.com/kms/latest/developerguide/limits.html#requests-per-second)에 도달한 경우 캐싱이 유용할 수 있습니다. 애플리케이션은 캐시된 키를 사용하여를 호출하는 대신 일부 데이터 키 요청을 처리할 수 있습니다 AWS KMS. ([AWS Support Center](https://console.aws.amazon.com/support/home#/)에서 사례를 생성하여 계정 한도를 높일 수도 있습니다.)

는 데이터 키 캐시를 생성하고 관리하는 데 AWS Encryption SDK 도움이 됩니다. [로컬 캐시](data-caching-details.md#simplecache)와, 캐시와 상호 작용하고 사용자가 설정한 [보안 임계값](thresholds.md)을 적용하는 [캐싱 암호화 자료 관리자](data-caching-details.md#caching-cmm)(캐싱 CMM)를 제공합니다. 이러한 구성 요소를 함께 사용하면 시스템 보안을 유지하면서 데이터 키를 효율적으로 재사용할 수 있습니다.

데이터 키 캐싱은 주의해야 AWS Encryption SDK 하는의 선택적 기능입니다. 기본적으로는 모든 암호화 작업에 대해 새 데이터 키를 AWS Encryption SDK 생성합니다. 이 기법은 암호화 모범 사례를 지원하므로 데이터 키를 과도하게 재사용하지 않도록 합니다. 일반적으로 데이터 키 캐싱은 성능 목표를 달성하는 데 필요한 경우에만 사용합니다. 그런 다음 데이터 키 캐싱 [보안 임계값](thresholds.md)을 사용하여 비용 및 성능 목표를 달성하는 데 필요한 최소한의 캐싱을 사용하는지 확인합니다.

버전 3.*x*는 키링 인터페이스가 아닌 레거시 마스터 키 공급자 인터페이스를 사용하는 캐싱 CMM AWS Encryption SDK for Java 만 지원합니다. 그러나 .NET AWS Encryption SDK 용의 버전 4.*x* 이상,의 버전 3.*x* AWS Encryption SDK for Java, Rust AWS Encryption SDK 용 **의 버전 4.*x* AWS Encryption SDK for Python, Go AWS Encryption SDK 용의 버전 1.*x*는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 지원합니다. AWS KMS 계층적 키링으로 암호화된 콘텐츠는 AWS KMS 계층적 키링으로만 복호화할 수 있습니다.

이러한 보안 트레이드오프에 대한 자세한 내용은 AWS 보안 블로그의 [AWS Encryption SDK: 데이터 키 캐싱이 애플리케이션에 적합한지 결정하는 방법](https://aws.amazon.com/blogs/security/aws-encryption-sdk-how-to-decide-if-data-key-caching-is-right-for-your-application/)을 참조하세요.

**Topics**
+ [

# 데이터 키 캐싱 사용 방법
](implement-caching.md)
+ [

# 캐시 보안 임계값 설정
](thresholds.md)
+ [

# 데이터 키 캐싱 세부 정보
](data-caching-details.md)
+ [

# 데이터 키 캐싱 예제
](sample-cache-example.md)

# 데이터 키 캐싱 사용 방법
<a name="implement-caching"></a>

이 주제에서는 애플리케이션에서 데이터 키 캐싱을 사용하는 방법을 보여줍니다. 프로세스를 단계별로 안내합니다. 그런 다음, 작업에서 데이터 키 캐싱을 사용하여 문자열을 암호화하는 간단한 예제로 단계들을 결합합니다.

이 섹션의 예제에서는 [2.0.*x*](about-versions.md) 이상 버전의 AWS Encryption SDK를 사용하는 방법을 보여줍니다. 이하 버전을 사용하는 예제의 경우 [프로그래밍 언어](programming-languages.md)에 대한 GitHub의 리포지토리의 [릴리스](https://github.com/aws/aws-encryption-sdk-c/releases) 목록에서 해당하는 릴리스를 찾을 수 있습니다.

에서 데이터 키 캐싱을 사용하는 전체 예제와 테스트된 예제는 다음을 AWS Encryption SDK참조하세요.
+ C/C\$1\$1: [caching\$1cmm.cpp](https://github.com/aws/aws-encryption-sdk-c/blob/master/examples/caching_cmm.cpp)
+ Java: [SimpleDataKeyCachingExample.java](https://github.com/aws/aws-encryption-sdk-java/blob/master/src/examples/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExample.java)
+ JavaScript 브라우저: [caching\$1cmm.ts](https://github.com/aws/aws-encryption-sdk-javascript/blob/master/modules/example-browser/src/caching_cmm.ts)
+ JavaScript Node.js: [caching\$1cmm.ts](https://github.com/aws/aws-encryption-sdk-javascript/blob/master/modules/example-node/src/caching_cmm.ts)
+ Python: [data\$1key\$1caching\$1basic.py](https://github.com/aws/aws-encryption-sdk-python/blob/master/examples/src/legacy/data_key_caching_basic.py)

[AWS Encryption SDK for .NET](dot-net.md)은 데이터 키 캐싱을 지원하지 않습니다.

**Topics**
+ [

## 데이터 키 캐싱 사용: 단계별
](#implement-caching-steps)
+ [

## 데이터 키 캐싱 예제: 문자열 암호화
](#caching-example-encrypt-string)

## 데이터 키 캐싱 사용: 단계별
<a name="implement-caching-steps"></a>

이 단계별 지침은 데이터 키 캐싱을 구현하는 데 필요한 구성 요소를 생성하는 방법을 보여줍니다.
+ [데이터 키 캐시를 생성합니다](data-caching-details.md#simplecache). 이 예제에서는가 AWS Encryption SDK 제공하는 로컬 캐시를 사용합니다. 캐시를 10개의 데이터 키로 제한합니다.

   

------
#### [ C ]

  ```
  // Cache capacity (maximum number of entries) is required
  size_t cache_capacity = 10; 
  struct aws_allocator *allocator = aws_default_allocator();
  
  struct aws_cryptosdk_materials_cache *cache = aws_cryptosdk_materials_cache_local_new(allocator, cache_capacity);
  ```

------
#### [ Java ]

  다음 예제에서는의 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x*는 데이터 키 캐싱 CMM을 더 AWS Encryption SDK for Java 이상 사용하지 않습니다. 버전 3.*x*에서는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 사용할 수도 있습니다.

  ```
  // Cache capacity (maximum number of entries) is required
  int MAX_CACHE_SIZE = 10; 
  
  CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(MAX_CACHE_SIZE);
  ```

------
#### [ JavaScript Browser ]

  ```
  const capacity = 10
  
  const cache = getLocalCryptographicMaterialsCache(capacity)
  ```

------
#### [ JavaScript Node.js ]

  ```
  const capacity = 10
  
  const cache = getLocalCryptographicMaterialsCache(capacity)
  ```

------
#### [ Python ]

  ```
  # Cache capacity (maximum number of entries) is required
  MAX_CACHE_SIZE = 10
  
  cache = aws_encryption_sdk.LocalCryptoMaterialsCache(MAX_CACHE_SIZE)
  ```

------

   
+ [마스터 키 공급자](concepts.md#master-key-provider)(Java 및 Python) 또는 [키링](concepts.md#keyring)(C 및 JavaScript)을 생성합니다. 이 예제에서는 AWS Key Management Service (AWS KMS) 마스터 키 공급자 또는 호환되는 [AWS KMS 키링](use-kms-keyring.md)을 사용합니다.

   

------
#### [ C ]

  ```
  // Create an AWS KMS keyring
  //   The input is the Amazon Resource Name (ARN) 
  //   of an AWS KMS key
  struct aws_cryptosdk_keyring *kms_keyring = Aws::Cryptosdk::KmsKeyring::Builder().Build(kms_key_arn);
  ```

------
#### [ Java ]

  다음 예제에서는의 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x*는 데이터 키 캐싱 CMM을 더 AWS Encryption SDK for Java 이상 사용하지 않습니다. 버전 3.*x*에서는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 사용할 수도 있습니다.

  ```
  // Create an AWS KMS master key provider
  //   The input is the Amazon Resource Name (ARN) 
  //   of an AWS KMS key
  MasterKeyProvider<KmsMasterKey> keyProvider = KmsMasterKeyProvider.builder().buildStrict(kmsKeyArn);
  ```

------
#### [ JavaScript Browser ]

  브라우저에 보안 인증을 안전하게 입력해야 합니다. 이 예는 런타임 시 보안 인증을 확인하는 webpack(kms.webpack.config)에서 보안 인증을 정의합니다. AWS KMS 클라이언트 및 자격 증명에서 AWS KMS 클라이언트 공급자 인스턴스를 생성합니다. 그런 다음 키링을 생성할 때 클라이언트 공급자를 AWS KMS key (`generatorKeyId)`.

  ```
  const { accessKeyId, secretAccessKey, sessionToken } = credentials
  
  const clientProvider = getClient(KMS, {
      credentials: {
        accessKeyId,
        secretAccessKey,
        sessionToken
      }
    })
  
  /*  Create an AWS KMS keyring
   *  You must configure the AWS KMS keyring with at least one AWS KMS key
  *  The input is the Amazon Resource Name (ARN) 
   */ of an AWS KMS key
  const keyring = new KmsKeyringBrowser({
      clientProvider,
      generatorKeyId,
      keyIds,
    })
  ```

------
#### [ JavaScript Node.js ]

  ```
  /* Create an AWS KMS keyring
   *   The input is the Amazon Resource Name (ARN) 
  */   of an AWS KMS key
  const keyring = new KmsKeyringNode({ generatorKeyId })
  ```

------
#### [ Python ]

  ```
  # Create an AWS KMS master key provider
  #  The input is the Amazon Resource Name (ARN) 
  #  of an AWS KMS key
  key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[kms_key_arn])
  ```

------

   
+ [캐싱 암호화 자료 관리자](data-caching-details.md#caching-cmm)(캐싱 CMM)를 생성합니다.

   

  캐싱 CMM을 캐시 및 마스터 키 공급자 또는 키링과 연결합니다. 그런 다음 캐싱 CMM에서 [캐시 보안 임계값을 설정](thresholds.md)합니다.

   

------
#### [ C ]

  에서는 기본 CMM과 같은 기본 CMM 또는 키링에서 캐싱 CMM을 생성할 AWS Encryption SDK for C수 있습니다. 이 예는 키링에서 캐싱 CMM을 생성합니다.

  캐싱 CMM을 생성한 후 키링 및 캐시에 대한 참조를 릴리스할 수 있습니다. 자세한 내용은 [참조 카운트](c-language-using.md#c-language-using-release)을 참조하세요.

  ```
  // Create the caching CMM
  //   Set the partition ID to NULL.
  //   Set the required maximum age value to 60 seconds.
  struct aws_cryptosdk_cmm *caching_cmm = aws_cryptosdk_caching_cmm_new_from_keyring(allocator, cache, kms_keyring, NULL, 60, AWS_TIMESTAMP_SECS);
  
  // Add an optional message threshold
  //   The cached data key will not be used for more than 10 messages.
  aws_status = aws_cryptosdk_caching_cmm_set_limit_messages(caching_cmm, 10);
  
  // Release your references to the cache and the keyring.
  aws_cryptosdk_materials_cache_release(cache);
  aws_cryptosdk_keyring_release(kms_keyring);
  ```

------
#### [ Java ]

  다음 예제에서는의 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x* AWS Encryption SDK for Java 는 데이터 키 캐싱을 지원하지 않지만 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 지원합니다.

  ```
  /*
   * Security thresholds
   *   Max entry age is required. 
   *   Max messages (and max bytes) per entry are optional
   */
  int MAX_ENTRY_AGE_SECONDS = 60;
  int MAX_ENTRY_MSGS = 10;
         
  //Create a caching CMM
  CryptoMaterialsManager cachingCmm =
      CachingCryptoMaterialsManager.newBuilder().withMasterKeyProvider(keyProvider)
                                   .withCache(cache)
                                   .withMaxAge(MAX_ENTRY_AGE_SECONDS, TimeUnit.SECONDS)
                                   .withMessageUseLimit(MAX_ENTRY_MSGS)
                                   .build();
  ```

------
#### [ JavaScript Browser ]

  ```
  /*
   * Security thresholds
   *   Max age (in milliseconds) is required.
   *   Max messages (and max bytes) per entry are optional.
   */
  const maxAge = 1000 * 60
  const maxMessagesEncrypted = 10
  
  /* Create a caching CMM from a keyring  */
  const cachingCmm = new WebCryptoCachingMaterialsManager({
    backingMaterials: keyring,
    cache,
    maxAge,
    maxMessagesEncrypted
  })
  ```

------
#### [ JavaScript Node.js ]

  ```
  /*
   * Security thresholds
   *   Max age (in milliseconds) is required.
   *   Max messages (and max bytes) per entry are optional.
   */
  const maxAge = 1000 * 60
  const maxMessagesEncrypted = 10
  
  /* Create a caching CMM from a keyring  */
  const cachingCmm = new NodeCachingMaterialsManager({
    backingMaterials: keyring,
    cache,
    maxAge,
    maxMessagesEncrypted
  })
  ```

------
#### [ Python ]

  ```
  # Security thresholds
  #   Max entry age is required. 
  #   Max messages (and max bytes) per entry are optional
  #
  MAX_ENTRY_AGE_SECONDS = 60.0
  MAX_ENTRY_MESSAGES = 10
         
  # Create a caching CMM
  caching_cmm = CachingCryptoMaterialsManager(
      master_key_provider=key_provider,
      cache=cache,
      max_age=MAX_ENTRY_AGE_SECONDS,
      max_messages_encrypted=MAX_ENTRY_MESSAGES
  )
  ```

------

더 이상 수행할 작업이 없습니다. 그런 다음에서 캐시를 AWS Encryption SDK 관리하도록 하거나 자체 캐시 관리 로직을 추가합니다.

호출에서 데이터 키 캐싱을 사용하여 데이터를 암호화하거나 복호화하려는 경우 마스터 키 공급자 또는 다른 CMM 대신에 사용자의 캐싱 CMM을 지정합니다.

**참고**  
데이터 스트림이나, 크기를 알 수 없는 데이터를 암호화하는 경우 요청에서 데이터 크기를 지정해야 합니다. AWS Encryption SDK 는 알 수 없는 크기의 데이터를 암호화할 때 데이터 키 캐싱을 사용하지 않습니다.

------
#### [ C ]

에서 캐싱 CMM으로 세션을 AWS Encryption SDK for C생성한 다음 세션을 처리합니다.

기본적으로 메시지 크기를 알 수 없고 제한이 없는 경우는 데이터 키를 캐싱하지 AWS Encryption SDK 않습니다. 정확한 데이터 크기를 모를 때 캐싱을 허용하려면 `aws_cryptosdk_session_set_message_bound` 메서드를 사용하여 메시지의 최대 크기를 설정합니다. 범위를 예상 메시지 크기보다 크게 설정합니다. 실제 메시지 크기가 범위를 초과하면 암호화 작업이 실패합니다.

```
/* Create a session with the caching CMM. Set the session mode to encrypt. */
struct aws_cryptosdk_session *session = aws_cryptosdk_session_new_from_cmm_2(allocator, AWS_CRYPTOSDK_ENCRYPT, caching_cmm);

/* Set a message bound of 1000 bytes */
aws_status = aws_cryptosdk_session_set_message_bound(session, 1000);

/* Encrypt the message using the session with the caching CMM */
aws_status = aws_cryptosdk_session_process(
             session, output_buffer, output_capacity, &output_produced, input_buffer, input_len, &input_consumed);

/* Release your references to the caching CMM and the session. */
aws_cryptosdk_cmm_release(caching_cmm);
aws_cryptosdk_session_destroy(session);
```

------
#### [ Java ]

다음 예제에서는 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x*는 데이터 키 캐싱 CMM을 더 AWS Encryption SDK for Java 이상 사용하지 않습니다. 버전 3.*x*에서는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 사용할 수도 있습니다.

```
// When the call to encryptData specifies a caching CMM,
// the encryption operation uses the data key cache
final AwsCrypto encryptionSdk = AwsCrypto.standard();
return encryptionSdk.encryptData(cachingCmm, plaintext_source).getResult();
```

------
#### [ JavaScript Browser ]

```
const { result } = await encrypt(cachingCmm, plaintext)
```

------
#### [ JavaScript Node.js ]

 AWS Encryption SDK for JavaScript for Node.js에서 캐싱 CMM을 사용하는 경우 `encrypt` 메서드에는 일반 텍스트의 길이가 필요합니다. 제공하지 않으면 데이터 키가 캐시되지 않습니다. 길이는 제공해도 입력한 일반 텍스트 데이터가 해당 길이를 초과하면 암호화 작업이 실패합니다. 데이터를 스트리밍할 때와 같이 일반 텍스트의 정확한 길이를 모르는 경우 예상되는 가장 큰 값을 제공합니다.

```
const { result } = await encrypt(cachingCmm, plaintext, { plaintextLength: plaintext.length })
```

------
#### [ Python ]

```
# Set up an encryption client
client = aws_encryption_sdk.EncryptionSDKClient()

# When the call to encrypt specifies a caching CMM,
# the encryption operation uses the data key cache
#
encrypted_message, header = client.encrypt(
    source=plaintext_source,
    materials_manager=caching_cmm
)
```

------

## 데이터 키 캐싱 예제: 문자열 암호화
<a name="caching-example-encrypt-string"></a>

이 간단한 코드 예제는 문자열을 암호화할 때 데이터 키 캐싱을 사용합니다. [단계별 프로시저](#implement-caching-steps)의 코드를 실행 가능한 테스트 코드로 결합합니다.

이 예제에서는 [로컬 캐시](data-caching-details.md#simplecache) 및 AWS KMS key에 대한 [마스터 키 공급자](concepts.md#master-key-provider) 또는 [키링](concepts.md#keyring)을 생성합니다. 그런 다음 로컬 캐시 및 마스터 키 공급자 또는 키링을 사용하여 적절한 [보안 임계값](thresholds.md)이 있는 캐싱 CMM을 생성합니다. Java 및 Python에서 암호화 요청은 캐싱 CMM, 암호화할 일반 텍스트 데이터 및 [암호화 컨텍스트](data-caching-details.md#caching-encryption-context)를 지정합니다. C에서는 세션에 캐싱 CMM이 지정되며, 세션은 암호화 요청에 제공됩니다.

이 예제를 실행하려면 [AWS KMS key의 Amazon 리소스 이름(ARN)](https://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html)을 제공해야 합니다. 데이터 키를 생성하려면 [AWS KMS key를 사용할 수 있는 권한](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-users)이 있어야 합니다.

데이터 키 캐시 생성 및 사용에 대한 자세한 실제 예제는 [데이터 키 캐싱 예제 코드](sample-cache-example-code.md) 섹션을 참조하세요.

------
#### [ C ]

```
/*
 * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use
 * this file except in compliance with the License. A copy of the License is
 * located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <aws/cryptosdk/cache.h>
#include <aws/cryptosdk/cpp/kms_keyring.h>
#include <aws/cryptosdk/session.h>

void encrypt_with_caching(
    uint8_t *ciphertext,     // output will go here (assumes ciphertext_capacity bytes already allocated)
    size_t *ciphertext_len,  // length of output will go here
    size_t ciphertext_capacity,
    const char *kms_key_arn,
    int max_entry_age,
    int cache_capacity) {
    const uint64_t MAX_ENTRY_MSGS = 100;

    struct aws_allocator *allocator = aws_default_allocator();
    
    // Load error strings for debugging
    aws_cryptosdk_load_error_strings();

    // Create a keyring
    struct aws_cryptosdk_keyring *kms_keyring = Aws::Cryptosdk::KmsKeyring::Builder().Build(kms_key_arn);

    // Create a cache
    struct aws_cryptosdk_materials_cache *cache = aws_cryptosdk_materials_cache_local_new(allocator, cache_capacity);

    // Create a caching CMM
    struct aws_cryptosdk_cmm *caching_cmm = aws_cryptosdk_caching_cmm_new_from_keyring(
        allocator, cache, kms_keyring, NULL, max_entry_age, AWS_TIMESTAMP_SECS);
    if (!caching_cmm) abort();

    if (aws_cryptosdk_caching_cmm_set_limit_messages(caching_cmm, MAX_ENTRY_MSGS)) abort();

    // Create a session
    struct aws_cryptosdk_session *session =        
        aws_cryptosdk_session_new_from_cmm_2(allocator, AWS_CRYPTOSDK_ENCRYPT, caching_cmm);
    if (!session) abort();

    // Encryption context
    struct aws_hash_table *enc_ctx = aws_cryptosdk_session_get_enc_ctx_ptr_mut(session);
    if (!enc_ctx) abort();
    AWS_STATIC_STRING_FROM_LITERAL(enc_ctx_key, "purpose");
    AWS_STATIC_STRING_FROM_LITERAL(enc_ctx_value, "test");
    if (aws_hash_table_put(enc_ctx, enc_ctx_key, (void *)enc_ctx_value, NULL)) abort();

    // Plaintext data to be encrypted
    const char *my_data = "My plaintext data";
    size_t my_data_len  = strlen(my_data);
    if (aws_cryptosdk_session_set_message_size(session, my_data_len)) abort();

    // When the session uses a caching CMM, the encryption operation uses the data key cache
    // specified in the caching CMM.
    size_t bytes_read;
    if (aws_cryptosdk_session_process(
            session,
            ciphertext,
            ciphertext_capacity,
            ciphertext_len,
            (const uint8_t *)my_data,
            my_data_len,
            &bytes_read))
        abort();
    if (!aws_cryptosdk_session_is_done(session) || bytes_read != my_data_len) abort();

    aws_cryptosdk_session_destroy(session);
    aws_cryptosdk_cmm_release(caching_cmm);
    aws_cryptosdk_materials_cache_release(cache);
    aws_cryptosdk_keyring_release(kms_keyring);
}
```

------
#### [ Java ]

다음 예제에서는의 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x*는 데이터 키 캐싱 CMM을 더 AWS Encryption SDK for Java 이상 사용하지 않습니다. 버전 3.*x*에서는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 사용할 수도 있습니다.

```
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazonaws.crypto.examples;

import com.amazonaws.encryptionsdk.AwsCrypto;
import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
import com.amazonaws.encryptionsdk.MasterKeyProvider;
import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager;
import com.amazonaws.encryptionsdk.caching.CryptoMaterialsCache;
import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache;
import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKey;
import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * Encrypts a string using an &KMS; key and data key caching
 *
 * <p>
 * Arguments:
 * <ol>
 * <li>KMS Key ARN: To find the Amazon Resource Name of your &KMS; key,
 *     see 'Find the key ID and ARN' at https://docs.aws.amazon.com/kms/latest/developerguide/find-cmk-id-arn.html
 * <li>Max entry age: Maximum time (in seconds) that a cached entry can be used
 * <li>Cache capacity: Maximum number of entries in the cache
 * </ol>
 */
public class SimpleDataKeyCachingExample {

    /*
     * Security thresholds
     *   Max entry age is required.
     *   Max messages (and max bytes) per data key are optional
     */
    private static final int MAX_ENTRY_MSGS = 100;

    public static byte[] encryptWithCaching(String kmsKeyArn, int maxEntryAge, int cacheCapacity) {
        // Plaintext data to be encrypted
        byte[] myData = "My plaintext data".getBytes(StandardCharsets.UTF_8);

        // Encryption context
        // Most encrypted data should have an associated encryption context
        // to protect integrity. This sample uses placeholder values.
        // For more information see:
        // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management
        final Map<String, String> encryptionContext = Collections.singletonMap("purpose", "test");

        // Create a master key provider
        MasterKeyProvider<KmsMasterKey> keyProvider = KmsMasterKeyProvider.builder()
            .buildStrict(kmsKeyArn);

        // Create a cache
        CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(cacheCapacity);

        // Create a caching CMM
        CryptoMaterialsManager cachingCmm =
            CachingCryptoMaterialsManager.newBuilder().withMasterKeyProvider(keyProvider)
                .withCache(cache)
                .withMaxAge(maxEntryAge, TimeUnit.SECONDS)
                .withMessageUseLimit(MAX_ENTRY_MSGS)
                .build();

        // When the call to encryptData specifies a caching CMM,
        // the encryption operation uses the data key cache
        final AwsCrypto encryptionSdk = AwsCrypto.standard();
        return encryptionSdk.encryptData(cachingCmm, myData, encryptionContext).getResult();
    }
}
```

------
#### [ JavaScript Browser ]

```
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

/* This is a simple example of using a caching CMM with a KMS keyring
 * to encrypt and decrypt using the AWS Encryption SDK for Javascript in a browser.
 */

import {
  KmsKeyringBrowser,
  KMS,
  getClient,
  buildClient,
  CommitmentPolicy,
  WebCryptoCachingMaterialsManager,
  getLocalCryptographicMaterialsCache,
} from '@aws-crypto/client-browser'
import { toBase64 } from '@aws-sdk/util-base64-browser'

/* This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
 * which enforces that this client only encrypts using committing algorithm suites
 * and enforces that this client
 * will only decrypt encrypted messages
 * that were created with a committing algorithm suite.
 * This is the default commitment policy
 * if you build the client with `buildClient()`.
 */
const { encrypt, decrypt } = buildClient(
  CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)

/* This is injected by webpack.
 * The webpack.DefinePlugin or @aws-sdk/karma-credential-loader will replace the values when bundling.
 * The credential values are pulled from @aws-sdk/credential-provider-node
 * Use any method you like to get credentials into the browser.
 * See kms.webpack.config
 */
declare const credentials: {
  accessKeyId: string
  secretAccessKey: string
  sessionToken: string
}

/* This is done to facilitate testing. */
export async function testCachingCMMExample() {
  /* This example uses an &KMS; keyring. The generator key in a &KMS; keyring generates and encrypts the data key.
   * The caller needs kms:GenerateDataKey permission on the &KMS; key in generatorKeyId.
   */
  const generatorKeyId =
    'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'

  /* Adding additional KMS keys that can decrypt.
   * The caller must have kms:Encrypt permission for every &KMS; key in keyIds.
   * You might list several keys in different AWS Regions.
   * This allows you to decrypt the data in any of the represented Regions.
   * In this example, the generator key
   * and the additional key are actually the same &KMS; key.
   * In `generatorId`, this &KMS; key is identified by its alias ARN.
   * In `keyIds`, this &KMS; key is identified by its key ARN.
   * In practice, you would specify different &KMS; keys,
   * or omit the `keyIds` parameter.
   * This is *only* to demonstrate how the &KMS; key ARNs are configured.
   */
  const keyIds = [
    'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f',
  ]

  /* Need a client provider that will inject correct credentials.
   * The credentials here are injected by webpack from your environment bundle is created
   * The credential values are pulled using @aws-sdk/credential-provider-node.
   * See kms.webpack.config
   * You should inject your credential into the browser in a secure manner
   * that works with your application.
   */
  const { accessKeyId, secretAccessKey, sessionToken } = credentials

  /* getClient takes a KMS client constructor
   * and optional configuration values.
   * The credentials can be injected here,
   * because browsers do not have a standard credential discovery process the way Node.js does.
   */
  const clientProvider = getClient(KMS, {
    credentials: {
      accessKeyId,
      secretAccessKey,
      sessionToken,
    },
  })

  /* You must configure the KMS keyring with your &KMS; keys */
  const keyring = new KmsKeyringBrowser({
    clientProvider,
    generatorKeyId,
    keyIds,
  })

  /* Create a cache to hold the data keys (and related cryptographic material).
   * This example uses the local cache provided by the Encryption SDK.
   * The `capacity` value represents the maximum number of entries
   * that the cache can hold.
   * To make room for an additional entry,
   * the cache evicts the oldest cached entry.
   * Both encrypt and decrypt requests count independently towards this threshold.
   * Entries that exceed any cache threshold are actively removed from the cache.
   * By default, the SDK checks one item in the cache every 60 seconds (60,000 milliseconds).
   * To change this frequency, pass in a `proactiveFrequency` value
   * as the second parameter. This value is in milliseconds.
   */
  const capacity = 100
  const cache = getLocalCryptographicMaterialsCache(capacity)

  /* The partition name lets multiple caching CMMs share the same local cryptographic cache.
   * By default, the entries for each CMM are cached separately. However, if you want these CMMs to share the cache,
   * use the same partition name for both caching CMMs.
   * If you don't supply a partition name, the Encryption SDK generates a random name for each caching CMM.
   * As a result, sharing elements in the cache MUST be an intentional operation.
   */
  const partition = 'local partition name'

  /* maxAge is the time in milliseconds that an entry will be cached.
   * Elements are actively removed from the cache.
   */
  const maxAge = 1000 * 60

  /* The maximum number of bytes that will be encrypted under a single data key.
   * This value is optional,
   * but you should configure the lowest practical value.
   */
  const maxBytesEncrypted = 100

  /* The maximum number of messages that will be encrypted under a single data key.
   * This value is optional,
   * but you should configure the lowest practical value.
   */
  const maxMessagesEncrypted = 10

  const cachingCMM = new WebCryptoCachingMaterialsManager({
    backingMaterials: keyring,
    cache,
    partition,
    maxAge,
    maxBytesEncrypted,
    maxMessagesEncrypted,
  })

  /* Encryption context is a *very* powerful tool for controlling
   * and managing access.
   * When you pass an encryption context to the encrypt function,
   * the encryption context is cryptographically bound to the ciphertext.
   * If you don't pass in the same encryption context when decrypting,
   * the decrypt function fails.
   * The encryption context is ***not*** secret!
   * Encrypted data is opaque.
   * You can use an encryption context to assert things about the encrypted data.
   * The encryption context helps you to determine
   * whether the ciphertext you retrieved is the ciphertext you expect to decrypt.
   * For example, if you are are only expecting data from 'us-west-2',
   * the appearance of a different AWS Region in the encryption context can indicate malicious interference.
   * See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
   *
   * Also, cached data keys are reused ***only*** when the encryption contexts passed into the functions are an exact case-sensitive match.
   * See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-caching-details.html#caching-encryption-context
   */
  const encryptionContext = {
    stage: 'demo',
    purpose: 'simple demonstration app',
    origin: 'us-west-2',
  }

  /* Find data to encrypt. */
  const plainText = new Uint8Array([1, 2, 3, 4, 5])

  /* Encrypt the data.
   * The caching CMM only reuses data keys
   * when it know the length (or an estimate) of the plaintext.
   * However, in the browser,
   * you must provide all of the plaintext to the encrypt function.
   * Therefore, the encrypt function in the browser knows the length of the plaintext
   * and does not accept a plaintextLength option.
   */
  const { result } = await encrypt(cachingCMM, plainText, { encryptionContext })

  /* Log the plain text
   * only for testing and to show that it works.
   */
  console.log('plainText:', plainText)
  document.write('</br>plainText:' + plainText + '</br>')

  /* Log the base64-encoded result
   * so that you can try decrypting it with another AWS Encryption SDK implementation.
   */
  const resultBase64 = toBase64(result)
  console.log(resultBase64)
  document.write(resultBase64)

  /* Decrypt the data.
   * NOTE: This decrypt request will not use the data key
   * that was cached during the encrypt operation.
   * Data keys for encrypt and decrypt operations are cached separately.
   */
  const { plaintext, messageHeader } = await decrypt(cachingCMM, result)

  /* Grab the encryption context so you can verify it. */
  const { encryptionContext: decryptedContext } = messageHeader

  /* Verify the encryption context.
   * If you use an algorithm suite with signing,
   * the Encryption SDK adds a name-value pair to the encryption context that contains the public key.
   * Because the encryption context might contain additional key-value pairs,
   * do not include a test that requires that all key-value pairs match.
   * Instead, verify that the key-value pairs that you supplied to the `encrypt` function are included in the encryption context that the `decrypt` function returns.
   */
  Object.entries(encryptionContext).forEach(([key, value]) => {
    if (decryptedContext[key] !== value)
      throw new Error('Encryption Context does not match expected values')
  })

  /* Log the clear message
   * only for testing and to show that it works.
   */
  document.write('</br>Decrypted:' + plaintext)
  console.log(plaintext)

  /* Return the values to make testing easy. */
  return { plainText, plaintext }
}
```

------
#### [ JavaScript Node.js ]

```
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import {
  KmsKeyringNode,
  buildClient,
  CommitmentPolicy,
  NodeCachingMaterialsManager,
  getLocalCryptographicMaterialsCache,
} from '@aws-crypto/client-node'

/* This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
 * which enforces that this client only encrypts using committing algorithm suites
 * and enforces that this client
 * will only decrypt encrypted messages
 * that were created with a committing algorithm suite.
 * This is the default commitment policy
 * if you build the client with `buildClient()`.
 */
const { encrypt, decrypt } = buildClient(
  CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)

export async function cachingCMMNodeSimpleTest() {
  /* An &KMS; key is required to generate the data key.
   * You need kms:GenerateDataKey permission on the &KMS; key in generatorKeyId.
   */
  const generatorKeyId =
    'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'

  /* Adding alternate &KMS; keys that can decrypt.
   * Access to kms:Encrypt is required for every &KMS; key in keyIds.
   * You might list several keys in different AWS Regions.
   * This allows you to decrypt the data in any of the represented Regions.
   * In this example, the generator key
   * and the additional key are actually the same &KMS; key.
   * In `generatorId`, this &KMS; key is identified by its alias ARN.
   * In `keyIds`, this &KMS; key is identified by its key ARN.
   * In practice, you would specify different &KMS; keys,
   * or omit the `keyIds` parameter.
   * This is *only* to demonstrate how the &KMS; key ARNs are configured.
   */
  const keyIds = [
    'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f',
  ]

  /* The &KMS; keyring must be configured with the desired &KMS; keys
   * This example passes the keyring to the caching CMM
   * instead of using it directly.
   */
  const keyring = new KmsKeyringNode({ generatorKeyId, keyIds })

  /* Create a cache to hold the data keys (and related cryptographic material).
   * This example uses the local cache provided by the Encryption SDK.
   * The `capacity` value represents the maximum number of entries
   * that the cache can hold.
   * To make room for an additional entry,
   * the cache evicts the oldest cached entry.
   * Both encrypt and decrypt requests count independently towards this threshold.
   * Entries that exceed any cache threshold are actively removed from the cache.
   * By default, the SDK checks one item in the cache every 60 seconds (60,000 milliseconds).
   * To change this frequency, pass in a `proactiveFrequency` value
   * as the second parameter. This value is in milliseconds.
   */
  const capacity = 100
  const cache = getLocalCryptographicMaterialsCache(capacity)

  /* The partition name lets multiple caching CMMs share the same local cryptographic cache.
   * By default, the entries for each CMM are cached separately. However, if you want these CMMs to share the cache,
   * use the same partition name for both caching CMMs.
   * If you don't supply a partition name, the Encryption SDK generates a random name for each caching CMM.
   * As a result, sharing elements in the cache MUST be an intentional operation.
   */
  const partition = 'local partition name'

  /* maxAge is the time in milliseconds that an entry will be cached.
   * Elements are actively removed from the cache.
   */
  const maxAge = 1000 * 60

  /* The maximum amount of bytes that will be encrypted under a single data key.
   * This value is optional,
   * but you should configure the lowest value possible.
   */
  const maxBytesEncrypted = 100

  /* The maximum number of messages that will be encrypted under a single data key.
   * This value is optional,
   * but you should configure the lowest value possible.
   */
  const maxMessagesEncrypted = 10

  const cachingCMM = new NodeCachingMaterialsManager({
    backingMaterials: keyring,
    cache,
    partition,
    maxAge,
    maxBytesEncrypted,
    maxMessagesEncrypted,
  })

  /* Encryption context is a *very* powerful tool for controlling
   * and managing access.
   * When you pass an encryption context to the encrypt function,
   * the encryption context is cryptographically bound to the ciphertext.
   * If you don't pass in the same encryption context when decrypting,
   * the decrypt function fails.
   * The encryption context is ***not*** secret!
   * Encrypted data is opaque.
   * You can use an encryption context to assert things about the encrypted data.
   * The encryption context helps you to determine
   * whether the ciphertext you retrieved is the ciphertext you expect to decrypt.
   * For example, if you are are only expecting data from 'us-west-2',
   * the appearance of a different AWS Region in the encryption context can indicate malicious interference.
   * See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
   *
   * Also, cached data keys are reused ***only*** when the encryption contexts passed into the functions are an exact case-sensitive match.
   * See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-caching-details.html#caching-encryption-context
   */
  const encryptionContext = {
    stage: 'demo',
    purpose: 'simple demonstration app',
    origin: 'us-west-2',
  }

  /* Find data to encrypt.  A simple string. */
  const cleartext = 'asdf'

  /* Encrypt the data.
   * The caching CMM only reuses data keys
   * when it know the length (or an estimate) of the plaintext.
   * If you do not know the length,
   * because the data is a stream
   * provide an estimate of the largest expected value.
   *
   * If your estimate is smaller than the actual plaintext length
   * the AWS Encryption SDK will throw an exception.
   *
   * If the plaintext is not a stream,
   * the AWS Encryption SDK uses the actual plaintext length
   * instead of any length you provide.
   */
  const { result } = await encrypt(cachingCMM, cleartext, {
    encryptionContext,
    plaintextLength: 4,
  })

  /* Decrypt the data.
   * NOTE: This decrypt request will not use the data key
   * that was cached during the encrypt operation.
   * Data keys for encrypt and decrypt operations are cached separately.
   */
  const { plaintext, messageHeader } = await decrypt(cachingCMM, result)

  /* Grab the encryption context so you can verify it. */
  const { encryptionContext: decryptedContext } = messageHeader

  /* Verify the encryption context.
   * If you use an algorithm suite with signing,
   * the Encryption SDK adds a name-value pair to the encryption context that contains the public key.
   * Because the encryption context might contain additional key-value pairs,
   * do not include a test that requires that all key-value pairs match.
   * Instead, verify that the key-value pairs that you supplied to the `encrypt` function are included in the encryption context that the `decrypt` function returns.
   */
  Object.entries(encryptionContext).forEach(([key, value]) => {
    if (decryptedContext[key] !== value)
      throw new Error('Encryption Context does not match expected values')
  })

  /* Return the values so the code can be tested. */
  return { plaintext, result, cleartext, messageHeader }
}
```

------
#### [ Python ]

```
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Example of encryption with data key caching."""
import aws_encryption_sdk
from aws_encryption_sdk import CommitmentPolicy


def encrypt_with_caching(kms_key_arn, max_age_in_cache, cache_capacity):
    """Encrypts a string using an &KMS; key and data key caching.

    :param str kms_key_arn: Amazon Resource Name (ARN) of the &KMS; key
    :param float max_age_in_cache: Maximum time in seconds that a cached entry can be used
    :param int cache_capacity: Maximum number of entries to retain in cache at once
    """
    # Data to be encrypted
    my_data = "My plaintext data"

    # Security thresholds
    #   Max messages (or max bytes per) data key are optional
    MAX_ENTRY_MESSAGES = 100

    # Create an encryption context
    encryption_context = {"purpose": "test"}

    # Set up an encryption client with an explicit commitment policy. Note that if you do not explicitly choose a
    # commitment policy, REQUIRE_ENCRYPT_REQUIRE_DECRYPT is used by default.
    client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)

    # Create a master key provider for the &KMS; key
    key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[kms_key_arn])

    # Create a local cache
    cache = aws_encryption_sdk.LocalCryptoMaterialsCache(cache_capacity)

    # Create a caching CMM
    caching_cmm = aws_encryption_sdk.CachingCryptoMaterialsManager(
        master_key_provider=key_provider,
        cache=cache,
        max_age=max_age_in_cache,
        max_messages_encrypted=MAX_ENTRY_MESSAGES,
    )

    # When the call to encrypt data specifies a caching CMM,
    # the encryption operation uses the data key cache specified
    # in the caching CMM
    encrypted_message, _header = client.encrypt(
        source=my_data, materials_manager=caching_cmm, encryption_context=encryption_context
    )

    return encrypted_message
```

------

# 캐시 보안 임계값 설정
<a name="thresholds"></a>

데이터 키 캐싱을 구현할 때는 [캐싱 CMM](data-caching-details.md#caching-cmm)이 적용하는 보안 임계값을 구성해야 합니다.

보안 임계값을 통해, 캐시된 각 데이터 키가 사용되는 기간과 각 데이터 키에서 보호되는 데이터의 양을 제한할 수 있습니다. 캐싱 CMM은 캐시 항목이 모든 보안 임계값을 준수하는 경우에만 캐시된 데이터 키를 반환합니다. 캐시 항목이 임계값을 초과하는 경우 해당 항목은 현재 작업에 사용되지 않으며 가능한 한 빨리 캐시에서 제거됩니다. 각 데이터 키를 처음 사용한 경우(캐싱 전)에는 이 임계값이 적용되지 않습니다.

일반적으로 비용 및 성능 목표를 달성하는 데 필요한 최소한의 캐싱을 사용합니다.

 AWS Encryption SDK 만 키 [파생 함수를 사용하여 암호화된 데이터 키를](https://en.wikipedia.org/wiki/Key_derivation_function) 캐싱합니다. 또한 일부 임계값에 대한 상한선을 설정합니다. 이러한 제한은 데이터 키가 암호화 한도를 초과하여 재사용되지 않도록 합니다. 그러나 일반 텍스트 데이터 키는 (기본적으로 메모리 내에) 캐시되므로, 키가 저장되는 시간을 최소화하도록 합니다. 또한 키가 손상될 경우 노출될 수 있는 데이터를 제한하도록 합니다.

캐시 보안 임계값 설정 예제는 AWS 보안 블로그의 [AWS Encryption SDK: 데이터 키 캐싱이 애플리케이션에 적합한지 결정하는 방법을 참조하세요](https://aws.amazon.com/blogs/security/aws-encryption-sdk-how-to-decide-if-data-key-caching-is-right-for-your-application/).

**참고**  
캐싱 CMM은 다음 임계값을 모두 적용합니다. 옵션 값을 지정하지 않으면 캐싱 CMM에 기본값이 사용됩니다.  
데이터 키 캐싱을 일시적으로 비활성화하기 위해 AWS Encryption SDK 의 Java 및 Python 구현에서는 *null 암호화 자료 캐시*(null 캐시)를 제공합니다. null 캐시는 모든 `GET` 요청에 대해 누락을 반환하고 `PUT` 요청에 응답하지 않습니다. [캐시 용량](data-caching-details.md#simplecache) 또는 보안 임계값을 0으로 설정하는 대신 null 캐시를 사용하는 것이 좋습니다. 자세한 내용은 [Java](https://aws.github.io/aws-encryption-sdk-java/com/amazonaws/encryptionsdk/caching/NullCryptoMaterialsCache.html) 및 [Python](https://aws-encryption-sdk-python.readthedocs.io/en/latest/generated/aws_encryption_sdk.caches.null.html)에서 null 캐시를 참조하세요.

**최대 기간(필수)**  
추가된 시점부터 시작하여 캐시된 항목을 사용할 수 있는 기간을 결정합니다. 이 값은 필수입니다. 0보다 큰 값을 입력합니다. 는 최대 수명 값을 제한하지 AWS Encryption SDK 않습니다.  
의 모든 언어 구현은 밀리초를 AWS Encryption SDK for JavaScript사용하는를 제외하고 최대 수명을 초 단위로 AWS Encryption SDK 정의합니다.  
애플리케이션이 캐시를 활용할 수 있는 가장 짧은 간격을 사용합니다. 최대 기간 임계값을 키 교체 정책처럼 사용할 수 있습니다. 이를 사용하여 데이터 키의 재사용을 제한하고, 암호화 자료의 노출을 최소화하며, 캐시되는 동안 정책이 변경되었을 수 있는 데이터 키를 제거할 수 있습니다.

**최대 메시지 암호화(선택 사항)**  
캐시된 데이터 키가 암호화할 수 있는 최대 메시지 수를 지정합니다. 이 값은 선택 사항입니다. 1과 2^32개 메시지 사이의 값을 입력합니다. 기본값은 메시지 2^32개입니다.  
캐시된 각 키로 보호되는 메시지 수는, 재사용을 통해 값을 가져올 수 있을 만큼 크지만 키가 손상될 경우 노출될 수 있는 메시지 수를 제한할 수 있을 만큼 작게 설정합니다.

**최대 바이트 암호화(선택 사항)**  
캐시된 데이터 키가 암호화할 수 있는 최대 바이트 수를 지정합니다. 이 값은 선택 사항입니다. 0과 2^63 - 1 사이의 값을 입력합니다. 기본값은 2^63 - 1입니다. 값이 0이면 빈 메시지 문자열을 암호화하는 경우에만 데이터 키 캐싱을 사용할 수 있습니다.  
이 임계값을 평가할 때 현재 요청의 바이트가 포함됩니다. 처리된 바이트와 현재 바이트가 임계값을 초과하면 캐시된 데이터 키가 더 적은 요청에서 사용되었을 수도 있지만 캐시에서 제거됩니다.

# 데이터 키 캐싱 세부 정보
<a name="data-caching-details"></a>

대부분의 애플리케이션은 사용자 지정 코드를 작성하지 않고도 데이터 키 캐싱의 기본 구현을 사용할 수 있습니다. 이 섹션에서는 기본 구현과, 옵션에 대한 몇 가지 세부 정보를 설명합니다.

**Topics**
+ [

## 데이터 키 캐싱의 작동 방식
](#how-caching-works)
+ [

## 암호화 자료 캐시 생성
](#simplecache)
+ [

## 암호화 자료 캐싱 관리자 생성
](#caching-cmm)
+ [

## 데이터 키 캐시 항목에는 무엇이 들어 있나요?
](#cache-entries)
+ [

## 암호화 컨텍스트: 캐시 항목을 선택하는 방법
](#caching-encryption-context)
+ [

## 내 애플리케이션이 캐시된 데이터 키를 사용하고 있나요?
](#caching-effect)

## 데이터 키 캐싱의 작동 방식
<a name="how-caching-works"></a>

요청에서 데이터 키 캐싱을 사용하여 데이터를 암호화하거나 복호화하는 경우 AWS Encryption SDK 는 먼저 캐시에서 요청과 일치하는 데이터 키를 검색합니다. 유효한 일치 항목을 찾으면 캐시된 데이터 키를 사용하여 데이터를 암호화합니다. 그러지 않으면 캐시가 없을 때와 마찬가지로 새 데이터 키가 생성됩니다.

스트리밍 데이터와 같이 크기를 알 수 없는 데이터에는 데이터 키 캐싱이 사용되지 않습니다. 이를 통해 캐싱 CMM이 [최대 바이트 임계값](thresholds.md)을 적절하게 적용할 수 있습니다. 이 동작을 방지하려면 메시지 크기를 암호화 요청에 추가합니다.

데이터 키 캐싱은 캐시 외에도 [캐싱 암호화 자료 관리자](#caching-cmm)(캐싱 CMM)를 사용합니다. 캐싱 CMM은 [캐시](#simplecache) 및 기본 [CMM](concepts.md#crypt-materials-manager)과 상호 작용하는 특수 [암호화 자료 관리자(CMM)](concepts.md#crypt-materials-manager)입니다. ([마스터 키 공급자](concepts.md#master-key-provider) 또는 키링을 지정하면 AWS Encryption SDK 에서 기본 CMM을 만듭니다.) 캐싱 CMM은 기본 CMM이 반환하는 데이터 키를 캐시합니다. 또한 캐싱 CMM은 사용자가 설정한 캐시 보안 임계값을 적용합니다.

캐시에서 잘못된 데이터 키가 선택되는 것을 방지하기 위해 모든 호환 가능한 캐싱 CMM은 캐시된 암호화 자료의 다음 속성이 구성 요소 요청과 일치해야 합니다.
+ [알고리즘 제품군](concepts.md#crypto-algorithm)
+ [암호화 컨텍스트](#caching-encryption-context)(비어 있는 경우에도)
+ 파티션 이름(캐싱 CMM을 식별하는 문자열)
+ (복호화 전용) 암호화된 데이터 키

**참고**  
는 [알고리즘 제품군](concepts.md#crypto-algorithm)이 키 [유도 함수를 사용하는 경우에만 데이터 키를](https://en.wikipedia.org/wiki/Key_derivation_function) AWS Encryption SDK 캐시합니다.

다음 워크플로는 데이터 키 캐싱을 사용하거나 사용하지 않고 데이터 암호화 요청을 처리하는 방법을 보여줍니다. 캐시와 캐싱 CMM을 포함하여 사용자가 생성한 캐싱 구성 요소가 프로세스에서 어떻게 사용되는지 보여줍니다.

### 캐싱을 사용하지 않고 데이터 암호화
<a name="workflow-wo-cache"></a>

캐싱을 사용하지 않고 암호화 자료를 가져오려면 다음을 수행합니다.

1. 애플리케이션이에 데이터 암호화 AWS Encryption SDK 를 요청합니다.

   요청은 마스터 키 공급자 또는 키링을 지정합니다. AWS Encryption SDK 는 사용자 마스터 키 또는 키링과 상호 작용하는 기본 CMM을 만듭니다.

1. 는 CMM에 암호화 자료를 AWS Encryption SDK 요청합니다(암호화 자료 가져오기).

1. CMM은 해당 [키링](concepts.md#keyring)(C 및 JavaScript) 또는 [마스터 키 공급자](concepts.md#master-key-provider)(Java 및 Python)에 암호화 자료를 요청합니다. 여기에는 AWS Key Management Service ()와 같은 암호화 서비스에 대한 호출이 포함될 수 있습니다AWS KMS. CMM은 암호화 자료를 AWS Encryption SDK에 반환합니다.

1. 는 일반 텍스트 데이터 키를 AWS Encryption SDK 사용하여 데이터를 암호화합니다. 암호화된 데이터와 암호화된 데이터 키를 [암호화된 메시지](concepts.md#message)에 저장하여 사용자에게 반환합니다.

![\[캐싱을 사용하지 않고 데이터 암호화\]](http://docs.aws.amazon.com/ko_kr/encryption-sdk/latest/developer-guide/images/encrypt-workflow-no-cache.png)


### 캐싱을 사용하여 데이터 암호화
<a name="workflow-with-cache"></a>

데이터 키 캐싱을 사용하여 암호화 자료를 가져오려면 다음을 수행합니다.

1. 애플리케이션이에 데이터 암호화 AWS Encryption SDK 를 요청합니다.

   요청은 기본 암호 구성 요소 관리자(CMM)와 연결된 [캐싱 암호 구성 요소 관리자(캐싱 CMM)](#caching-cmm)를 지정합니다. 마스터 키 공급자 또는 키링을 지정하면 AWS Encryption SDK 에서 기본 CMM을 만듭니다.

1. SDK는 지정된 캐싱 CMM에 암호화 자료를 요청합니다.

1. 캐싱 CMM은 캐시에서 암호화 자료를 요청합니다.

   1. 캐시가 일치하는 항목을 찾으면 일치하는 캐시 항목의 사용 기간 및 사용 값을 업데이트하고 캐시된 암호화 자료를 캐싱 CMM에 반환합니다.

      캐시 항목이 [보안 임계값](thresholds.md)을 준수하는 경우 캐싱 CMM은 해당 항목을 SDK에 반환합니다. 그러지 않으면 항목을 제거하라고 캐시에 지시하고 일치하는 항목이 없는 것처럼 진행합니다.

   1. 캐시에서 유효한 일치 항목을 찾을 수 없는 경우 캐싱 CMM은 기본 CMM에 새 데이터 키를 생성하도록 요청합니다.

      기본 CMM은 해당 키링(C 및 JavaScript) 또는 마스터 키 공급자(Java 및 Python)로부터 암호화 자료를 가져옵니다. 여기에는 AWS Key Management Service와 같은 서비스에 대한 호출이 포함될 수 있습니다. 기본 CMM은 데이터 키의 일반 텍스트 및 암호화된 사본을 캐싱 CMM에 반환합니다.

      캐싱 CMM은 새 암호화 자료를 캐시에 저장합니다.

1. 캐싱 CMM은 암호화 자료를 AWS Encryption SDK에 반환합니다.

1. 는 일반 텍스트 데이터 키를 AWS Encryption SDK 사용하여 데이터를 암호화합니다. 암호화된 데이터와 암호화된 데이터 키를 [암호화된 메시지](concepts.md#message)에 저장하여 사용자에게 반환합니다.

![\[데이터 키 캐싱을 사용하여 데이터 암호화\]](http://docs.aws.amazon.com/ko_kr/encryption-sdk/latest/developer-guide/images/encrypt-workflow-with-cache.png)


## 암호화 자료 캐시 생성
<a name="simplecache"></a>

는 데이터 키 캐싱에 사용되는 암호화 자료 캐시에 대한 요구 사항을 AWS Encryption SDK 정의합니다. 또한 구성 가능한 메모리 내 [최소 최근 사용(LRU) 캐시](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29)인 로컬 캐시도 제공합니다. 로컬 캐시의 인스턴스를 생성하려면 Java 및 Python에서 `LocalCryptoMaterialsCache` 생성자를 생성하거나, JavaScript에서 getLocalCryptographicMaterialsCache 함수 또는 C에서 `aws_cryptosdk_materials_cache_local_new` 생성자를 사용합니다.

로컬 캐시에는 캐시된 항목의 추가, 제거, 일치 및 캐시 유지 관리를 비롯한 기본 캐시 관리를 위한 로직이 포함되어 있습니다. 사용자 지정 캐시 관리 로직을 작성할 필요가 없습니다. 로컬 캐시를 그대로 사용하거나, 사용자 지정하거나, 호환되는 캐시로 대체할 수 있습니다.

로컬 캐시를 생성할 때 *용량*, 즉 캐시에 저장할 수 있는 최대 항목 수를 설정합니다. 이 설정을 사용하면 데이터 키 재사용이 제한되는 효율적인 캐시를 설계할 수 있습니다.

 AWS Encryption SDK for Java 및 *는 null 암호화 자료 캐시*(NullCryptoMaterialsCache) AWS Encryption SDK for Python 도 제공합니다. NullCryptoMaterialsCache는 모든 `GET` 작업에 대해 누락을 반환하고 `PUT` 작업에 응답하지 않습니다. NullCryptoMaterialsCache를 테스트에 사용하거나 캐싱 코드가 포함된 애플리케이션에서 캐싱을 일시적으로 비활성화할 수 있습니다.

에서 AWS Encryption SDK각 암호화 자료 캐시는 [캐싱 암호화 자료 관리자](#caching-cmm)(캐싱 CMM)와 연결됩니다. 캐싱 CMM은 캐시에서 데이터 키를 가져와 캐시에 데이터 키를 넣고 사용자가 설정한 [보안 임계값](thresholds.md)을 적용합니다. 캐싱 CMM을 생성할 때, 해당 CMM이 사용할 캐시를 지정하고, CMM이 캐싱할 데이터 키를 생성하는 기본 CMM 또는 마스터 키 공급자를 지정합니다.

## 암호화 자료 캐싱 관리자 생성
<a name="caching-cmm"></a>

데이터 키 캐싱을 활성화하려면 [캐시](#simplecache) 및 *캐싱 암호화 자료 관리자*(캐싱 CMM)를 생성합니다. 그런 다음 데이터 암호화 또는 복호화 요청에서 표준 [암호화 자료 관리자(CMM)](concepts.md#crypt-materials-manager), [마스터 키 공급자](concepts.md#master-key-provider) 또는 [키링](concepts.md#keyring) 대신 캐싱 CMM을 지정합니다.

다음과 같은 두 가지 유형의 CMM이 있습니다. 둘 다 데이터 키(및 관련 암호화 자료)를 가져오지만 다음과 같이 방법이 다릅니다.
+ CMM은 키링(C 또는 JavaScript) 또는 마스터 키 공급자(Java와 Python)와 연결됩니다. SDK가 CMM에 암호화 또는 복호화 구성 요소를 요청하면 CMM은 키링 또는 마스터 키 공급자로부터 구성 요소를 가져옵니다. Java 및 Python에서 CMM은 마스터 키를 사용하여 데이터 키를 생성, 암호화 또는 복호화합니다. C 및 JavaScript에서 키링은 암호화 자료를 생성 및 암호화하고 반환합니다.
+ 캐싱 CMM은 하나의 캐시(예: [로컬 캐시](#simplecache) 및 기본 CMM)와 연결됩니다. SDK가 캐싱 CMM에 암호화 자료를 요청하면 캐싱 CMM은 캐시에서 해당 구성 요소를 가져오려고 시도합니다. 일치하는 항목을 찾을 수 없는 경우 캐싱 CMM은 기본 CMM에 구성 요소를 요청합니다. 그런 다음 새 암호화 자료를 캐싱한 다음 호출자에게 반환합니다.

또한 캐싱 CMM은 각 캐시 항목에 사용자가 설정한 [보안 임계값](thresholds.md)을 적용합니다. 보안 임계값은 캐싱 CMM에서 설정하고 적용하므로 캐시가 민감한 구성 요소용으로 설계되지 않았더라도 호환되는 모든 캐시를 사용할 수 있습니다.

## 데이터 키 캐시 항목에는 무엇이 들어 있나요?
<a name="cache-entries"></a>

데이터 키 캐싱은 데이터 키 및 관련 암호화 자료를 캐시에 저장합니다. 각 항목에는 아래 나열된 요소가 포함됩니다. 이 정보는 데이터 키 캐싱 기능을 사용할지 여부를 결정할 때와, 캐싱 암호화 자료 관리자(캐싱 CMM)에서 보안 임계값을 설정할 때 유용할 수 있습니다.

**암호화 요청의 캐시된 항목**  
암호화 작업의 결과로 데이터 키 캐시에 추가되는 항목에는 다음 요소가 포함됩니다.
+ 일반 텍스트 데이터 키
+ 암호화된 데이터 키(하나 이상)
+ [암호화 컨텍스트](#caching-encryption-context) 
+ 메시지 서명 키(하나가 사용되는 경우)
+ [알고리즘 제품군](concepts.md#crypto-algorithm)
+ 메타데이터(보안 임계값 적용을 위한 사용 카운터 포함)

**복호화 요청의 캐시된 항목**  
복호화 작업의 결과로 데이터 키 캐시에 추가되는 항목에는 다음 요소가 포함됩니다.
+ 일반 텍스트 데이터 키
+ 서명 확인 키(하나가 사용되는 경우)
+ 메타데이터(보안 임계값 적용을 위한 사용 카운터 포함)

## 암호화 컨텍스트: 캐시 항목을 선택하는 방법
<a name="caching-encryption-context"></a>

모든 요청에 암호화 컨텍스트를 지정하여 데이터를 암호화할 수 있습니다. 하지만 암호화 컨텍스트는 데이터 키 캐싱에서 특별한 역할을 합니다. 이를 통해 데이터 키가 동일한 캐싱 CMM에서 생성된 경우에도 캐시에 데이터 키의 하위 그룹을 만들 수 있습니다.

[암호화 컨텍스트](concepts.md#encryption-context)는 비밀이 아닌 임의의 데이터를 포함하는 키-값 페어 세트입니다. 암호화하는 동안 암호화 컨텍스트는 암호화된 데이터에 암호적으로 바인딩되므로 데이터를 복호화하는 데 동일한 암호화 컨텍스트가 필요합니다. 에서 AWS Encryption SDK암호화 컨텍스트는 [암호화된 데이터 및 데이터 키와 함께 암호화된 메시지](concepts.md#message)에 저장됩니다.

데이터 키 캐시를 사용하는 경우 암호화 컨텍스트를 통해 암호화 작업에 사용할 캐시된 특정 데이터 키를 선택할 수도 있습니다. 암호화 컨텍스트는 데이터 키(캐시 항목 ID의 일부)와 함께 캐시 항목에 저장됩니다. 캐시된 데이터 키는 암호화 컨텍스트가 일치하는 경우에만 재사용됩니다. 암호화 요청에 특정 데이터 키를 재사용하려면 동일한 암호화 컨텍스트를 지정합니다. 이러한 데이터 키를 피하려면 다른 암호화 컨텍스트를 지정합니다.

암호화 컨텍스트는 항상 선택 사항이지만 권장됩니다. 요청에 암호화 컨텍스트를 지정하지 않는 경우 빈 암호화 컨텍스트가 캐시 항목 식별자에 포함되고 각 요청과 일치합니다.

## 내 애플리케이션이 캐시된 데이터 키를 사용하고 있나요?
<a name="caching-effect"></a>

데이터 키 캐싱은 특정 애플리케이션 및 워크로드에 매우 효과적인 최적화 전략입니다. 그러나 약간의 위험이 수반되므로, 상황에 얼마나 효과적일 수 있는지 판단한 다음 이점이 위험보다 큰지 판단하는 것이 중요합니다.

데이터 키 캐싱은 데이터 키를 재사용하기 때문에 가장 확실한 효과는 새 데이터 키를 생성하기 위한 호출 횟수를 줄이는 것입니다. 데이터 키 캐싱이 구현되면는 초기 데이터 키를 생성하고 캐시가 AWS KMS `GenerateDataKey` 누락될 때만 작업을 AWS Encryption SDK 호출합니다. 그러나 캐싱은 동일한 암호화 컨텍스트 및 알고리즘 세트를 포함하여 동일한 특성을 가진 수많은 데이터 키를 생성하는 애플리케이션에서만 성능을 눈에 띄게 개선합니다.

의 구현 AWS Encryption SDK 이 실제로 캐시의 데이터 키를 사용하고 있는지 확인하려면 다음 기술을 시도해 보세요.
+ 마스터 키 인프라의 로그에서 호출 빈도를 확인하여 새 데이터 키를 만듭니다. 데이터 키 캐싱이 효과적이면 새 키를 만드는 호출 횟수가 눈에 띄게 떨어집니다. 예를 들어 AWS KMS 마스터 키 또는 키링을 사용하는 경우 CloudTrail 로그에서 [GenerateDataKey](https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html) 호출을 검색합니다.
+ 다양한 암호화 요청에 대한 응답으로 AWS Encryption SDK 에서 반환되는 [암호화된 메시지](concepts.md#message)를 비교합니다. 예를 들어를 사용하는 경우 다른 암호화 호출의 [ParsedCiphertext](https://aws.github.io/aws-encryption-sdk-java/com/amazonaws/encryptionsdk/ParsedCiphertext.html) 객체를 AWS Encryption SDK for Java비교합니다. AWS Encryption SDK for JavaScript에서 [MessageHeader](https://github.com/aws/aws-encryption-sdk-javascript/blob/master/modules/serialize/src/types.ts#L21)의 `encryptedDataKeys` 속성 내용을 비교합니다. 데이터 키를 재사용하면 암호화된 메시지의 암호화된 데이터 키가 동일합니다.

# 데이터 키 캐싱 예제
<a name="sample-cache-example"></a>

이 예제에서는 [데이터 키 캐싱](data-key-caching.md)과 [로컬 캐시](data-caching-details.md#simplecache)를 함께 사용하여 다양한 리전에서 여러 디바이스에 의해 생성되는 데이터가 암호화 및 저장되는 애플리케이션의 속도를 가속화합니다.

이 시나리오에서는 여러 데이터 생산자가 데이터를 생성하고 암호화하여 각 리전의 [Kinesis 스트림](https://aws.amazon.com/kinesis/streams/)에 씁니다. [AWS Lambda](https://aws.amazon.com/lambda/) 함수(소비자)는 스트림을 복호화하여 일반 텍스트 데이터를 해당 리전의 DynamoDB 테이블에 씁니다. 데이터 생산자와 소비자는 AWS Encryption SDK 및 [AWS KMS 마스터 키 공급자](concepts.md#master-key-provider)를 사용합니다. KMS에 대한 호출을 줄이기 위해 각 생산자와 소비자는 자체 로컬 캐시를 보유하고 있습니다.

[Java 및 Python](sample-cache-example-code.md)에서 이러한 예제의 소스 코드를 찾을 수 있습니다. 샘플에는 샘플에 대한 리소스를 정의하는 CloudFormation 템플릿도 포함되어 있습니다.

![\[이 다이어그램은 데이터 생산자와 소비자가 AWS KMS, Amazon Kinesis Data Streams 및 Amazon DynamoDB를 사용하는 방법을 보여줍니다.\]](http://docs.aws.amazon.com/ko_kr/encryption-sdk/latest/developer-guide/images/simplecache-example.png)


## 로컬 캐시 결과
<a name="caching-example-impact"></a>

아래 표에서는 로컬 캐시가 이 예제의 총 KMS 호출 수(리전별 초당)를 원래 값의 1%로 줄이는 것을 보여줍니다.


**생산자 요청**  
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/encryption-sdk/latest/developer-guide/sample-cache-example.html)


**소비자 요청**  
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/encryption-sdk/latest/developer-guide/sample-cache-example.html)

# 데이터 키 캐싱 예제 코드
<a name="sample-cache-example-code"></a>

이 코드 샘플은 Java 및 Python에서 [로컬 캐시](data-caching-details.md#simplecache)를 사용한 데이터 키 캐싱을 간단하게 구현합니다. 이 코드는 로컬 캐시의 두 인스턴스를 생성합니다. 하나는 데이터를 암호화하는 [데이터 생산자](#caching-producer)용이고 다른 하나는 데이터를 해독하는 [데이터 소비자](#caching-consumer)(AWS Lambda 함수)용입니다. 각 언어의 데이터 키 캐싱 구현에 대한 자세한 내용은 AWS Encryption SDK에 대한 [Javadoc](https://aws.github.io/aws-encryption-sdk-java/) 및 [Python 설명서](https://aws-encryption-sdk-python.readthedocs.io/en/latest/)를 참조하세요.

데이터 키 캐싱은가 AWS Encryption SDK 지원하는 모든 [프로그래밍 언어](programming-languages.md)에 사용할 수 있습니다.

에서 데이터 키 캐싱을 사용하는 전체 예제와 테스트된 예제는 다음을 AWS Encryption SDK참조하세요.
+ C/C\$1\$1: [caching\$1cmm.cpp](https://github.com/aws/aws-encryption-sdk-c/blob/master/examples/caching_cmm.cpp) 
+ Java: [SimpleDataKeyCachingExample.java](https://github.com/aws/aws-encryption-sdk-java/blob/master/src/examples/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExample.java)
+ JavaScript 브라우저: [caching\$1cmm.ts](https://github.com/aws/aws-encryption-sdk-javascript/blob/master/modules/example-browser/src/caching_cmm.ts) 
+ JavaScript Node.js: [caching\$1cmm.ts](https://github.com/aws/aws-encryption-sdk-javascript/blob/master/modules/example-node/src/caching_cmm.ts) 
+ Python: [data\$1key\$1caching\$1basic.py](https://github.com/aws/aws-encryption-sdk-python/blob/master/examples/src/legacy/data_key_caching_basic.py)

## 생산자
<a name="caching-producer"></a>

생산자는 맵을 가져와 JSON으로 변환하고, AWS Encryption SDK 를 사용하여 암호화하고, 사이퍼텍스트 레코드를 각의 [Kinesis 스트림](https://aws.amazon.com/kinesis/streams/)으로 푸시합니다 AWS 리전.

이 코드는 [캐싱 암호 자료 관리자(캐싱 CMM)](data-caching-details.md#caching-cmm)를 정의해서 [로컬 캐시](data-caching-details.md#simplecache) 및 기본 [AWS KMS 마스터 키 공급자](concepts.md#master-key-provider)와 연결합니다. 캐싱 CMM은 마스터 키 공급자의 데이터 키(및 [관련 암호화 자료](data-caching-details.md#cache-entries))를 캐시합니다. 또한 SDK를 대신하여 캐시와 상호 작용하며 사용자가 설정한 보안 임계값을 적용합니다.

암호화 메서드 호출은 일반 [암호화 자료 관리자(CMM)](concepts.md#crypt-materials-manager) 또는 마스터 키 공급자 대신 캐싱 CMM을 지정하므로 암호화에는 데이터 키 캐싱이 사용됩니다.

------
#### [ Java ]

다음 예제에서는의 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x*는 데이터 키 캐싱 CMM을 더 AWS Encryption SDK for Java 이상 사용하지 않습니다. 버전 3.*x*에서는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 사용할 수도 있습니다.

```
/*
 * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
 * in compliance with the License. A copy of the License is located at
 *
 * http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package com.amazonaws.crypto.examples.kinesisdatakeycaching;

import com.amazonaws.encryptionsdk.AwsCrypto;
import com.amazonaws.encryptionsdk.CommitmentPolicy;
import com.amazonaws.encryptionsdk.CryptoResult;
import com.amazonaws.encryptionsdk.MasterKeyProvider;
import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager;
import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache;
import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKey;
import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider;
import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory;
import com.amazonaws.util.json.Jackson;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisClient;
import software.amazon.awssdk.services.kms.KmsClient;

/**
 * Pushes data to Kinesis Streams in multiple Regions.
 */
public class MultiRegionRecordPusher {

    private static final long MAX_ENTRY_AGE_MILLISECONDS = 300000;
    private static final long MAX_ENTRY_USES = 100;
    private static final int MAX_CACHE_ENTRIES = 100;
    private final String streamName_;
    private final ArrayList<KinesisClient> kinesisClients_;
    private final CachingCryptoMaterialsManager cachingMaterialsManager_;
    private final AwsCrypto crypto_;

    /**
     * Creates an instance of this object with Kinesis clients for all target Regions and a cached
     * key provider containing KMS master keys in all target Regions.
     */
    public MultiRegionRecordPusher(final Region[] regions, final String kmsAliasName,
        final String streamName) {
        streamName_ = streamName;
        crypto_ = AwsCrypto.builder()
            .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt)
            .build();
        kinesisClients_ = new ArrayList<>();

        AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build();

        // Build KmsMasterKey and AmazonKinesisClient objects for each target region
        List<KmsMasterKey> masterKeys = new ArrayList<>();
        for (Region region : regions) {
            kinesisClients_.add(KinesisClient.builder()
                .credentialsProvider(credentialsProvider)
                .region(region)
                .build());

            KmsMasterKey regionMasterKey = KmsMasterKeyProvider.builder()
                .defaultRegion(region)
                .builderSupplier(() -> KmsClient.builder().credentialsProvider(credentialsProvider))
                .buildStrict(kmsAliasName)
                .getMasterKey(kmsAliasName);

            masterKeys.add(regionMasterKey);
        }

        // Collect KmsMasterKey objects into single provider and add cache
        MasterKeyProvider<?> masterKeyProvider = MultipleProviderFactory.buildMultiProvider(
            KmsMasterKey.class,
            masterKeys
        );

        cachingMaterialsManager_ = CachingCryptoMaterialsManager.newBuilder()
            .withMasterKeyProvider(masterKeyProvider)
            .withCache(new LocalCryptoMaterialsCache(MAX_CACHE_ENTRIES))
            .withMaxAge(MAX_ENTRY_AGE_MILLISECONDS, TimeUnit.MILLISECONDS)
            .withMessageUseLimit(MAX_ENTRY_USES)
            .build();
    }

    /**
     * JSON serializes and encrypts the received record data and pushes it to all target streams.
     */
    public void putRecord(final Map<Object, Object> data) {
        String partitionKey = UUID.randomUUID().toString();
        Map<String, String> encryptionContext = new HashMap<>();
        encryptionContext.put("stream", streamName_);

        // JSON serialize data
        String jsonData = Jackson.toJsonString(data);

        // Encrypt data
        CryptoResult<byte[], ?> result = crypto_.encryptData(
            cachingMaterialsManager_,
            jsonData.getBytes(),
            encryptionContext
        );
        byte[] encryptedData = result.getResult();

        // Put records to Kinesis stream in all Regions
        for (KinesisClient regionalKinesisClient : kinesisClients_) {
            regionalKinesisClient.putRecord(builder ->
                builder.streamName(streamName_)
                    .data(SdkBytes.fromByteArray(encryptedData))
                    .partitionKey(partitionKey));
        }
    }
}
```

------
#### [ Python ]

```
"""
Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 
Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
in compliance with the License. A copy of the License is located at
 
https://aws.amazon.com/apache-2-0/
 
or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import json
import uuid
 
from aws_encryption_sdk import EncryptionSDKClient, StrictAwsKmsMasterKeyProvider, CachingCryptoMaterialsManager, LocalCryptoMaterialsCache, CommitmentPolicy
from aws_encryption_sdk.key_providers.kms import KMSMasterKey
import boto3
 
 
class MultiRegionRecordPusher(object):
    """Pushes data to Kinesis Streams in multiple Regions."""
    CACHE_CAPACITY = 100
    MAX_ENTRY_AGE_SECONDS = 300.0
    MAX_ENTRY_MESSAGES_ENCRYPTED = 100
 
    def __init__(self, regions, kms_alias_name, stream_name):
        self._kinesis_clients = []
        self._stream_name = stream_name
 
        # Set up EncryptionSDKClient
        _client = EncryptionSDKClient(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)
 
        # Set up KMSMasterKeyProvider with cache
        _key_provider = StrictAwsKmsMasterKeyProvider(kms_alias_name)
 
        # Add MasterKey and Kinesis client for each Region
        for region in regions:
            self._kinesis_clients.append(boto3.client('kinesis', region_name=region))
            regional_master_key = KMSMasterKey(
                client=boto3.client('kms', region_name=region),
                key_id=kms_alias_name
            )
            _key_provider.add_master_key_provider(regional_master_key)
 
        cache = LocalCryptoMaterialsCache(capacity=self.CACHE_CAPACITY)
        self._materials_manager = CachingCryptoMaterialsManager(
            master_key_provider=_key_provider,
            cache=cache,
            max_age=self.MAX_ENTRY_AGE_SECONDS,
            max_messages_encrypted=self.MAX_ENTRY_MESSAGES_ENCRYPTED
        )
 
    def put_record(self, record_data):
        """JSON serializes and encrypts the received record data and pushes it to all target streams.
 
        :param dict record_data: Data to write to stream
        """
        # Kinesis partition key to randomize write load across stream shards
        partition_key = uuid.uuid4().hex
 
        encryption_context = {'stream': self._stream_name}
 
        # JSON serialize data
        json_data = json.dumps(record_data)
 
        # Encrypt data
        encrypted_data, _header = _client.encrypt(
            source=json_data,
            materials_manager=self._materials_manager,
            encryption_context=encryption_context
        )
 
        # Put records to Kinesis stream in all Regions
        for client in self._kinesis_clients:
            client.put_record(
                StreamName=self._stream_name,
                Data=encrypted_data,
                PartitionKey=partition_key
            )
```

------

## 소비자
<a name="caching-consumer"></a>

데이터 소비자는 [Kinesis](https://aws.amazon.com/kinesis/) 이벤트에 의해 트리거되는 [AWS Lambda](https://aws.amazon.com/lambda/) 함수입니다. 각 레코드를 복호화하고 역직렬화하며 일반 텍스트 레코드를 동일 리전의 [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) 테이블에 씁니다.

생산자 코드와 마찬가지로 소비자 코드는 복호화 메서드를 호출할 때 캐싱 암호 자료 관리자(캐싱 CMM)를 사용하여 데이터 키를 캐싱할 수 있도록 합니다.

Java 코드는 지정된를 사용하여 *엄격 모드에서* 마스터 키 공급자를 빌드합니다 AWS KMS key. 복호화 시에는 엄격 모드가 반드시 필요하지 않지만 [모범 사례입니다](best-practices.md#strict-discovery-mode). Python 코드는 *검색 모드를* 사용합니다.이 모드를 사용하면가 데이터 키를 암호화한 래핑 키를 AWS Encryption SDK 사용하여 복호화할 수 있습니다.

------
#### [ Java ]

다음 예제에서는 버전 2.*x*를 사용합니다 AWS Encryption SDK for Java. 버전 3.*x*는 데이터 키 캐싱 CMM을 더 AWS Encryption SDK for Java 이상 사용하지 않습니다. 버전 3.*x*에서는 대체 암호화 자료 캐싱 솔루션인 [AWS KMS 계층적 키링](use-hierarchical-keyring.md)을 사용할 수도 있습니다.

이 코드는 엄격 모드에서 복호화하기 위한 마스터 키 공급자를 생성합니다. 는 AWS Encryption SDK AWS KMS keys 사용자가 지정한 만 사용하여 메시지를 복호화할 수 있습니다.

```
/*
 * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
 * in compliance with the License. A copy of the License is located at
 *
 * http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package com.amazonaws.crypto.examples.kinesisdatakeycaching;

import com.amazonaws.encryptionsdk.AwsCrypto;
import com.amazonaws.encryptionsdk.CommitmentPolicy;
import com.amazonaws.encryptionsdk.CryptoResult;
import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager;
import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache;
import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord;
import com.amazonaws.util.BinaryUtils;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;

/**
 * Decrypts all incoming Kinesis records and writes records to DynamoDB.
 */
public class LambdaDecryptAndWrite {

    private static final long MAX_ENTRY_AGE_MILLISECONDS = 600000;
    private static final int MAX_CACHE_ENTRIES = 100;
    private final CachingCryptoMaterialsManager cachingMaterialsManager_;
    private final AwsCrypto crypto_;
    private final DynamoDbTable<Item> table_;

    /**
     * Because the cache is used only for decryption, the code doesn't set the max bytes or max
     * message security thresholds that are enforced only on on data keys used for encryption.
     */
    public LambdaDecryptAndWrite() {
        String kmsKeyArn = System.getenv("CMK_ARN");
        cachingMaterialsManager_ = CachingCryptoMaterialsManager.newBuilder()
            .withMasterKeyProvider(KmsMasterKeyProvider.builder().buildStrict(kmsKeyArn))
            .withCache(new LocalCryptoMaterialsCache(MAX_CACHE_ENTRIES))
            .withMaxAge(MAX_ENTRY_AGE_MILLISECONDS, TimeUnit.MILLISECONDS)
            .build();

        crypto_ = AwsCrypto.builder()
            .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt)
            .build();

        String tableName = System.getenv("TABLE_NAME");
        DynamoDbEnhancedClient dynamodb = DynamoDbEnhancedClient.builder().build();
        table_ = dynamodb.table(tableName, TableSchema.fromClass(Item.class));
    }

    /**
     * @param event
     * @param context
     */
    public void handleRequest(KinesisEvent event, Context context)
        throws UnsupportedEncodingException {
        for (KinesisEventRecord record : event.getRecords()) {
            ByteBuffer ciphertextBuffer = record.getKinesis().getData();
            byte[] ciphertext = BinaryUtils.copyAllBytesFrom(ciphertextBuffer);

            // Decrypt and unpack record
            CryptoResult<byte[], ?> plaintextResult = crypto_.decryptData(cachingMaterialsManager_,
                ciphertext);

            // Verify the encryption context value
            String streamArn = record.getEventSourceARN();
            String streamName = streamArn.substring(streamArn.indexOf("/") + 1);
            if (!streamName.equals(plaintextResult.getEncryptionContext().get("stream"))) {
                throw new IllegalStateException("Wrong Encryption Context!");
            }

            // Write record to DynamoDB
            String jsonItem = new String(plaintextResult.getResult(), StandardCharsets.UTF_8);
            System.out.println(jsonItem);
            table_.putItem(Item.fromJSON(jsonItem));
        }
    }

    private static class Item {

        static Item fromJSON(String jsonText) {
            // Parse JSON and create new Item
            return new Item();
        }
    }
}
```

------
#### [ Python ]

이 Python 코드는 검색 모드에서 마스터 키 공급자를 사용하여 복호화합니다. 이렇게 하면 AWS Encryption SDK 가 데이터 키를 암호화한 래핑 키를 사용하여 복호화할 수 있습니다. 복호화에 사용할 수 있는 래핑 키를 지정하는 엄격 모드가 [모범 사례](best-practices.md#strict-discovery-mode)입니다.

```
"""
Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 
Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
in compliance with the License. A copy of the License is located at
 
https://aws.amazon.com/apache-2-0/
 
or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import base64
import json
import logging
import os
 
from aws_encryption_sdk import EncryptionSDKClient, DiscoveryAwsKmsMasterKeyProvider, CachingCryptoMaterialsManager, LocalCryptoMaterialsCache, CommitmentPolicy
import boto3
 
_LOGGER = logging.getLogger(__name__)
_is_setup = False
CACHE_CAPACITY = 100
MAX_ENTRY_AGE_SECONDS = 600.0
 
def setup():
    """Sets up clients that should persist across Lambda invocations."""
    global encryption_sdk_client
    encryption_sdk_client = EncryptionSDKClient(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)
 
    global materials_manager
    key_provider = DiscoveryAwsKmsMasterKeyProvider()
    cache = LocalCryptoMaterialsCache(capacity=CACHE_CAPACITY)
           
    #  Because the cache is used only for decryption, the code doesn't set
    #   the max bytes or max message security thresholds that are enforced
    #   only on on data keys used for encryption.
    materials_manager = CachingCryptoMaterialsManager(
        master_key_provider=key_provider,
        cache=cache,
        max_age=MAX_ENTRY_AGE_SECONDS
    )
    global table
    table_name = os.environ.get('TABLE_NAME')
    table = boto3.resource('dynamodb').Table(table_name)
    global _is_setup
    _is_setup = True
 
 
def lambda_handler(event, context):
    """Decrypts all incoming Kinesis records and writes records to DynamoDB."""
    _LOGGER.debug('New event:')
    _LOGGER.debug(event)
    if not _is_setup:
        setup()
    with table.batch_writer() as batch:
        for record in event.get('Records', []):
            # Record data base64-encoded by Kinesis
            ciphertext = base64.b64decode(record['kinesis']['data'])
 
            # Decrypt and unpack record
            plaintext, header = encryption_sdk_client.decrypt(
                source=ciphertext,
                materials_manager=materials_manager
            )
            item = json.loads(plaintext)
 
            # Verify the encryption context value
            stream_name = record['eventSourceARN'].split('/', 1)[1]
            if stream_name != header.encryption_context['stream']:
                raise ValueError('Wrong Encryption Context!')
 
            # Write record to DynamoDB
            batch.put_item(Item=item)
```

------

# 데이터 키 캐싱 예제: CloudFormation template
<a name="sample-cache-example-cloudformation"></a>

이 CloudFormation 템플릿은 [데이터 키 캐싱 예제](sample-cache-example.md)를 재현하는 데 필요한 모든 AWS 리소스를 설정합니다.

------
#### [ JSON ]

```
{
    "Parameters": {
        "SourceCodeBucket": {
            "Type": "String",
            "Description": "S3 bucket containing Lambda source code zip files"
        },
        "PythonLambdaS3Key": {
            "Type": "String",
            "Description": "S3 key containing Python Lambda source code zip file"
        },
        "PythonLambdaObjectVersionId": {
            "Type": "String",
            "Description": "S3 version id for S3 key containing Python Lambda source code zip file"
        },
        "JavaLambdaS3Key": {
            "Type": "String",
            "Description": "S3 key containing Python Lambda source code zip file"
        },
        "JavaLambdaObjectVersionId": {
            "Type": "String",
            "Description": "S3 version id for S3 key containing Python Lambda source code zip file"
        },
        "KeyAliasSuffix": {
            "Type": "String",
            "Description": "Suffix to use for KMS key Alias (ie: alias/KeyAliasSuffix)"
        },
        "StreamName": {
            "Type": "String",
            "Description": "Name to use for Kinesis Stream"
        }
    },
    "Resources": {
        "InputStream": {
            "Type": "AWS::Kinesis::Stream",
            "Properties": {
                "Name": {
                    "Ref": "StreamName"
                },
                "ShardCount": 2
            }
        },
        "PythonLambdaOutputTable": {
            "Type": "AWS::DynamoDB::Table",
            "Properties": {
                "AttributeDefinitions": [
                    {
                        "AttributeName": "id",
                        "AttributeType": "S"
                    }
                ],
                "KeySchema": [
                    {
                        "AttributeName": "id",
                        "KeyType": "HASH"
                    }
                ],
                "ProvisionedThroughput": {
                    "ReadCapacityUnits": 1,
                    "WriteCapacityUnits": 1
                }
            }
        },
        "PythonLambdaRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",		 	 	 
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "lambda.amazonaws.com"
                            },
                            "Action": "sts:AssumeRole"
                        }
                    ]
                },
                "ManagedPolicyArns": [
                    "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
                ],
                "Policies": [
                    {
                        "PolicyName": "PythonLambdaAccess",
                        "PolicyDocument": {
                            "Version": "2012-10-17",		 	 	 
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "dynamodb:DescribeTable",
                                        "dynamodb:BatchWriteItem"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${PythonLambdaOutputTable}"
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "dynamodb:PutItem"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${PythonLambdaOutputTable}*"
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "kinesis:GetRecords",
                                        "kinesis:GetShardIterator",
                                        "kinesis:DescribeStream",
                                        "kinesis:ListStreams"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "PythonLambdaFunction": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "Description": "Python consumer",
                "Runtime": "python2.7",
                "MemorySize": 512,
                "Timeout": 90,
                "Role": {
                    "Fn::GetAtt": [
                        "PythonLambdaRole",
                        "Arn"
                    ]
                },
                "Handler": "aws_crypto_examples.kinesis_datakey_caching.consumer.lambda_handler",
                "Code": {
                    "S3Bucket": {
                        "Ref": "SourceCodeBucket"
                    },
                    "S3Key": {
                        "Ref": "PythonLambdaS3Key"
                    },
                    "S3ObjectVersion": {
                        "Ref": "PythonLambdaObjectVersionId"
                    }
                },
                "Environment": {
                    "Variables": {
                        "TABLE_NAME": {
                            "Ref": "PythonLambdaOutputTable"
                        }
                    }
                }
            }
        },
        "PythonLambdaSourceMapping": {
            "Type": "AWS::Lambda::EventSourceMapping",
            "Properties": {
                "BatchSize": 1,
                "Enabled": true,
                "EventSourceArn": {
                    "Fn::Sub": "arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}"
                },
                "FunctionName": {
                    "Ref": "PythonLambdaFunction"
                },
                "StartingPosition": "TRIM_HORIZON"
            }
        },
        "JavaLambdaOutputTable": {
            "Type": "AWS::DynamoDB::Table",
            "Properties": {
                "AttributeDefinitions": [
                    {
                        "AttributeName": "id",
                        "AttributeType": "S"
                    }
                ],
                "KeySchema": [
                    {
                        "AttributeName": "id",
                        "KeyType": "HASH"
                    }
                ],
                "ProvisionedThroughput": {
                    "ReadCapacityUnits": 1,
                    "WriteCapacityUnits": 1
                }
            }
        },
        "JavaLambdaRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",		 	 	 
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "lambda.amazonaws.com"
                            },
                            "Action": "sts:AssumeRole"
                        }
                    ]
                },
                "ManagedPolicyArns": [
                    "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
                ],
                "Policies": [
                    {
                        "PolicyName": "JavaLambdaAccess",
                        "PolicyDocument": {
                            "Version": "2012-10-17",		 	 	 
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "dynamodb:DescribeTable",
                                        "dynamodb:BatchWriteItem"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${JavaLambdaOutputTable}"
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "dynamodb:PutItem"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${JavaLambdaOutputTable}*"
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "kinesis:GetRecords",
                                        "kinesis:GetShardIterator",
                                        "kinesis:DescribeStream",
                                        "kinesis:ListStreams"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "JavaLambdaFunction": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "Description": "Java consumer",
                "Runtime": "java8",
                "MemorySize": 512,
                "Timeout": 90,
                "Role": {
                    "Fn::GetAtt": [
                        "JavaLambdaRole",
                        "Arn"
                    ]
                },
                "Handler": "com.amazonaws.crypto.examples.kinesisdatakeycaching.LambdaDecryptAndWrite::handleRequest",
                "Code": {
                    "S3Bucket": {
                        "Ref": "SourceCodeBucket"
                    },
                    "S3Key": {
                        "Ref": "JavaLambdaS3Key"
                    },
                    "S3ObjectVersion": {
                        "Ref": "JavaLambdaObjectVersionId"
                    }
                },
                "Environment": {
                    "Variables": {
                        "TABLE_NAME": {
                            "Ref": "JavaLambdaOutputTable"
                        },
                        "CMK_ARN": {
                            "Fn::GetAtt": [
                                "RegionKinesisCMK",
                                "Arn"
                            ]
                        }
                    }
                }
            }
        },
        "JavaLambdaSourceMapping": {
            "Type": "AWS::Lambda::EventSourceMapping",
            "Properties": {
                "BatchSize": 1,
                "Enabled": true,
                "EventSourceArn": {
                    "Fn::Sub": "arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}"
                },
                "FunctionName": {
                    "Ref": "JavaLambdaFunction"
                },
                "StartingPosition": "TRIM_HORIZON"
            }
        },
        "RegionKinesisCMK": {
            "Type": "AWS::KMS::Key",
            "Properties": {
                "Description": "Used to encrypt data passing through Kinesis Stream in this region",
                "Enabled": true,
                "KeyPolicy": {
                    "Version": "2012-10-17",		 	 	 
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": {
                                    "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root"
                                }
                            },
                            "Action": [
                                "kms:Encrypt",
                                "kms:GenerateDataKey",
                                "kms:CreateAlias",
                                "kms:DeleteAlias",
                                "kms:DescribeKey",
                                "kms:DisableKey",
                                "kms:EnableKey",
                                "kms:PutKeyPolicy",
                                "kms:ScheduleKeyDeletion",
                                "kms:UpdateAlias",
                                "kms:UpdateKeyDescription"
                            ],
                            "Resource": "*"
                        },
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": [
                                    {
                                        "Fn::GetAtt": [
                                            "PythonLambdaRole",
                                            "Arn"
                                        ]
                                    },
                                    {
                                        "Fn::GetAtt": [
                                            "JavaLambdaRole",
                                            "Arn"
                                        ]
                                    }
                                ]
                            },
                            "Action": "kms:Decrypt",
                            "Resource": "*"
                        }
                    ]
                }
            }
        },
        "RegionKinesisCMKAlias": {
            "Type": "AWS::KMS::Alias",
            "Properties": {
                "AliasName": {
                    "Fn::Sub": "alias/${KeyAliasSuffix}"
                },
                "TargetKeyId": {
                    "Ref": "RegionKinesisCMK"
                }
            }
        }
    }
}
```

------
#### [ YAML ]

```
Parameters:
    SourceCodeBucket:
        Type: String
        Description: S3 bucket containing Lambda source code zip files
    PythonLambdaS3Key:
        Type: String
        Description: S3 key containing Python Lambda source code zip file
    PythonLambdaObjectVersionId:
        Type: String
        Description: S3 version id for S3 key containing Python Lambda source code zip file
    JavaLambdaS3Key:
        Type: String
        Description: S3 key containing Python Lambda source code zip file
    JavaLambdaObjectVersionId:
        Type: String
        Description: S3 version id for S3 key containing Python Lambda source code zip file
    KeyAliasSuffix:
        Type: String
        Description: 'Suffix to use for KMS CMK Alias (ie: alias/<KeyAliasSuffix>)'
    StreamName:
        Type: String
        Description: Name to use for Kinesis Stream
Resources:
    InputStream:
        Type: AWS::Kinesis::Stream
        Properties:
            Name: !Ref StreamName
            ShardCount: 2
    PythonLambdaOutputTable:
        Type: AWS::DynamoDB::Table
        Properties:
            AttributeDefinitions:
                -
                    AttributeName: id
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: id
                    KeyType: HASH
            ProvisionedThroughput:
                ReadCapacityUnits: 1
                WriteCapacityUnits: 1
    PythonLambdaRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: 2012-10-17
                Statement:
                    -
                        Effect: Allow
                        Principal:
                            Service: lambda.amazonaws.com
                        Action: sts:AssumeRole
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
            Policies:
                -
                    PolicyName: PythonLambdaAccess
                    PolicyDocument:
                        Version: 2012-10-17
                        Statement:
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:DescribeTable
                                    - dynamodb:BatchWriteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${PythonLambdaOutputTable}
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:PutItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${PythonLambdaOutputTable}*
                            -
                                Effect: Allow
                                Action:
                                    - kinesis:GetRecords
                                    - kinesis:GetShardIterator
                                    - kinesis:DescribeStream
                                    - kinesis:ListStreams
                                Resource: !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}
    PythonLambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
            Description: Python consumer
            Runtime: python2.7
            MemorySize: 512
            Timeout: 90
            Role: !GetAtt PythonLambdaRole.Arn
            Handler: aws_crypto_examples.kinesis_datakey_caching.consumer.lambda_handler
            Code:
                S3Bucket: !Ref SourceCodeBucket
                S3Key: !Ref PythonLambdaS3Key
                S3ObjectVersion: !Ref PythonLambdaObjectVersionId
            Environment:
                Variables:
                    TABLE_NAME: !Ref PythonLambdaOutputTable
    PythonLambdaSourceMapping:
        Type: AWS::Lambda::EventSourceMapping
        Properties:
            BatchSize: 1
            Enabled: true
            EventSourceArn: !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}
            FunctionName: !Ref PythonLambdaFunction
            StartingPosition: TRIM_HORIZON
    JavaLambdaOutputTable:
        Type: AWS::DynamoDB::Table
        Properties:
            AttributeDefinitions:
                -
                    AttributeName: id
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: id
                    KeyType: HASH
            ProvisionedThroughput:
                ReadCapacityUnits: 1
                WriteCapacityUnits: 1
    JavaLambdaRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: 2012-10-17
                Statement:
                    -
                        Effect: Allow
                        Principal:
                            Service: lambda.amazonaws.com
                        Action: sts:AssumeRole
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
            Policies:
                -
                    PolicyName: JavaLambdaAccess
                    PolicyDocument:
                        Version: 2012-10-17
                        Statement:
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:DescribeTable
                                    - dynamodb:BatchWriteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${JavaLambdaOutputTable}
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:PutItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${JavaLambdaOutputTable}*
                            -
                                Effect: Allow
                                Action:
                                    - kinesis:GetRecords
                                    - kinesis:GetShardIterator
                                    - kinesis:DescribeStream
                                    - kinesis:ListStreams
                                Resource: !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}
    JavaLambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
            Description: Java consumer
            Runtime: java8
            MemorySize: 512
            Timeout: 90
            Role: !GetAtt JavaLambdaRole.Arn
            Handler: com.amazonaws.crypto.examples.kinesisdatakeycaching.LambdaDecryptAndWrite::handleRequest
            Code:
                S3Bucket: !Ref SourceCodeBucket
                S3Key: !Ref JavaLambdaS3Key
                S3ObjectVersion: !Ref JavaLambdaObjectVersionId
            Environment:
                Variables:
                    TABLE_NAME: !Ref JavaLambdaOutputTable
                    CMK_ARN: !GetAtt RegionKinesisCMK.Arn
    JavaLambdaSourceMapping:
        Type: AWS::Lambda::EventSourceMapping
        Properties:
            BatchSize: 1
            Enabled: true
            EventSourceArn: !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${InputStream}
            FunctionName: !Ref JavaLambdaFunction
            StartingPosition: TRIM_HORIZON
    RegionKinesisCMK:
        Type: AWS::KMS::Key
        Properties:
            Description: Used to encrypt data passing through Kinesis Stream in this region
            Enabled: true
            KeyPolicy:
                Version: 2012-10-17
                Statement:
                    -
                        Effect: Allow
                        Principal:
                            AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
                        Action:
                            # Data plane actions
                            - kms:Encrypt
                            - kms:GenerateDataKey
                            # Control plane actions
                            - kms:CreateAlias
                            - kms:DeleteAlias
                            - kms:DescribeKey
                            - kms:DisableKey
                            - kms:EnableKey
                            - kms:PutKeyPolicy
                            - kms:ScheduleKeyDeletion
                            - kms:UpdateAlias
                            - kms:UpdateKeyDescription
                        Resource: '*'
                    -
                        Effect: Allow
                        Principal:
                            AWS:
                                - !GetAtt PythonLambdaRole.Arn
                                - !GetAtt JavaLambdaRole.Arn
                        Action: kms:Decrypt
                        Resource: '*'
    RegionKinesisCMKAlias:
        Type: AWS::KMS::Alias
        Properties:
            AliasName: !Sub alias/${KeyAliasSuffix}
            TargetKeyId: !Ref RegionKinesisCMK
```

------