Amazon Web Services 한국 블로그

신규 AWS Encryption SDK로 빠르게 데이터 암호화 구현하기

AWS 암호화 개발팀은 AWS Encryption SDK를 발표하게 되어 기쁘게 생각합니다. 이 새로운 암호화 SDK는 응용 프로그램의 보안을 해칠수 있는 오류를 최소화하면서 개발자들이 쉽게 암호화를 적용할 수 있게 해줍니다.

새로운 SDK 사용을 위해 반드시 AWS 고객이 되실 필요는 없지만, 이번 포스팅에서는 AWS 고객들이 즉시 사용 가능한 예제들을 포함하고 있습니다.

암호화를 적용하는 개발자들은 항상 두 가지 문제를 신경써야 합니다:

  1. 데이터를 암호화 하는데, 어떻게 정확하게 키를 생성하고 사용할 것인가.
  2. 사용 후, 키를 안전하게 보호하는 방법

새로운 AWS 암호화 SDK에서 제공하는 라이브러리는 각종 개발 환경에서 사용될 수 있는 암호화 공급자를 채택하여 상세한 세부 사항을 구현함으로써 첫 번째 문제를 해결합니다. 또한, 이 라이브러리는 고객이 고객의 키를 보호하는 방법을 선택할 수 있도록 직관적인 인터페이스를 제공하여 두 번째 문제를 해결하는 데 도움을 줍니다.

따라서 개발자는 암호의 복잡성을 신경쓰는 대신에, 그들이 구축하는 응용 프로그램의 코어에 집중할 수 있습니다. 이 블로그 게시물에서는 AWS 암호화 SDK를 사용하여, 하나의 리젼이나 특정 키 관리 솔루션에 얽매이지 않고, 어플리케이션 가용성을 개선하는 데 도움이 되는 방법으로 키를 보호하고, 데이터 암호화 프로세스를 어떻게 단순화할 수 있는지를 보여줍니다.

Envelope 암호화 및 신규 SDK

AWS 암호화 SDK를 사용할 때 이해해야 하는 중요한 개념은 Envelope 암호화(하이브리드 암호화라고도 함)입니다. 암호화 알고리즘 마다 상이한 보안 수준을 가지고 있고, 모든 사용 사례에 맞는 단일 알고리즘은 없습니다.

예를 들어, (예 : RSA 또는 AWS 키 관리 서비스 [KMS]과 같은) 보안성이 높은 키 관리 특성을 가진 방식은 종종 많은 양의 데이터를 처리해야 하는 경우와 잘 맞지 않습니다. Envelope 암호화는 빠른 암,복호화에 적합한 일회용 데이터 키(예 : AES-GCM 같은)를 가지고 대용량 데이터를 암호하하는 방식으로 이 문제를 해결합니다. 그런 다음 해당 데이터 키를 마스터 키로 다시 암호화하게 되고, 마스터 키는 별도로 적절한 알고리즘이나 전담 키 관리 솔루션을 사용하여 보호하게 됩니다.

Envelope 암호화의 또 다른 장점은 다수의 수신자들이 하나의 암호화된 메시지를 복호화할 수 있다는 것입니다. 모든 수신인이 키를 공유하거나(일반적으로 안전하지 않은 경우가 많음) 또는  동일 메시지를 여러 번 암호화하는(비실용적 임) 방식과 비교하여, 단지 데이터 키를 각 수신인의 마스터키로 암호화하여 공유하기만 하면 됩니다. 이 방식은 복수의 키로 여러번 중복적인 작업을 하는 것에 비하여 작업량도 줄여주고 훨씬 더 실용적입니다.

Envelope 암호화의 단점은 상대적으로 구현이 복잡하다는 것 입니다. 모든 클라이언트가 특정 형태의 데이터 포맷을 만들고 해석할 수 있어야 하며, 멀티플 키와 알고리즘을 다룰 수 있고, 과거버전 및 향후 버전과의 호환성을 유지할 수 있어야 합니다.

