Azure Cosmos DB の Always Encrypted でクライアントサイド暗号化を行う

適用対象: NoSQL

重要

暗号化パッケージの 1.0 リリースで、大きな変更が導入されました。 以前のバージョンでデータ暗号化キーと暗号化が有効なコンテナーを作成した場合は、クライアント コードを 1.0 パッケージに移行した後、データベースとコンテナーを作成しなおす必要があります。

Always Encrypted は、Azure Cosmos DB に保存されている、クレジッド カード番号、国民や地域の ID 番号 (たとえば、アメリカの社会保障番号) などの機密データを保護するための機能です。 Always Encrypted により、クライアント アプリケーション内部の機密データを暗号化でき、暗号化キーがデータベースに渡されることもありません。

Azure Cosmos DB では、Always Encrypted によってクライアントサイド暗号化を実行できます。 次の状況で、クライアントサイドのデータ暗号化が必要な場合があります。

  • 特定の性質を有する機密データを保護している: Always Encrypted では、クライアントアプリケーション内部の機密データを暗号化でき、プレーン テキスト データや暗号化キーが Azure Cosmos DB サービスに渡されることもありません。
  • プロパティごとにアクセスを制御している: 暗号化の制御は、自分が所有し Azure Key Vault で管理しているキーで行うため、アクセス ポリシーを適用することで、各クライアントがどの機密プロパティにアクセスできるかを制御できます。

概念

Azure Cosmos DB の Always Encrypted により、クライアントサイド暗号化の構成に新しい要素が追加されました。

暗号化キー

データ暗号化キー

Always Encrypted の使用時は、事前に作成が必要なデータ暗号化キー (DEK) によってデータを暗号化します。 これらの DEK は Azure Cosmos DB サービスに保存し、データベース単位で定義するため、DEK は複数のコンテナー間で共有できます。 DEK は Azure Cosmos DB SDK を使用してクライアントサイドで作成します。

次のことを実行できます。

  • 暗号化するプロパティごとに 1 つの DEK を作成する、または
  • 同じ DEK で複数のプロパティを暗号化する。

カスタマー マネージド キー

DEK は、Azure Cosmos DB に保存する際にカスタマーマネージド キー (CMK) でラップされます。 CMK では、DEK のラップとラップ解除を行うことにより、対応する DEK で暗号化したデータへのアクセスを効果的に制御します。 CMK のストレージは拡張可能な設計になっており、既定の実装では Azure Key Vault に保存される想定です。

暗号化キー

暗号化ポリシー

インデックス作成ポリシーと同様に、JSON のプロパティの暗号化方法を記述したコンテナー単位の仕様が暗号化ポリシーです。 このポリシーはコンテナー作成時に設定する必要があり、変更はできません。 現在のリリースでは、暗号化ポリシーは更新できません。

暗号化ポリシーでは、暗号化するプロパティごとに次のものを指定します。

  • /property 形式のプロパティのパス。 現在、一番上の階層のパスだけをサポートしており、/path/to/property のような入れ子のパスはサポートしていません。
  • プロパティの暗号化と解読に使用する DEK の ID。
  • 暗号化の種類。 "ランダム化された (randomized)" タイプまたは "決定論的な (deterministic)" タイプのいずれかです。
  • プロパティの暗号化に使用する暗号化アルゴリズム。 指定したアルゴリズムは、互換性があれば、キー作成時に指定したアルゴリズムを上書きできます。

ランダム暗号化と決定論的暗号化の比較

Azure Cosmos DB サービスでは、Always Encrypted で暗号化したプロパティのプレーン テキストを読むことができません。 ただし、プロパティに使用する暗号化の種類によっては、暗号化されたデータに対するいくつかのクエリ機能をサポートしています。 Always Encrypted では次の 2 種類の暗号化をサポートしています。

  • 決定論的暗号化: 同一のプレーン テキスト値を同一の暗号化構成で暗号化した場合、常に同じ暗号化値を生成します。 決定論的暗号化では、クエリを使用して、暗号化されたプロパティに等価性のフィルターをかけられます。 ただし、暗号化されたプロパティのパターンを調べることで、暗号化された情報を攻撃者に推測されることがあり得ます。 暗号化される値の候補が少ない場合、たとえば True/False や North/South/East/West などの文脈では、これが特に問題になります。

  • ランダム化された暗号化: 予測しにくい方法でデータを暗号化する方法が使用されます。 ランダム暗号化は安全性が高いですが、クエリによって、暗号化されたプロパティにフィルターをかけることができません。

