Amazon Web Services ブログ

データの暗号化をシンプルにし、アプリケーションの可用性を向上する新しいAWS Encryption SDKの利用法

本日、AWSの暗号化チームは AWS Encryption SDKを発表します。この新しいSDKによって、開発者は、アプリケーションのセキュリティに影響を及ぼしうるエラーを最小化しながら容易に暗号化を実施できます。新しいSDKはAWSのお客様でなくてもご利用いただけますが、AWSのお客様にとってすぐに利用可能なサンプルが含まれています。
 
暗号化を行う際、開発者は次の2つの問題によく直面します:

  1. どのように正しく暗号鍵を生成し、利用するか
  2. 利用後、鍵をどのように保護するか

新しいAWS Encryption SDKによって提供されるライブラリは、ユーザーの開発環境で利用可能な暗号化プロバイダを利用し、低レベルの詳細な部分を透過的に実装することで1つ目の問題に対応します。また、どのように鍵を保護したいかを選択できる直感的なインターフェースを提供することで2つ目の問題への対応を手助けします。開発者は、暗号化の複雑性ではなく構築しようとしているアプリケーションコアにフォーカスすることが出来ます。この記事では、AWS Encryption SDKを利用してどのようにデータの暗号化プロセスを簡素化できるか、単一のリージョンあるいは鍵管理ソリューションに縛られない、アプリケーションの可用性を改善するのに役立つ方法でどのように鍵を保護できるかについてお伝えします。

 

エンベロープ暗号化と新しいSDK

AWS Encryption SDKを利用する際に理解しておくべき重要なコンセプトは、エンベロープ暗号化(ハイブリッド暗号化としても知られています)です。アルゴリズムが違えば強度も異なり、単一のアルゴリズムで全てのユースケースにフィットするものは有りません。例えば、(RSAやAWS Key Mangement Service [KMS]などの)優れた鍵管理の特性を持つソリューションは、大容量のデータに対してはあまり有効ではありません。エンベロープ暗号化は、(AES-GCMのように)大容量データに適した単一用途のデータキーを使ってバルクデータを暗号化することでこの問題を解決します。エンベロープ暗号化ではその後、鍵管理に適したアルゴリズムや他のソリューションを使ってデータキーを暗号化します。

エンベロープ暗号化のもう一つの優位性は、複数の受信者で復号化できるようひとつのメッセージを暗号化できることです。全員で鍵を共有(これはたいていセキュアではありません)したり、全体のメッセージを複数回暗号化したり(これは現実的ではありません)するのではなく、データキーだけがそれぞれの受信者の鍵を使って暗号化されます。これによって重複した処理を顕著に削減でき、複数の鍵を利用した暗号化がより実用的になります。

 
エンベロープ暗号化の問題点は実装の複雑さです。全てのクライアントはデータフォーマットの生成および構文解析ができ、複数の鍵とアルゴリズムをハンドルでき、理想的には妥当な範囲で前方および後方互換性を保てる事が必須になります。

 

AWS Encryption SDKはどう役立つのか?

AWS Encryption SDKは、セキュアなアルゴリズムの組み合わせ(将来的に拡張可能)をサポートし、マスターキーのタイプやアルゴリズムの制限が無い、慎重にデザインされレビューされたデータフォーマットを採用しています。AWS Encryption SDK自身は、KMS、および AWS CloudHSMやその他の PKCS #11デバイスを含むJava Cryptography Architecture (JCA/JCE)を直接サポートするpoduction-readyなリファレンスJava実装です。他言語でのSDKの実装については現在開発中です。

AWS Encryption SDKの一つの利点は、低レベルの暗号処理はSDKで取り扱われるため、データの移動にフォーカスすることができることです。次に、パワフルでセキュアなマルチリージョンソリューションを構築する簡単なコードを示します。

 

Example 1:高可用性のためにアプリケーションの機密データを複数リージョンのKMSマスターキーで暗号化する