AWS Encryption SDK가 어떤 도움을 줄 수 있나요?

AWS 암호화 SDK는 (미래의 확장이 가능한 형태로) 여러 보안 알고리즘 조합을 지원할 수 있고, 마스터 키의 종류나 알고리즘에 아무런 제한이 없도록, 데이터 포맷을 신중하게 설계  및  검토하였습니다.  AWS 암호화 SDK 자체는 AWS CloudHSM 및 기타 PKCS # 11 디바이스에 대한 지원을 포함하여, KMS와 Java 암호화 아키텍쳐(JCA / JCE)를 직접 지원해야 하는 운영환경에서 충분히 사용 가능하도록 자바로 구현되었습니다. 다른 언어를 위한 SDK는 현재 개발되고있습니다.

AWS 암호화 SDK의 이점 중 하나는 사용자는 데이터의 안전한 이동만 신경쓰면 되도록 상세한 수준의 암호화 세부 사항은 이미 구현되어 있다는 것입니다.  아래 보기를 통해, 여러분이 단 몇줄의 코딩으로 멀티 리전 상에서 강력하고 안전한 솔루션을 확보 할 수 있는지를 확인하 실 수 있습니다.

샘플 사례 1: 높은 가용성 유지를 위해 복수개의 리전 별 마스터 키를 통해 어플리케이션을 안전하게 암호화하는 방법

많은 고객들이 복수개의 가용영역 뿐만 아니라 멀티 리전 상에서도 구축을 하려고 합니다. 하지만 이런 구성을 하려고 할때마다 고객 마스터 키가 리전간에 공유될 수 없는 상황 때문에 어려움에 직면하게 됩니다. Envelop 암호화 방식을 이용하면, 서로 다른 리전간에 복수개의 고객 마스터 키를 가지고 데이터 키를 암호화 함으로써 이런 문제를 해결할 수 있습니다. 그리고 각 리전에서 운영되고 있는 어플리케이션들은 보다 빠르고 신뢰할수 있는 방식으로 암복호화를 수행할 수 있도록 로컬 KMS쪽에 접근하면 됩니다.

본 포스팅에서 이후 나오는 모든 사례를 위해, EC2상에 IAM roles을 부여했습니다. 이로 인해, 크레덴셜 관리에 대한 부담을 덜고, 가장 가까운 엔드포인트로 요청을 보낼수 있는 잇점을 누릴 수 있습니다. 본 예제에서는 또한 AWS SDK for Java (AWS Encryption SDK와는 다른) 와 Bouncy Castle 이 구성되어 있다고 가정하겠습니다.

아래 암호화 로직은 전체적인 이해를 돕기 위한 매우 간단한 상위 레벨의 디자인 입니다. 커맨드 라인으로 부터 몇가지 파라메터들을 받아들이고, 마스터 키를 가져와서 파일을 암호화하는데 사용합니다(샘플 코드에 보여지는 대로). 본 포스팅의 다른 부분에 메써드에 대한 상세한 내용이 나옵니다.

public static void main(final String[] args) throws Exception {
// Get parameters from the command line
final String inputFile = args[0];
final String outputFile = args[1];

// Get all the master keys we'll use
final MasterKeyProvider<?> provider = getMasterKeyProvider();

// Actually encrypt the data
encryptFile(provider, inputFile, outputFile);
}

마스터 키들을 만들고 단일 마스터 키 제공자로 묶기

다음 코드 예제는 3개의 리젼(us-east-1, us-west-1, and us-west-2)에서 고객 마스터키들을 가지고 데이터를 암호화하는 방법을 보여줍니다. 본 예제에서는 고객 마스터키들을 이미 만들어서 가지고 있다고 가정하고, 각 고객 마스터키 별로 각각의 리전에 alias/exampleKey 라는 Alias를 생성했다고 가정하겠습니다. 고객 마스터키나 Alias생성에 대한 부분은 AWS KMS의  Creating Keys 를 참고하시기 바랍니다.