Always Encrypted の決定論的暗号化とランダム暗号化に関する詳細は、初期化ベクター (IV) の生成に関するセクションをご覧ください。

Azure Key Vault をセットアップする

Always Encrypted の使用を始めるには、まず Azure Key Vault に CMK を作成します。

  1. Azure Key Vault の新しいインスタンスを作成するか、既存のインスタンスに移動します。
  2. [キー] セクションで新しいキーを作成します。
  3. キーを作成したら現在のバージョンに移動し、完全なキー識別子をコピーします。
    [https://login.microsoftonline.com/consumers/](https://<my-key-vault>.vault.azure.net/keys/<key>/<version>) キー識別子の末尾でキーのバージョンを指定しない場合、最新バージョンのキーが使用されます。

次に、Azure Cosmos DB SDK で Azure Key Vault のインスタンスにアクセスする方法を構成する必要があります。 この認証は、Microsoft Entra ID を使用して行われます。 クライアント コードと Azure Key Vault インスタンスの間のプロキシには、どの種類の ID も使用できますが、Microsoft Entra アプリケーションの ID かマネージド ID を使う場合が多いでしょう。 Microsoft Entra ID をプロキシとして使うには、次の手順のようにします。

  1. Azure Key Vault のお使いのインスタンスから、[アクセス ポリシー] セクションに移動して、新しいポリシーを追加します。

    1. [キーのアクセス許可][取得][一覧表示][キーの折り返しを解除][キーを折り返す][検証][署名] を選択します。
    2. [プリンシパルの選択] で、お使いの Microsoft Entra ID を検索します。

CMK を誤って削除することを防止する

CMK を誤って削除して暗号化されたデータにアクセスできなくなることがないよう、Azure Key Vault のインスタンスには Soft Delete (論理的な削除) と Purge Protection (消去保護) の 2 つのプロパティを設定することをお勧めします。

新しい Azure Key Vault インスタンスを作成する場合は、作成時にこれらのプロパティを有効にします。

新しい Azure Key Vault インスタンスの論理的な削除と消去の保護プロパティのスクリーンショット。

既存の Azure Key Vault インスタンスを使用している場合は、Azure portal の [プロパティ] セクションを見て、これらのプロパティが有効であることを確認できます。 これらのプロパティのいずれかが有効になっていない場合は、次のいずれかの記事の「消去保護を有効にする」と「論理的な削除を有効にする」のセクションを参照してください。

SDK を初期化する

Note

Azure Cosmos DB の Always Encrypted は現在、次のものでサポートされています。

Always Encrypted を使用するには、KeyResolver のインスタンスを Azure Cosmos DB SDK のインスタンスにアタッチする必要があります。 このクラスは、Azure.Security.KeyVault.Keys.Cryptography 名前空間で定義されてますが、CMK を保存しているキー ストアと相互作用するために使用されます。

次のスニペットでは、DefaultAzureCredential クラスを使って、Azure Key Vault インスタンスにアクセスするときに使用する Microsoft Entra ID を取得します。 ここでは、異なる種類の TokenCredential クラスを作成する例が用意されています。

Note

TokenCredential クラスにアクセスするには、Azure.Identity パッケージを追加する必要があります。

var tokenCredential = new DefaultAzureCredential();
var keyResolver = new KeyResolver(tokenCredential);
var client = new CosmosClient("<connection-string>")
    .WithEncryption(keyResolver, KeyEncryptionKeyResolverName.AzureKeyVault);

データ暗号化キーを作成する

コンテナーでデータを暗号化するには、親データベースでデータ暗号化キーを事前に作成しておく必要があります。

新しいデータ暗号化キーを作成するには、CreateClientEncryptionKeyAsync メソッドを呼び出し、次を渡します。

  • データベースにあるキーを一意に特定する文字列識別子。
  • キーと共に使用する暗号化アルゴリズム。 現在サポートしているアルゴリズムは 1 つだけです。
  • Azure Key Vault に保存している CMK のキー識別子。 このパラメーターは、次の場所にある汎用 EncryptionKeyWrapMetadata オブジェクトで渡されます。
    • type は、キー リゾルバーの種類 (たとえば、Azure Key Vault) を定義します。
    • name には、任意のフレンドリ名を指定できます。
    • value は、キー識別子である必要があります。

    重要

    キーが作成された後、現在のバージョンを参照し、完全なキー識別子 https://<my-key-vault>.vault.azure.net/keys/<key>/<version> をコピーします。 キー識別子の末尾でキーのバージョンを指定しない場合、最新バージョンのキーが使用されます。

    • algorithm では、ユーザーが管理するキーを使用してキー暗号化キーをラップするために使用するアルゴリズムを定義します。
var database = client.GetDatabase("my-database");
await database.CreateClientEncryptionKeyAsync(
    "my-key",
    DataEncryptionAlgorithm.AeadAes256CbcHmacSha256,
    new EncryptionKeyWrapMetadata(
        KeyEncryptionKeyResolverName.AzureKeyVault,
        "akvKey",
        "https://<my-key-vault>.vault.azure.net/keys/<key>/<version>",
        EncryptionAlgorithm.RsaOaep.ToString()));

暗号化ポリシーを使用してコンテナーを作成する

コンテナー作成時に、コンテナー単位の暗号化ポリシーを指定します。

var path1 = new ClientEncryptionIncludedPath
{
    Path = "/property1",
    ClientEncryptionKeyId = "my-key",
    EncryptionType = EncryptionType.Deterministic.ToString(),
    EncryptionAlgorithm = DataEncryptionAlgorithm.AeadAes256CbcHmacSha256
};
var path2 = new ClientEncryptionIncludedPath
{
    Path = "/property2",
    ClientEncryptionKeyId = "my-key",
    EncryptionType = EncryptionType.Randomized.ToString(),
    EncryptionAlgorithm = DataEncryptionAlgorithm.AeadAes256CbcHmacSha256
};
await database.DefineContainer("my-container", "/partition-key")
    .WithClientEncryptionPolicy()
    .WithIncludedPath(path1)
    .WithIncludedPath(path2)
    .Attach()
    .CreateAsync();

暗号化されたデータを読み書きする

データ暗号化の方法

ドキュメントを Azure Cosmos DB に書き込むときは毎回、暗号化ポリシーでどのプロパティをどのように暗号化するよう指定されているか、SDK による確認が行われます。 暗号化により生成されるのは Base64 文字列です。

複雑なデータの暗号化:

  • 暗号化するプロパティが JSON 配列であるときは、配列のすべての項目が暗号化されます。

  • 暗号化するプロパティが JSON オブジェクトであるときは、オブジェクトのリーフの値だけが暗号化されます。 中間階層のサブプロパティの名前はプレーン テキスト形式のままになります。

暗号化された項目を読む

ポイント読み取り (ID とパーティション キーによって単一の項目を取得する操作) 要求を行うとき、および変更フィードを読み取るときは、暗号化されたプロパティの解読を行うのに明示的な操作は必要ありません。 その理由:

  • 暗号化ポリシーでどのプロパティを解読するかが指定されているか、SDK による確認が行われるからです。
  • 暗号化により生成されたデータには、元の値の JSON の種類の情報が埋め込まれます。

暗号化されたプロパティの暗号化とそれに続く解読は、要求からの応答にのみ基づいて行います。 たとえば、property1 を暗号化して property2 (SELECT property1 AS property2 FROM c) に投影した場合、SDK でこれを受け取ったときに、暗号化されたプロパティとして認識されません。

暗号化されたプロパティにフィルターをかけるクエリ

暗号化されたプロパティにフィルターをかけるクエリを書くときは、特定のメソッドを使用してクエリのパラメーターを渡す必要があります。 このメソッドは次の引数を取ります。

  • クエリ パラメーターの名前。
  • クエリで使用する値。
  • 暗号化されたプロパティのパス (暗号化ポリシーで指定)。

重要

暗号化されたプロパティは等価性のフィルター (WHERE c.property = @Value) でのみ使用できます。 それ以外の方法で使用すると、予測不能で誤ったクエリの結果が返されます。 今後公開されるバージョンの SDK で、この制約を適切に処理できるようにする予定です。

var queryDefinition = container.CreateQueryDefinition(
    "SELECT * FROM c where c.property1 = @Property1");
await queryDefinition.AddParameterAsync(
    "@Property1",
    1234,
    "/property1");

一部のプロパティだけが解読できる状態でドキュメントを読む

プロパティの暗号化に使用するすべての CMK に対するアクセス権がクライアントにない場合、データを読み取ると、一部のプロパティだけを解読できます。 たとえば、property1 をキー 1 で、property2 をキー 2 で暗号化した場合、キー 1 に対するアクセス権だけを持つクライアントでは、データの読み取りはできるものの property2 は読めません。 このような場合、SQL クエリでデータを読み取って、クライアントで解読できないプロパティは除く必要があります: SELECT c.property1, c.property3 FROM c

CMK のローテーション

使用中の CMK が漏洩した疑いがある場合、CMK を "ローテーション" する (つまり、使用中の CMK に代わって新しい CMK を使う) ことができます。 定期的な CMK のローテーションは、よく行われるセキュリティ対策でもあります。 特定の DEK をラップするのに使用する新しい CMK のキー識別子を指定するだけで、このローテーションを実行できます。 この操作はデータの暗号化方法には影響しませんが、DEK の保護に効果があります。 前の CMK へのアクセス権は、ロテーションが完了するまでは残しておくべきです。

await database.RewrapClientEncryptionKeyAsync(
    "my-key",
    new EncryptionKeyWrapMetadata(
        KeyEncryptionKeyResolverName.AzureKeyVault,
        "akvKey",
        "https://<my-key-vault>.vault.azure.net/keys/<new-key>/<version>",
        EncryptionAlgorithm.RsaOaep.ToString()));

DEK のローテーション

データ暗号化キーのローテーションの実行は、ターンキー機能として提供されていません。 これは、DEK を更新するには、このキーが使用されているすべてのコンテナーをスキャンし、このキーで暗号化されたすべてのプロパティを再暗号化する必要があるためです。 Azure Cosmos DB サービスが DEK のプレーン テキスト値を格納していないか、これまでアクセスしていないため、この操作を実行できるのはクライアント側のみです。

実際には、影響を受けるコンテナーから新しいコンテナ―へのデータ移行を実行することで、DEK ローテーションを行うことができます。 新しいコンテナーは、元のコンテナーとまったく同じ方法で作成できます。 このようなデータ移行を支援するために、GitHub にスタンドアロン移行ツールがあります。

暗号化されたプロパティの追加

既存の暗号化ポリシーに暗号化されたプロパティを追加することは、上記のセクションで説明したのと同じ理由でサポートされていません。 この操作では、プロパティのすべてのインスタンスが適切に暗号化されていることを確認するために、コンテナーのフル スキャンが必要です。これは、クライアント側でのみ実行できる操作です。 DEK のローテーションと同様に、暗号化されたプロパティを追加するには、適切な暗号化ポリシーを使用して新しいコンテナーへのデータ移行を実行します。

スキーマの観点から新しい暗号化されたプロパティを追加する方法に柔軟性がある場合は、Azure Cosmos DB のスキーマに依存しない性質を活用することもできます。 暗号化ポリシーで定義されているプロパティを "プロパティ バッグ" として使用する場合は、制約なしで次のプロパティを追加できます。 たとえば、property1 が暗号化ポリシーで定義されていて、最初に property1.property2 をドキュメントに書き込む場合を考えてみましょう。 後の段階で、暗号化されたプロパティとして property3 を追加する必要がある場合は、ドキュメントに property1.property3 の書き込みを開始できます。新しいプロパティも自動的に暗号化されます。 この方法では、データの移行は必要ありません。

次の手順