高可用性アプリケーションのベストプラクティスの一つは、複数のアベイラビリティゾーンだけではなく複数のリージョンでデプロイすることです。KMSはリージョンをまたがってカスタマーマスターキー(CMK)を共有できないため、データがKMSで暗号化されている場合にはこのようなデプロイメントは困難です。エンベロープ暗号化では、異なるリージョンの複数のKMS CMKを使ってデータキーを暗号化することでこの制限に対するワークアラウンドを取ることができます。各リージョンで稼働するアプリケーションは暗号文の復号化を行うために、高速で信頼性の高いアクセスのためにローカルのKMSエンドポイントを利用することができます。

このドキュメントの全ての例において、 IAM roles for EC2を設定したAmazon EC2インスタンスが稼働していることを想定しています。IAM Roleによってクレデンシャルの管理を避けることができ、最も近いエンドポイントへのリクエストができるビルトインロジックのアドバンテージがあります。また例では、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,us-west2のCMKを利用してデータをどう暗号化するかを示します。この例では、各リージョンのCMKをセットアップ済みでそれぞれのCMKに対してalias/exampleKeyという名前のエイリアスを作成しています。CMK作成の詳細については、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ストリームとダイレクトに統合されています。

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 ユーザーデータとして送信することができます。

 

データを復号化する

次のコードサンプルは、EC2ユーザーデータのコンテンツを復号化し、指定したファイルに書き出します。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スタックを使って復号化するコードを得たことになります。これで、1つの暗号文を管理するだけで、高い可用性と低レイテンシーな復号化を実現することができます。

 

Example 2: エスクローとポータビリティのためにアプリケーションの機密データを異なるプロバイダーのマスターキーで暗号化する

複数のマスターキーでデータを暗号化したいもう一つの理由として、鍵管理を単一のプロバイダに頼る事を避けるためというものがあります。単一の鍵管理ソリューションと抱合せにならないことにより、アプリケーションの可用性を改善することができます。このアプローチは、コンプライアンス要件やデータ損失からの防御、災害対策の要件によって複数のプロバイダを必要とする場合に役立つかもしれません。

エスクローのためのデータ暗号化や、プライマリのプロバイダと独立した追加の復号化マスターキーを利用するために、この記事で先ほど紹介したものと同様のテクニックが使えます。この例では、RSAの公開鍵と、KMSとは独立したオフラインハードウェアセキュリティモジュール(HSM)のような鍵管理インフラストラクチャに保管されている秘密鍵を、追加のマスターキーとしてどのように利用するかを示します。(RSAキーペアの生成と管理はこの記事の対象外とします)

 

データを公開マスターキーで暗号化する

データを暗号化するためにいくつかのKmsMasterKeysを作成した先ほどのコードサンプルと同様に、次のコードサンプルはRSA公開鍵を利用するもう一つのMマスターキーを作成します。この例では、Java Cryptography Extensions (JCE)のjava.security.PublicKeyオブジェクトを利用するためにJceMasterKeyを利用します。この例では、新しいマスターキーを(他の全てのマスターキーと一緒に)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インターフェースをサポートしています。あるいは、少なくとも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);
    }
}

 

結論

エンベロープ暗号化はパワフルですが従来は実装が難しいものでした。新しいAWS Encryption SDKはユーザーがデータキーを管理する助けとなり、複数のマスターキーでデータを暗号化するプロセスを簡素化します。結果、この新しいSDKを利用することでビジネスを前進させるコードにフォーカスすることができます。またこのSDKは、ユーザーの標準に適合、強制させる為に構成された暗号化ライブラリを利用しなければならない場合にも容易に拡張ができるフレームワークを提供します。

AWS Encryption SDKをリリースする事をとても嬉しく思っており、このSDKを利用して皆様が何をするかを伺うのを待ちきれません。新しいSDKやこの記事についてコメントのある方は、下記のコメントセクションにお願いします。実装や使い方に対する質問のある方は、KMS forumまでお願い致します。

– Greg(翻訳はSA布目が担当しました。原文はこちら)