본 예제에서 보면 모든 마스터키들을 통합 하는 단일 마스터키 제공자로 MultipleProviderFactory를 활용하고 있는 것을 볼수 있습니다. 코드상에서 보면 첫번째 마스터 키로 신규 데이터 키를 생성하는데 사용했고, 다른 마스터키를 통해 새로운 데이터 키를 암호화하는데 사용하는 것을 살펴보실 수 있습니다.

private static MasterKeyProvider<?> getMasterKeyProvider() {
// Get credentials from Roles for EC2
final AWSCredentialsProvider creds =
new InstanceProfileCredentialsProvider();

// Get KmsMasterKeys for the regions we care about
final String aliasName = "alias/exampleKey";
final KmsMasterKey useast1 = KmsMasterKey.getInstance(creds, "arn:aws:kms:us-east-1:" + ACCOUNT_ID + ":" + aliasName);
final KmsMasterKey uswest1 = KmsMasterKey.getInstance(creds, "arn:aws:kms:us-west-1:" + ACCOUNT_ID + ":" + aliasName);
final KmsMasterKey uswest2 = KmsMasterKey.getInstance(creds, "arn:aws:kms:us-west-2:" + ACCOUNT_ID + ":" + aliasName);

return MultipleProviderFactory.buildMultiProvider(useast1, uswest1, uswest2);
}

이와 같은 MasterKeyProvider를 생성하는 로직을 활용하여 필요한 마스터 키들을 미리 만들어 놓고 전사적으로 재활용하는 방식을 통해 보안팀 주도의 중앙 관리 형태를 쉽게 구축할 수 있으며, 더 나아가 개발과정의 암호화 관련 작업들을 전사적으로 표준화, 단순화 할 수 있으며, 회사 정책기준으로 개발 과정의 암호화 프로세스를 통제할 수 있게 됩니다.

데이터 암호화

여러분들이 암호화해야 될 데이터가 여러 군데에 존재할 수 있습니다. 다음 예제에서는 디스크로 부터 파일을 읽어 들여서 암호화된 복제본을 생성하는 과정입니다. 아래 예제에서 처럼 AWS Encryption SDK 는 Java streams 와 쉽게 연동됩니다.

private static void encryptFile(final MasterKeyProvider<?> provider,
final String inputFile, final String outputFile) throws IOException {
// Get an instance of the encryption logic
final AwsCrypto crypto = new AwsCrypto();

// Open the files for reading and writing
try (
final FileInputStream in = new FileInputStream(inputFile);
final FileOutputStream out = new FileOutputStream(outputFile);

// Wrap the output stream in encryption logic
// It is important that this is closed because it adds footers
final CryptoOutputStream<?> encryptingStream =
crypto.createEncryptingStream(provider, out)) {

// Copy the data over
IOUtils.copy(in, encryptingStream);
   }
}

해당 파일은 암호화 작업을 위한 여러가지 구성정보 들을 담고 있으며, 이 정보를 EC2인스턴의 EC2 user data 로서 인스턴스 시작과정에서 사용하게 됩니다.

데이터 복호화

다음 예제는 EC2 user data의 내용을 복호화하고 그 내용을 지정된 파일에 기록하는 것입니다. AWS SDK for Java 는 디폴트 설정으로 로컬 리전의 KMS 를 사용하도록 되어 있고, 따라서 리전 밖으로의 요청 과정 없이 빠르게 복호화를 진행하게 됩니다.

public static void main(String[] args) throws Exception {
// Get parameters from the command line
final String outputFile = args[0];

// Create a master key provider that points to the local
// KMS stack and uses Roles for EC2 to get credentials.
final KmsMasterKeyProvider provider = new KmsMasterKeyProvider(
new InstanceProfileCredentialsProvider());

// Get an instance of the encryption logic
final AwsCrypto crypto = new AwsCrypto();

// Open a stream to read the user data
// and a stream to write out the decrypted file
final URL userDataUrl = new URL("http://169.254.169.254/latest/user-data");
try (
final InputStream in = userDataUrl.openStream();
final FileOutputStream out = new FileOutputStream(outputFile);

// Wrap the input stream in decryption logic
final CryptoInputStream<?> decryptingStream =
crypto.createDecryptingStream(provider, in)) {

// Copy the data over
IOUtils.copy(decryptingStream, out);
  }
}

