共用方式為


針對 Azure Cosmos DB,用戶端加密與 Always Encrypted 一起使用

適用於:NoSQL

重要

加密套件 1.0 版已引入重大變更。 如果您使用舊版建立資料加密金鑰和已啟用加密功能的容器,在將用戶端程式碼移轉至 1.0 版套件之後,您必須重新建立資料庫和容器。

Always Encrypted 功能旨在保護 Azure Cosmos DB 中儲存的敏感性資料,例如信用卡號碼或國民/地區身分證號碼 (例如美國社會安全號碼)。 Always Encrypted 可讓用戶端將用戶端應用程式內的敏感性資料加密,絕不向資料庫透露加密金鑰。

Always Encrypted 將用戶端加密功能帶給 Azure Cosmos DB。 在下列情況中,可能需要在用戶端加密資料:

  • 保護具有特定機密特性的敏感性資料: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 建立。

您可以:

  • 針對每個要加密的屬性各建立一個 DEK,或
  • 使用相同的 DEK 來加密多個屬性。

客戶管理的金鑰

DEK 由客戶自控金鑰 (CMK) 包裝之後儲存在 Azure Cosmos DB 中。 CMK 掌控 DEK 的包裝和解除包裝,有效控制能否存取以相應 DEK 加密的資料。 CMK 儲存體是可延伸的設計,預設實作是將 CMK 儲存在 Azure Key Vault 中。

加密金鑰

加密原則

類似於編製索引原則,加密原則是容器層級規格,描述如何加密 JSON 屬性。 建立容器時必須提供此原則,而且此原則不可變。 在目前的版本中,您無法更新加密原則。

針對您要加密的每個屬性,加密原則定義:

  • 格式為 /property 的屬性路徑。 目前僅支援頂層路徑,不支援巢狀路徑 (例如 /path/to/property)。
  • 加密和解密屬性時所用 DEK 的識別碼。
  • 加密類型。 可以是「隨機化」或「確定性」類型。
  • 加密屬性時使用的加密演算法。 指定的演算法可以覆寫建立金鑰時所定義的演算法 (如果相容)。

隨機化與確定性加密

Azure Cosmos DB 服務絕對看不到以 Always Encrypted 加密的屬性純文字。 不過,視屬性使用的加密類型而定,在加密的資料上仍支援一些查詢功能。 Always Encrypted 支援以下兩種加密:

  • 確定性加密:此加密類型對所有指定的純文字值和加密設定一律產生相同的加密值。 使用確定性加密可讓查詢在加密的屬性上執行等式篩選。 不過,這可能讓攻擊者在加密的屬性中檢查模式,以猜測加密值的相關資訊。 尤其,如果只有少數幾個可能的加密值,例如 True/False 或東/西/南/北區域,則更是如此。

  • 隨機化加密:此加密類型會使用更難預測的方式來加密資料。 隨機化加密比較安全,但無法讓查詢篩選加密的屬性。

若要深入了解 Always Encrypted 的確定性和隨機化加密,請參閱產生初始化向量 (IV)

設定 Azure Key Vault

開始使用 Always Encrypted 的第一個步驟是在 Azure Key Vault 中建立 CMK:

  1. 建立新的或瀏覽至現有的 Azure Key Vault 執行個體。
  2. 在 [金鑰] 區段中建立新的金鑰。
  3. 建立金鑰之後,瀏覽其目前的版本,並複製完整的金鑰識別碼:
    https://<my-key-vault>.vault.azure.net/keys/<key>/<version>. 如果您省略金鑰識別碼結尾的金鑰版本,則會使用最新版的金鑰。

接下來,您必須設定 Azure Cosmos DB SDK 如何存取 Azure Key Vault 執行個體。 此驗證是透過 Microsoft Entra 身分識別來完成。 雖然可使用任何一種身分識別,但您最可能使用 Microsoft Entra 應用程式的身分識別,或使用受控識別,作為用戶端程式碼與 Azure Key Vault 執行個體之間的 Proxy。 透過下列步驟,使用您的 Microsoft Entra 身分識別作為 Proxy:

  1. 從您的 Azure Key Vault 執行個體瀏覽至 [存取原則] 區段,然後新增原則:

    1. 在 [金鑰權限] 中,選取 [取得]、[列出]、[取消包裝金鑰]、[包裝金鑰]、[驗證] 及 [簽署]
    2. 在 [選取主體] 中,搜尋您的 Microsoft Entra 身分識別。

防止意外刪除 CMK

為了確保在意外刪除 CMK 之後不會失去對加密資料的存取權,建議您在 Azure Key Vault 執行個體上設定兩個屬性:虛刪除清除保護

如果您建立新的 Azure Key Vault 執行個體,請在建立期間啟用這些屬性:

新 Azure 金鑰保存庫 實例虛刪除和清除保護屬性的螢幕快照。

如果您使用現有的 Azure Key Vault 執行個體,您可以查看 Azure 入口網站上的 [屬性] 區段,以確認這些屬性是否已啟用。 如果這些屬性皆未啟用,請參閱下列其中一篇文章中的「啟用虛刪除」和「啟用清除保護」小節:

初始化 SDK

注意

目前支援適用於 Azure Cosmos DB 的 Always Encrypted:

若要使用 Always Encrypted,必須將 KeyResolver 的執行個體連結至 Azure Cosmos DB SDK 執行個體。 此類別定義於 Azure.Security.KeyVault.Keys.Cryptography 命名空間之中,用來與裝載 CMK 的金鑰存放區進行互動。

下列程式碼片段會使用 DefaultAzureCredential 類別來取得存取 Azure Key Vault 執行個體時所要使用的 Microsoft Entra 身分識別。 您可以在此找到建立各種不同 TokenCredential 類別的範例。

注意

您將需要額外的 Azure.Identity 套件 來存取 TokenCredential 類別。

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

建立資料加密金鑰

必須在父資料庫中建立資料加密金鑰,才能在容器中加密資料。

建立新的資料加密金鑰是透過呼叫 CreateClientEncryptionKeyAsync 方法並傳遞來完成:

  • 用於資料庫中唯一識別金鑰的字串識別碼。
  • 要與金鑰搭配使用的加密演算法。 目前只支援一種演算法。
  • 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 就會查閱加密原則,以找出需要加密的屬性和加密方式。 加密的結果為 Base 64 字串。

複雜類型的加密

  • 當需要加密的屬性是 JSON 陣列時,陣列的每個項目都加密。

  • 當需要加密的屬性是 JSON 物件時,只有物件的分葉值會加密。 中繼子屬性名稱維持純文字格式。

讀取加密的項目

發出點讀取 (依識別碼和分割區索引鍵來擷取單一項目)、查詢或讀取變更摘要時,不需要明確動作來解密加密的屬性。 這是因為:

  • 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 是以 key1 加密,而 property2 以 key2 加密,則只能存取 key1 的用戶端應用程式仍可讀取資料,但無法讀取 property2。 在這種情況下,您必須透過 SQL 查詢來讀取資料,並排除用戶端無法解密的屬性:SELECT c.property1, c.property3 FROM c

CMK 輪替

如果您懷疑目前的 CMK 已遭洩露,則可能需要「轉替」CMK (也就是使用新的 CMK 代替目前的 CMK)。 定期輪替 CMK 也是常用的安全性做法。 若要執行此輪替,您只需要提供新 CMK 的金鑰識別碼,此 CMK 應該用來包裝特定 DEK。 請注意,此作業不影響資料加密,但能保護 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,而且新的屬性也會自動加密。 此方法不需要任何資料移轉。

下一步