자, 다 되었습니다!. 여러분은 지금 복수개의 리전에 있는 마스터 키들을 가지고 데이터를 암호화하고 로컬 KMS를 사용하여 데이터를 복호화하는 과정을 진행하신 것입니다. 이를 통해 복호화에 대한 빠른 속도와 높은 가용성을 확보하면서도 한개의 암호문만 관리하면 되는 장점을 확인할 수 있었습니다.

샘플 사례2: 위탁이나 이동성을 고려하여 서로 다른 제공자로부터 나온 마스터키들을 가지고 어플리케이션 기밀정보를 암호화하기

복수개의 마스터키를 가지고 데이터를 암호화하려는 또 다른 이유로는 암호화 키들에 대한 단일 제공자에 의존하는 것을 회피하는 것을 생각할 수 있습니다. 단일 키관리 솔루션에 얽매이지 않게 되면, 여러분들의 어플리케이션의 가용성을 향상시키는 데 도움이 되기 때문이죠. 이같은 접근은 또한 컴플라이언스나 데이터 유출방지, 또는 복수개의 제공자를 갖춰야 하는 재해복구 요건을 맞추는 데 도움이 될 수 있습니다.

여러분들은 본 포스팅의 전반부에 다루어진 기법들을 활용하여, 공탁을 위해 데이터를 암호화하거나 메인 제공자와 별개의 부가적인 복호화 마스터 키를 활용할 수 있습니다. 본 예제는 부가적인 마스터 키를 다루는 방법을 통해 KMS에 독립적인(즉 하드웨어 보안 모듈(HSM)과 같이 KMS와 별개의 키관리 인프라에 저장된 개인키와 공개키) 암호 전략을 이해할 수 있을 것입니다. (RSA키 쌍을 생성하고 관리하는 내용은 본 포스팅의 범위를 벗어납니다.)

퍼블릭 마스터 키를 가지고 데이터를 암호화 하기

데이터 암호화를 위한 다수의KmsMasterKeys를 생성하는 바로 직전의 샘플 코드와 유사하게, 이번 예제에서는 RSA 공개키를 위해 마스터키를 한개 더 만들게 됩니다. 이번 코드에서는 JceMasterKey를 이용해서Java Cryptography Extensions (JCE)내의 java.security.PublicKey 객체를 이용합니다. 아래 예제를 보시면  MultipleProviderFactory (다른 모든 마스터 키들에 연계된)로 새로 생성된 마스터키를 전달합니다. 그런 후, rsa_public_key.der라고 불리는 파일에서 공개키를 로딩합니다.

private static MasterKeyProvider<?> getMasterKeyProvider()
throws IOException, GeneralSecurityException {

// Get credentials from Roles for EC2
final AWSCredentialsProvider creds =
new InstanceProfileCredentialsProvider();

// Get KmsMasterKeys for the regions we care about
final String aliasName = "alias/exampleKey";
final KmsMasterKey useast1 = KmsMasterKey.getInstance(creds, "arn:aws:kms:us-east-1:" + ACCOUNT_ID + ":" + aliasName);
final KmsMasterKey uswest1 = KmsMasterKey.getInstance(creds, "arn:aws:kms:us-west-1:" + ACCOUNT_ID + ":" + aliasName);
final KmsMasterKey uswest2 = KmsMasterKey.getInstance(creds, "arn:aws:kms:us-west-2:" + ACCOUNT_ID + ":" + aliasName);

// Load the RSA public key from a file and make a MasterKey from it.
final byte[] rsaBytes = Files.readAllBytes(
new File("rsa_public_key.der").toPath());
final KeyFactory rsaFactory = KeyFactory.getInstance("RSA");
final PublicKey rsaKey = rsaFactory.generatePublic(
new X509EncodedKeySpec(rsaBytes));
final JceMasterKey rsaMasterKey =
JceMasterKey.getInstance(rsaKey, null,
"escrow-provider", "escrow",
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding");

return MultipleProviderFactory.buildMultiProvider(useast1, uswest1, uswest2, rsaMasterKey);
}

개인키를 가지고 데이터를 복호화 하기
많은 HSM 장비들은 표준 Java KeyStore 인터페이스를 제공하거나, 적어도 기존의 자바 키 저장소 구현체를 사용할 수 있게 해주는 PKCS #11 드라이버들을 제공합니다. 다음의 복호화 코드 샘플에서는 KeyStore로부터 RSA개인키를 사용합니다.

public static void main(String[] args) throws Exception {
// Get parameters from the command line
final String inputFile = args[0];
final String outputFile = args[1];

// Get the KeyStore
// In a production system, this would likely be backed by an HSM.
// For this example, it will simply be a file on disk
final char[] keystorePassword = "example".toCharArray();
final KeyStore keyStore = KeyStore.getInstance("JKS");
try (final FileInputStream fis = new FileInputStream("cryptosdk.jks")) {
keyStore.load(fis, keystorePassword);
}

// Create a master key provider from the keystore.
// Be aware that because KMS isn’t being used, it cannot directly
// protect the integrity or authenticity of this data.
final KeyStoreProvider provider = new KeyStoreProvider(keyStore,
new PasswordProtection(keystorePassword),
"escrow-provider", "RSA/ECB/OAEPWithSHA-256AndMGF1Padding");

// Get an instance of the encryption logic
final AwsCrypto crypto = new AwsCrypto();

// Open a stream to read the encrypted file
// and a stream to write out the decrypted file

try (
final FileInputStream in = new FileInputStream(inputFile);
final FileOutputStream out = new FileOutputStream(outputFile);

// Wrap the output stream in encryption logic
final CryptoInputStream<?> decryptingStream =
crypto.createDecryptingStream(provider, in)) {

// Copy the data over
IOUtils.copy(decryptingStream, out)
}
}

결론

Envelope 암호화는 강력하긴 하지만 상세 구현에 대한 부담이 있어 왔습니다. 이번 새로운 AWS encryption SDK는 데이터 키를 관리하는데 도움을 주고, 복수개의 마스터 키 환경에서 데이터 암호화 과정을 단순화 시켜 줍니다. 결과적으로 이번 출시된 SDK를 통해 여러분들은 여러분이 구현하고자 하는 비지니스  로직에 좀더 집중할 수 있도록 해 줍니다. 또한 여러분들이 준수해야 되는 여러가지 표준 항목들을 만족시키는 암호화 라이브러리를 확보하고 있다는 점을 쉽게 입증할 수 있는 일종의 프레임 웍을 제공합니다.

이번에 AWS Encryption SDK 를 공개하게 되어 매우 기쁘고, 이것을 가지고 여러분들이 어떤 것들을 하고자 하는지 알고 싶습니다. 만약 신규SDK에 대한 코멘트나 본 포스팅 내용에 대한 어떠한 의견이라도 있다면 아래 “코멘트” 섹션에 여러분들의 소중한 의견을 남겨 주시기 바랍니다. 만약 여러분들이 다른 의문이 있다면 KMS forum을 활용해 주시길 바랍니다.

– Greg

본 글은 신규 AWS Encryption SDK 출시에 맞추어 빠르고 단순한 데이터 암호화를 및 애플리케이션 가용성을 개선할 수 있는 방법에 대한 How to Use the New AWS Encryption SDK to Simplify Data Encryption and Improve Application Availability의 글을 AWS 코리아 임기성 솔루션 아키텍트께서 번역해 주셨습니다.