在 Microsoft .NET Data Provider for SQL Server 中使用 Always Encrypted

適用於 .NET Framework .NET .NET 標準

此文章提供如何使用 Always Encrypted具有安全記憶體保護區的 Always EncryptedMicrosoft .NET Data Provider for SQL Server 來開發 .NET 應用程式的相關資訊。

[永遠加密] 可讓用戶端應用程式加密敏感性資料,且永遠不會顯示資料或 SQL Server 或 Azure SQL Database 的加密金鑰。 已啟用 Always Encrypted 的驅動程式 (例如 Microsoft .NET Data Provider for SQL Server) 會在用戶端應用程式中明確地加密及解密敏感性資料,以達成此安全性。 驅動程式會自動判斷哪些查詢參數對應至敏感性資料庫資料行 (使用 Always Encrypted 保護),並在將資料傳遞至伺服器前,先加密這些參數的值。 同樣地,驅動程式會以清晰簡明的方式,將擷取自查詢結果的加密資料庫資料行資料進行解密。 如需詳細資訊,請參閱使用 Always Encrypted 開發應用程式使用 Always Encrypted 搭配安全記憶體保護區開發應用程式

Prerequisites

下表摘要說明搭配 Microsoft.Data.SqlClient 使用 Always Encrypted 所需的 .NET 平台。

支援 Always Encrypted 支援具有安全記憶體保護區的 Always Encrypted 目標框架 Microsoft.Data.SqlClient 版本 作業系統
.NET 框架 4.6+ 1.1.0+ Windows
.NET 核心 2.1+ 2.1.0+1 Windows、Linux、macOS
.NET 標準 2.0 2.1.0+ Windows、Linux、macOS
.NET 標準 2.1+ 2.1.0+ Windows、Linux、macOS

注意

1 在 Microsoft.Data.SqlClient 2.1.0 版之前,只有 Windows 上支援 Always Encrypted。

為應用程式查詢啟用 [永遠加密]

若要對以加密資料行為目標的參數進行加密,以及對查詢結果進行解密,最簡單的方式是將 Column Encryption Setting 連接字串關鍵字的值設定為 enabled

下列範例使用連接字串來啟用 Always Encrypted:

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting=enabled";
SqlConnection connection = new SqlConnection(connectionString);

下列程式碼片段為使用 SqlConnectionStringBuilder.ColumnEncryptionSetting 屬性的對等範例。

SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "server63";
builder.InitialCatalog = "Clinic";
builder.IntegratedSecurity = true;
builder.ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled;
SqlConnection connection = new SqlConnection(builder.ConnectionString);
connection.Open();

也可以針對個別查詢啟用 Always Encrypted。 請參閱以下的控制 Always Encrypted 的效能影響一節。 啟用 Always Encrypted 並不足以保證加密或解密成功。 您還需要確認以下事項:

  • 應用程式擁有 VIEW ANY COLUMN MASTER KEY DEFINITIONVIEW ANY COLUMN ENCRYPTION KEY DEFINITION 資料庫權限,這些權限是存取資料庫中關於 Always Encrypted 金鑰的元資料所必需的。 如需詳細資料,請參閱 Always Encrypted (資料庫引擎) 中的「資料庫權限」一節
  • 應用程式可以存取保護資料行加密金鑰的資料行主要金鑰,這些加密金鑰會加密查詢的資料庫資料行。

啟用具有安全記憶體保護區的 Always Encrypted

從 Microsoft.Data.SqlClient 1.1.0 版開始,驅動程式支援具有安全記憶體保護區的 Always Encrypted

如需使用記憶體保護區開發應用程式的一般資訊,請參閱使用具有安全記憶體保護區的 Always Encrypted 開發應用程式

若要為資料庫連線啟用記憶體保護區計算,除了啟用 Always Encrypted 之外,您還必須設定下列連接字串關鍵字 (如上一節所述):

  • Attestation Protocol - 指定證明通訊協定。

    • 如果未指定此關鍵字,則該連線會停用安全隔離區功能。
    • 如果您搭配虛擬化型安全性 (VBS) 記憶體保護區和主機守護者服務 (HGS) 使用 SQL Server,則此關鍵字的值應為 HGS
    • 如果您搭配 Intel SGX 記憶體保護區和 Microsoft Azure 證明使用 Azure SQL Database,則此關鍵字的值應為 AAS
    • 如果您使用的是搭配 VBS enclaves 的 Azure SQL Database 或 SQL Server,且想要略過證明,則此關鍵字的值應為 None。 需要 4.1 版或更新版本。

    注意

    「None」(無證明)是 Azure SQL Database 中 VBS 飛地目前唯一支援的選項。

  • Enclave Attestation URL - 指定證明 URL (證明服務端點)。 您必須向您的證明服務管理員取得您環境的證明資料 URL。

如需逐步教學,請參閱教學課程:使用安全記憶體保護區搭配 Always Encrypted 開發 .NET 應用程式

擷取及修改已加密資料行中的資料

一旦針對應用程式查詢啟用 Always Encrypted 之後,您就可以使用標準的 SqlClient API (請參閱在 ADO.NET 中擷取和修改資料 \(部分機器翻譯\)) 或 Microsoft.Data.SqlClient 命名空間 \(英文\) 中定義的 Microsoft .NET Data Provider for SQL Server API,來擷取或修改加密資料庫資料行中的資料。 如果您的應用程式具有必要的資料庫權限,而且可以存取資料行主要金鑰,則 Microsoft .NET Data Provider for SQL Server 會加密所有以加密資料行為目標的查詢參數,並解密擷取自加密資料行的資料,對應為資料庫結構描述中之資料行設定的 SQL Server 資料類型,傳回 .NET 類型純文字值。 若未啟用 Always Encrypted,使用目標加密資料行參數的查詢就會失敗。 只要查詢未包含針對加密資料行的參數,仍然可以從加密資料行擷取資料。 不過,Microsoft .NET Data Provider for SQL Server 將不會嘗試解密所有擷取自加密資料行的值,而應用程式將會接收二進位的加密資料 (作為位元組陣列)。

下表摘要說明查詢的行為,視 Always Encrypted 是否啟用而定:

查詢特性 Always Encrypted 已啟用,且應用程式可以存取金鑰和金鑰中繼資料 Always Encrypted 已啟用,且應用程式無法存取金鑰或金鑰中繼資料 [永遠加密] 已停用
使用以加密資料行為目標之參數的查詢。 以清晰簡明的方式加密參數值。 錯誤 錯誤
從加密資料行擷取資料且不含任何針對加密資料行之參數的查詢。 來自加密資料行的結果會以透明方式解密。 應用程式會接收 .NET 資料類型的純文字值,其對應到為加密資料行所設定的 SQL Server 類型。 錯誤 來自加密資料行的結果不會被解密。 應用程式收到位元組陣列 (byte[]) 形態的加密值。

以下範例將說明擷取和修改加密資料行中的資料。 這些範例假設目標資料表具有下列結構描述。 SSNBirthDate 資料行均已加密。

CREATE TABLE [dbo].[Patients]([PatientId] [int] IDENTITY(1,1),
 [SSN] [char](11) COLLATE Latin1_General_BIN2
 ENCRYPTED WITH (ENCRYPTION_TYPE = DETERMINISTIC,
 ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
 COLUMN_ENCRYPTION_KEY = CEK1) NOT NULL,
 [FirstName] [nvarchar](50) NULL,
 [LastName] [nvarchar](50) NULL,
 [BirthDate] [date]
 ENCRYPTED WITH (ENCRYPTION_TYPE = RANDOMIZED,
 ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
 COLUMN_ENCRYPTION_KEY = CEK1) NOT NULL
 PRIMARY KEY CLUSTERED ([PatientId] ASC) ON [PRIMARY])
 GO

插入資料範例

這個範例會將一個資料列插入 Patients 資料表中。 請注意下列詳細資料:

  • 範例程式碼中沒有任何需要加密的項目。 Microsoft .NET Data Provider for SQL Server 會自動偵測並加密以加密資料行為目標的 paramSSNparamBirthdate 參數。 此行為可讓加密對應用程式變得透明化。
  • 插入至資料庫資料行的值,包括加密的資料行,會傳遞為 SqlParameter 物件。 雖然將值傳送至未加密的資料行時,可以不使用 SqlParameter(不過仍強烈建議使用,因為這有助於防止 SQL 插入攻擊),但對於要傳送至加密資料行的值,則必須使用。 如果插入 SSNBirthDate 資料行中的值,是以內嵌在查詢陳述式中的常值形式傳遞,查詢就會失敗,因為 Microsoft .NET Data Provider for SQL Server 將無法判定目標加密資料行中的值,因此無法加密這些值。 因此,伺服器會認定它們與加密資料行不相容而拒絕它們。
  • 針對 SSN 資料行的參數資料類型設為 ANSI(非 Unicode)字串,對應至 SQL Server 的 char/varchar 資料類型。 如果參數的類型設為 Unicode 字串 (String),其對應為 nchar/nvarchar,則查詢會失敗,因為 Always Encrypted 不支援將已加密的 nchar/nvarchar 值轉換為已加密的 char/varchar 值。 如需資料類型對應的相關資訊,請參閱 SQL Server 資料型別對應
  • 插入到 BirthDate 資料行中的參數,其資料型別會透過 SqlParameter.SqlDbType Property 明確設定為目標 SQL Server 資料型別,而不是依賴在使用 SqlParameter.DbType Property 時,將 .NET 型別隱含對應至 SQL Server 資料型別的機制。 預設會將 DateTime 結構 對應到 SQL Server datetime 資料類型。 因為 BirthDate 資料行的資料類型是日期,而且 Always Encrypted 不支援將加密的日期時間值轉換成加密的日期值,所以使用預設的對應會造成錯誤。
string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting=enabled";

using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
    connection.Open();
    cmd.CommandText = @"INSERT INTO [dbo].[Patients] ([SSN], [FirstName], [LastName], [BirthDate]) VALUES (@SSN, @FirstName, @LastName, @BirthDate);";

    SqlParameter paramSSN = cmd.CreateParameter();
    paramSSN.ParameterName = @"@SSN";
    paramSSN.DbType = DbType.AnsiStringFixedLength;
    paramSSN.Direction = ParameterDirection.Input;
    paramSSN.Value = "795-73-9838";
    paramSSN.Size = 11;
    cmd.Parameters.Add(paramSSN);

    SqlParameter paramFirstName = cmd.CreateParameter();
    paramFirstName.ParameterName = @"@FirstName";
    paramFirstName.DbType = DbType.String;
    paramFirstName.Direction = ParameterDirection.Input;
    paramFirstName.Value = "Catherine";
    paramFirstName.Size = 50;
    cmd.Parameters.Add(paramFirstName);

    SqlParameter paramLastName = cmd.CreateParameter();
    paramLastName.ParameterName = @"@LastName";
    paramLastName.DbType = DbType.String;
    paramLastName.Direction = ParameterDirection.Input;
    paramLastName.Value = "Abel";
    paramLastName.Size = 50;
    cmd.Parameters.Add(paramLastName);

    SqlParameter paramBirthdate = cmd.CreateParameter();
    paramBirthdate.ParameterName = @"@BirthDate";
    paramBirthdate.SqlDbType = SqlDbType.Date;
    paramBirthdate.Direction = ParameterDirection.Input;
    paramBirthdate.Value = new DateTime(1996, 09, 10);
    cmd.Parameters.Add(paramBirthdate);

    cmd.ExecuteNonQuery();
}

擷取純文字資料範例

下列範例會示範根據加密值篩選資料,以及從加密資料行擷取純文字資料。

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting=enabled";
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
    connection.Open();
    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE SSN=@SSN";

    SqlParameter paramSSN = cmd.CreateParameter();
    paramSSN.ParameterName = @"@SSN";
    paramSSN.DbType = DbType.AnsiStringFixedLength;
    paramSSN.Direction = ParameterDirection.Input;
    paramSSN.Value = "795-73-9838";
    paramSSN.Size = 11;
    cmd.Parameters.Add(paramSSN);
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(@"{0}, {1}, {2}, {3}", reader[0], reader[1], reader[2], ((DateTime)reader[3]).ToShortDateString());
            }
        }
    }
}

注意

  • WHERE 子句中用於篩選 SSN 資料行的值需要使用 SqlParameter 來傳遞,因此 Microsoft .NET Data Provider for SQL Server 能夠明確地將其加密,然後傳送到資料庫。

  • 此程式將以純文字形式列印所有值,因為 Microsoft .NET Data Provider for SQL Server 會明確地解密擷取自 SSNBirthDate 資料行的資料。

  • 如果查詢使用了確定性加密來進行加密,則可以執行資料行的相等比較。

擷取加密的資料範例

若未啟用 Always Encrypted,只要查詢沒有針對加密資料行的參數,查詢仍可從加密資料行擷取資料。

下例會示範如何從加密資料行擷取二進位的加密資料。

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true";

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
    connection.Open();
    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [LastName]=@LastName";

    SqlParameter paramLastName = cmd.CreateParameter();
    paramLastName.ParameterName = @"@LastName";
    paramLastName.DbType = DbType.String;
    paramLastName.Direction = ParameterDirection.Input;
    paramLastName.Value = "Abel";
    paramLastName.Size = 50;
    cmd.Parameters.Add(paramLastName);
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(@"{0}, {1}, {2}, {3}", BitConverter.ToString((byte[])reader[0]), reader[1], reader[2], BitConverter.ToString((byte[])reader[3]));
            }
        }
    }
}

注意

  • 由於連接字串中並未啟用 Always Encrypted,因此查詢將會以位元組陣列 (程式會將值轉換為字串) 傳回加密的 SSNBirthDate 值。

  • 在已停用 [一律加密] 的情況下,從加密資料行擷取資料的查詢可以包含參數,只要這些參數都不是針對加密資料行。 上述依 LastName 篩選的查詢,在資料庫中未加密。 如果依 SSNBirthDate 篩選查詢,查詢就會失敗。

查詢加密資料行時避免常見問題

本節說明從 .NET 應用程式查詢加密資料行時常見的錯誤類別,以及如何避免的一些指導方針。

不支援的資料類型轉換錯誤

Always Encrypted 僅支援少數加密資料類型轉換。 如需支援的類型轉換詳細清單,請參閱 Always Encrypted。 請執行下列作業以免發生資料型別轉換錯誤︰

  • 設定以加密資料行為目標的參數類型,如此一來,參數的 SQL Server 資料類型就會與目標資料行的類型完全相同,或是支援將參數的 SQL Server 資料類型轉換成資料行的目標類型。 您可以使用 SqlParameter.SqlDbType 屬性,強制執行 .NET 資料類型到特定 SQL Server 資料類型的必要對應。
  • 確認小數和數值 SQL Server 資料類型資料行為目標之參數的有效位數和小數位數,和為目標資料行設定的有效位數和小數位數相同。
  • 驗證在修改目標資料行值的查詢中,以 SQL Server datetime2、datetimeoffset 或 time 資料類型資料行為目標的參數,其精確度不會高於目標資料行的精確度。

因為傳送純文字,而不是傳送加密值所造成的錯誤。

任何要寫入加密資料行的值,都必須在應用程式內完成加密。 嘗試對加密資料行插入/修改或以純文字值篩選,會造成類似錯誤如下:

Microsoft.Data.SqlClient.SqlException (0x80131904): Operand type clash: varchar is incompatible with varchar(8000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'Clinic') collation_name = 'SQL_Latin1_General_CP1_CI_AS'

若要避免這類錯誤,請確認以下事項:

  • 針對加密資料行的應用程式查詢已啟用 Always Encrypted (在連線字串中,或在特定查詢的 SqlCommand 物件中)。
  • 您可以使用 SqlParameter 將資料傳送至加密的資料行。 下列範例顯示一個查詢:它錯誤地以加密資料行 (SSN) 上的常值/常數進行篩選,而非將該常值傳入 SqlParameter 物件中。
using (SqlCommand cmd = connection.CreateCommand())
{
    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE SSN = '795-73-9838'";
    cmd.ExecuteNonQuery();
}

使用資料行主要金鑰存放區

若要加密參數值或解密查詢結果的資料,Microsoft .NET Data Provider for SQL Server 需要取得為目標資料行設定的資料行加密金鑰。 資料行加密金鑰是以加密形式儲存在資料庫中繼資料中。 每個資料行加密金鑰都有一個對應的資料行主金鑰,用來加密該資料行加密金鑰。 資料庫中繼資料不會儲存資料行主要金鑰,而是只包含金鑰存放區的相關資訊,金鑰存放區包含特定的資料行主要金鑰,以及金鑰在金鑰存放區中的位置。

為取得資料行加密金鑰的純文字值,Microsoft .NET Data Provider for SQL Server 需先取得有關資料行加密金鑰及其對應資料行主要金鑰的中繼資料。 其接著會使用中繼資料中的資訊來連絡包含資料行主要金鑰的金鑰存放區,並將加密的資料行加密金鑰解密。 Microsoft .NET Data Provider for SQL Server 會使用資料行主要金鑰存放區提供者與金鑰存放區通訊,也就是衍生自 SqlColumnEncryptionKeyStoreProvider 類別 的類別執行個體。

取得資料行加密金鑰的流程:

  1. 若已針對查詢啟用 Always Encrypted,則當查詢具有參數時,Microsoft .NET Data Provider for SQL Server 就會明確呼叫 sys.sp_describe_parameter_encryption,來擷取以加密資料行為目標之參數的加密中繼資料。 針對查詢結果所包含的加密資料,SQL Server 會自動附加加密中繼資料。 資料行主要金鑰的資訊包括:

    • 封裝包含資料行主金鑰之金鑰存放區的金鑰存放區提供者名稱。
    • 金鑰路徑,其指定金鑰存放區中資料行主要金鑰的位置。

    資料行加密金鑰的資訊包括:

    • 資料欄加密金鑰的加密值。
    • 用於加密資料行加密金鑰的演算法名稱。
  2. Microsoft .NET Data Provider for SQL Server 會使用資料行主要金鑰存放區提供者的名稱,在內部資料結構中查閱提供者物件,該物件為衍生自 SqlColumnEncryptionKeyStoreProvider 類別的類別執行個體。

  3. 若要將資料行加密金鑰解密,Microsoft .NET Data Provider for SQL Server 會呼叫 SqlColumnEncryptionKeyStoreProvider.DecryptColumnEncryptionKey() 方法,來傳遞資料行主要金鑰路徑、資料行加密金鑰的加密值及加密演算法的名稱,用以產生加密的資料行加密金鑰。

使用內建資料行主密鑰存放區提供者

Microsoft .NET Data Provider for SQL Server 隨附下列內建的資料行主要金鑰存放區提供者,並已預先使用特定的提供者名稱註冊 (用來查找提供者)。 只有 Windows 支援這些內建的金鑰存放區提供者。

類別 描述 提供者(查詢)名稱 平台
SqlColumnEncryptionCertificateStoreProvider 類別 Windows 憑證存放區的提供者。 MSSQL_CERTIFICATE_STORE Windows
SqlColumnEncryptionCngProvider 類別 支援 Microsoft Cryptography API: Next Generation (CNG) API 的金鑰存放區提供者。 一般而言,這類型的存放區是硬體安全性模組,可保護和管理數位金鑰,並提供密碼編譯處理的實體裝置。 MSSQL_CNG_STORE Windows
SqlColumnEncryptionCspProvider 類別 支援 Microsoft Cryptography API (CAPI)的金鑰存放區提供者。 一般而言,這類型的存放區是硬體安全性模組,可保護和管理數位金鑰,並提供密碼編譯處理的實體裝置。 MSSQL_CSP_PROVIDER Windows

您不需進行任何應用程式程式碼變更,即可使用這些提供者,但請注意下列細節:

  • 您 (或您的 DBA) 需要確認設定在資料行主要金鑰中繼資料的提供者名稱是否正確,且資料行主要金鑰路徑符合指定提供者有效的金鑰路徑格式。 建議你使用像 SQL Server Management Studio 這類工具來設定金鑰,當發出 CREATE COLUMN MASTER KEY (Transact-SQL) 語句時,這些工具會自動產生有效的提供者名稱和金鑰路徑。 如需詳細資訊,請參閱 使用 SQL Server Management Studio 設定永遠加密使用 PowerShell 設定永遠加密
  • 確定應用程式可以存取金鑰存放區中的金鑰。 此程序可能包括:視金鑰存放區而定,授與您的應用程式存取金鑰和/或金鑰存放區的權限,或執行其他金鑰存放區專屬的設定步驟。 例如,若要存取實作 CNG 或 CAPI 的金鑰存放區 (例如硬體安全性模組),您需要確定應用程式機器上已安裝存放區實作 CNG 或 CAPI 的程式庫。 如需詳細資料,請參閱建立及儲存 Always Encrypted 的資料行主要金鑰

使用 Azure Key Vault 提供者

Azure Key Vault 是用來儲存及管理 Always Encrypted 資料行主金鑰的便利選項(尤其是在您的應用程式託管於 Azure 時)。 Microsoft .NET Data Provider for SQL Server 不包含適用於 Azure Key Vault 的內建資料行主要金鑰存放區提供者,但可以 NuGet 套件形式提供 (Microsoft.Data.SqLClient.AlwaysEncrypted.AzureKeyVaultProvider),讓您輕鬆整合應用程式。 如需詳細資訊,請參閱 一律加密 - 透過資料加密並將您的加密金鑰儲存在 Azure 金鑰保存庫,來保護 SQL Database 中的機密資料

類別 描述 提供者(查詢)名稱 平台
SqlColumnEncryptionAzureKeyVaultProvider 類別 Azure Key Vault 的提供者。 AZURE_KEY_VAULT Windows、Linux、macOS

.NET 支援能力

版本 Microsoft.Data.SqlClient 版本 .NET 平台
3.0.0 3.0.0+ .NET Framework 4.6.1+、.NET Core 2.1+、.NET Standard 2.0+
2.0.0 1.1.3+
2.1.0+
.NET Framework 4.6.1+、.NET Core 2.1+
.NET 標準 2.0+
1.2.0 1.0.19269.1+
2.1.0+
.NET Framework 4.6+、.NET Core 2.1+
.NET 標準 2.0+
1.1.0 1.0.19269.1+ .NET Framework 4.6+、.NET Core 2.1+
1.0.0 1.0.19269.1+ .NET Framework 4.6+、.NET Core 2.1+

v3.0.0 起,Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 在使用 SqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnectionSqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand API 註冊提供者時,支援資料行加密金鑰快取功能。

v2.0.0 起,Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 支援新的 Azure.CoreAzure.Identity API 透過 Azure Key Vault 執行驗證。 TokenCredential 實作的執行個體現在可以傳遞至 SqlColumnEncryptionAzureKeyVaultProvider 建構函式,以初始化 Azure Key Vault 提供者物件。

注意

Microsoft.Data.SqLClient.AlwaysEncrypted.AzureKeyVaultProvider 支援 Azure Key Vault 中的保存庫和受控 HSM

如需示範使用 Azure Key Vault 加密/解密的範例,請參閱使用 Always Encrypted 的 Azure Key Vault使用具有安全記憶體保護區的 Always Encrypted 的 Azure Key Vault

實作自訂資料行主金鑰存放區提供者

如果您想要將資料行主要金鑰儲存於現有提供者不支援的金鑰存放區,可以透過擴充 SqlColumnEncryptionKeyStoreProvider 類別,並使用下列其中一種方法註冊自訂提供者,以實作該提供者:

public class MyCustomKeyStoreProvider : SqlColumnEncryptionKeyStoreProvider
{
    public const string ProviderName = "MY_CUSTOM_STORE";

    public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
    {
        // Logic for encrypting a column encrypted key.
    }
    public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] EncryptedColumnEncryptionKey)
    {
        // Logic for decrypting a column encrypted key.
    }
}  
class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers =
            new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
        providers.Add(MyCustomKeyStoreProvider.ProviderName, new MyCustomKeyStoreProvider());
        SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
        // ...
    }
}

資料行加密金鑰快取優先順序

本節適用於 Microsoft .NET Data Provider for SQL Server 3.0 版和更新版本。

Microsoft .NET Data Provider for SQL Server 不會快取在連線或命令執行個體上註冊之自訂金鑰存放區提供者所解密的資料行加密金鑰 (CEK)。 自訂金鑰存放區提供者應實作自己的 CEK 快取機制。

Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProviderv3.0.0 版本開始,每個 Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 執行個體都有自己的 CEK 快取實作。 當在連線或命令執行個體上註冊時,由 Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 執行個體解密的 CEK 會在該執行個體離開作用範圍時清除:

    class Program
    {
#pragma warning disable CS0649 // Field is never assigned to
        private static string connectionString;
#pragma warning restore CS0649

        static void Main()
        {
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                using (SqlCommand command = connection.CreateCommand())
                {
                    Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
                    SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
                    customKeyStoreProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
                    command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customKeyStoreProviders);
                    // Perform database operation using Azure Key Vault Provider
                    // Any decrypted column encryption keys will be cached
                } // Column encryption key cache of "azureKeyVaultProvider" is cleared when "azureKeyVaultProvider" goes out of scope
            }
        }
    }

注意

如果使用 SqlConnection.RegisterColumnEncryptionKeyStoreProviders 方法在驅動程式全域註冊金鑰存放區提供者執行個體,驅動程式將會停用自訂金鑰存放區提供者實作的 CEK 快取。 任何 CEK 快取實作都應該在快取 CEK 之前參考 SqlColumnEncryptionKeyStoreProvider.ColumnEncryptionKeyCacheTtl 的值;如果值為零,則不快取。 這可避免在嘗試設定金鑰快取時重複快取及造成使用者混淆。

註冊自訂資料行主金鑰存放區提供者

本章節適用於提供者的 3.0 版和更新版本。

您可以在三個不同層級向驅動程式註冊自訂主要金鑰存放區提供者。 三種註冊的優先順序如下:

  • 如果各命令的註冊項目不為空,系統將予以檢查。
  • 如果依據命令的註冊是空的,則檢查依據連線的註冊是否為空。
  • 如果各連線的註冊為空,則會檢查全域註冊。

一旦在某個註冊層級找到任何金鑰存放區提供者,驅動程式就不會退回到其他註冊層級去搜尋提供者。 如果提供者已註冊,但在某個層級找不到正確的提供者,則會擲回例外,其中只包含該次檢查之註冊中的已註冊提供者。

適用於 Windows 憑證存放區、CNG 存放區和 CSP 的內建資料行主金鑰存放區提供者已預先註冊。

這三個註冊層級支援查詢加密資料時的不同案例。 適當的方法可用來確保應用程式使用者在可提供必要的資料行主要金鑰時,能夠向包含資料行主要金鑰的金鑰存放區進行驗證,以存取純文字資料。

會在多個使用者之間共用 SqlConnection 執行個體的應用程式,可能會想要使用 SqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand。 每個使用者都必須在 SqlCommand 執行個體上註冊金鑰存放區提供者,才能執行查詢以存取加密的資料行。 如果金鑰存放區提供者能夠透過使用者獲得的認證存取金鑰存放區中的必要資料行主要金鑰,查詢就會成功。

對於為每個使用者建立一個 SqlConnection 執行個體的應用程式,可能會想要使用 SqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection。 透過此方法註冊的金鑰存放區提供者,可供連線用於任何存取加密資料的查詢。

使用 SqlConnection.RegisterColumnEncryptionKeyStoreProviders 註冊的金鑰存放區提供者,會在向金鑰存放區進行驗證時,使用應用程式提供的身分識別。

下列範例顯示在連線執行個體上註冊之自訂資料行主要金鑰存放區提供者的優先順序:

class Program
{
    static void Main(string connectionString)
    {
        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
        MyCustomKeyStoreProvider myProvider = new MyCustomKeyStoreProvider();
        customKeyStoreProviders.Add("MY_CUSTOM_STORE", myProvider);
        // Registers the provider globally
        SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customKeyStoreProviders);

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            customKeyStoreProviders.Clear();
            SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
            customKeyStoreProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
            // Registers the provider on the connection
            // These providers will take precedence over globally registered providers
            connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);
        }
    }
}

下列範例顯示在命令執行個體上註冊的自訂資料行主要金鑰存放區提供者的優先順序:

class Program
{
    static void Main(string connectionString)
    {
        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
        MyCustomKeyStoreProvider firstProvider = new MyCustomKeyStoreProvider();
        customKeyStoreProviders.Add("FIRST_CUSTOM_STORE", firstProvider);
        // Registers the provider globally
        SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customKeyStoreProviders);

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            customKeyStoreProviders.Clear();
            MyCustomKeyStoreProvider secondProvider = new MyCustomKeyStoreProvider();
            customKeyStoreProviders.Add("SECOND_CUSTOM_STORE", secondProvider);
            // Registers the provider on the connection
            connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);

            using (SqlCommand command = connection.CreateCommand())
            {
                customKeyStoreProviders.Clear();
                SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
                customKeyStoreProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
                // Registers the provider on the command
                // These providers will take precedence over connection-level providers and globally registered providers
                command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customKeyStoreProviders);
            }
        }
    }
}

使用資料行主要金鑰存放區提供者以程式設計方式佈建金鑰

Microsoft .NET Data Provider for SQL Server 存取加密資料行時,會明確尋找並呼叫正確的資料行主要金鑰存放區提供者,以解密資料行加密金鑰。 一般來說,一般應用程式程式碼不會直接呼叫資料行主要金鑰存放區提供者。 但是,您可以明確具現並呼叫提供者,以程式設計方式建立及管理 Always Encrypted 金鑰:產生加密的資料行加密金鑰,以及解密資料行加密金鑰(例如作為資料行主金鑰輪替的一部分)。 如需詳細資訊,請參閱 Always Encrypted 的金鑰管理概觀。 只有使用自訂的金鑰存放區提供者時,才可能需要實作您自己的金鑰管理工具。 使用儲存在金鑰存放區的金鑰時,凡是有內建提供者或位在 Azure Key Vault 中的金鑰,皆可使用 SQL Server Management Studio 或 PowerShell 等現有工具,來管理和佈建金鑰。 下列範例示範如何產生資料行加密金鑰,以及使用 SqlColumnEncryptionCertificateStoreProvider 類別 \(英文\),利用憑證來加密金鑰。

using System.Security.Cryptography;
static void Main(string[] args)
{
    byte[] EncryptedColumnEncryptionKey = GetEncryptedColumnEncryptonKey();
    Console.WriteLine("0x" + BitConverter.ToString(EncryptedColumnEncryptionKey).Replace("-", ""));
    Console.ReadKey();
}

static byte[]  GetEncryptedColumnEncryptonKey()
{
    int cekLength = 32;
    String certificateStoreLocation = "CurrentUser";
    String certificateThumbprint = "AA11BB22CC33DD44EE55FF66AA77BB88CC99DD00";
    // Generate the plaintext column encryption key.
    byte[] columnEncryptionKey = new byte[cekLength];
    RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
    rngCsp.GetBytes(columnEncryptionKey);

    // Encrypt the column encryption key with a certificate.
    string keyPath = String.Format(@"{0}/My/{1}", certificateStoreLocation, certificateThumbprint);
    SqlColumnEncryptionCertificateStoreProvider provider = new SqlColumnEncryptionCertificateStoreProvider();
    return provider.EncryptColumnEncryptionKey(keyPath, @"RSA_OAEP", columnEncryptionKey);
}

控制 Always Encrypted 的效能影響

由於 Always Encrypted 是一項用戶端加密技術,因此,所觀察到的效能額外負荷大部分都是在用戶端,而非資料庫中。 除了加密和解密作業成本之外,用戶端上效能額外負荷的其他來源如下:

  • 額外反覆存取資料庫以擷取查詢參數的中繼資料。
  • 呼叫資料行主要金鑰存放區以存取資料行主要金鑰。

此節描述 Microsoft .NET Data Provider for SQL Server 的內建效能最佳化,以及如何控制上述兩個因素對效能的影響。

控制擷取查詢參數中繼資料時的往返次數

如果已針對連線啟用 Always Encrypted,Microsoft .NET Data Provider for SQL Server 預設將針對每個參數化查詢呼叫 sys.sp_describe_parameter_encryption,以將查詢陳述式 (不含任何參數值) 傳遞至 SQL Server。 sys.sp_describe_parameter_encryption 會分析查詢陳述式,以找出是否有任何參數需要加密;如果有,則會對每個需要加密的參數傳回加密相關資訊,讓 Microsoft .NET Data Provider for SQL Server 能夠加密參數值。 上述行為可對用戶端應用程式確保高透明度。 只要在 SqlParameter 物件中將要寫入加密資料行的值傳遞給 Microsoft .NET Data Provider for SQL Server,應用程式 (及應用程式開發人員) 就不需要知道哪些查詢會存取加密資料行。

查詢中繼資料快取

Microsoft .NET Data Provider for SQL Server 會快取每個查詢陳述式的 sys.sp_describe_parameter_encryption 結果。 因此,如果多次執行同一個查詢陳述式,驅動程式就只會呼叫一次 sys.sp_describe_parameter_encryption。 查詢陳述式的加密中繼資料快取可大幅降低從資料庫擷取中繼資料所產生的效能開銷。 預設啟用快取。 您可以將 SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled 屬性設定為 false,以停用參數中繼資料快取,但除非是如下所述的罕見情況,否則不建議這樣做:

請考慮具有兩個不同結構描述的資料庫︰s1s2。 每個結構描述都包含一個與其同名的資料表:ts1.ts2.t 資料表的定義相同,但與加密相關的屬性除外:名為 c 的資料行在 s1.t 中未加密,但在 s2.t 中加密。 資料庫有兩個使用者︰u1u2u1 使用者的預設結構描述是 s1u2 的預設結構描述是 s2。 .NET 應用程式會開啟兩個資料庫連線,其中一個連線上模擬 u1 使用者,另一個連線上模擬 u2 使用者。 應用程式會在使用者 c 的連線上,傳送目標為 u1 資料行之參數的查詢 (此查詢並未指定結構描述,因此會假設使用預設的使用者結構描述)。 接下來,應用程式會在 u2 使用者的連線上傳送相同的查詢。 如果已啟用查詢中繼資料快取,則在第一次查詢後,將在快取中填入中繼資料,以指出 c 資料行 (查詢參數的目標) 並未加密。 當第二次查詢有相同的查詢陳述式時,就會使用儲存在快取中的資訊。 因此,驅動程式將在未加密參數的情況下傳送查詢 (這不正確,因為已將目標資料行 s2.t.c 加密),以將參數的純文字值洩漏給伺服器。 伺服器會偵測到不相容,並強制驅動程式重新整理快取,因此應用程式會明確重新傳送具有正確加密參數值的查詢。 這種情況應該停用快取,以免向伺服器洩漏機密值。

在查詢層級設定 [永遠加密]

若要控制擷取加密中繼資料對參數化查詢的效能影響,您可以針對個別查詢啟用 Always Encrypted,而不是針對連線來設定它。 如此一來,您可以確保只有在查詢的參數是以加密資料行為目標時,而且您已知情況確實如此,才會呼叫 sys.sp_describe_parameter_encryption。 不過請注意,如此一來就會降低加密的透明度︰如果您變更資料庫資料行的加密屬性,可能就需要變更應用程式的程式碼,以配合結構描述變更。

注意

在查詢層級設定 Always Encrypted,會限制參數加密中繼資料快取所帶來的效能提升。

若要控制個別查詢的 [永遠加密] 行為,您需要使用 SqlCommandSqlCommandColumnEncryptionSetting的這個建構函式。 以下是一些實用的方針:

  • 如果用戶端應用程式的大多數查詢都會存取加密資料行:
    • 資料行加密設定連接字串關鍵字設為 [啟用] 。
    • 針對不會存取任何加密資料行的個別查詢,將 SqlCommandColumnEncryptionSetting 設為 Disabled。 此設定會禁止呼叫 sys.sp_describe_parameter_encryption 及嘗試解密結果集內的任何值。
    • 針對不含任何需要加密的參數,但會從加密資料行擷取資料的個別查詢,將 SqlCommandColumnEncryptionSetting 設為 ResultSetOnly。 此設定會禁止呼叫 sys.sp_describe_parameter_encryption 和參數加密。 查詢可解密已加密資料行中的結果。
  • 如果用戶端應用程式的大多數查詢不會存取加密資料行:
    • 資料行加密設定連接字串關鍵字設為 [停用] 。
    • 針對有任何參數需要加密的個別查詢,將 SqlCommandColumnEncryptionSetting 設為 Enabled。 此設定會允許呼叫 sys.sp_describe_parameter_encryption 及解密擷取自加密資料行的任何查詢結果。
    • 針對不含任何需要加密的參數,但會從加密資料行擷取資料的查詢,將 SqlCommandColumnEncryptionSetting 設為 ResultSetOnly。 此設定會禁止呼叫 sys.sp_describe_parameter_encryption 和參數加密。 查詢即可解密從加密資料行取得的結果。

在下列範例中,資料庫連線已停用 Always Encrypted。 應用程式發出的查詢具有以 LastName 資料行為目標的參數,而此資料行未加密。 查詢會從加密的 SSNBirthDate 資料行擷取資料。 在這種情況下,不需要呼叫 sys.sp_describe_parameter_encryption 以擷取加密中繼資料。 不過,需要啟用查詢結果的解密,以使應用程式從兩個加密資料行接收純文字值。 SqlCommandColumnEncryptionSettingResultSetOnly 設定用於確保這一點。

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true";
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(@"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [LastName]=@LastName",
connection, null, SqlCommandColumnEncryptionSetting.ResultSetOnly))
{
    connection.Open();
    SqlParameter paramLastName = cmd.CreateParameter();
    paramLastName.ParameterName = @"@LastName";
    paramLastName.DbType = DbType.String;
    paramLastName.Direction = ParameterDirection.Input;
    paramLastName.Value = "Abel";
    paramLastName.Size = 50;
    cmd.Parameters.Add(paramLastName);
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(@"{0}, {1}, {2}, {3}", reader[0], reader[1], reader[2], ((DateTime)reader[3]).ToShortDateString());
            }
        }
    }
}

資料行加密金鑰快取

為了減少呼叫資料行主要金鑰存放區來解密資料行加密金鑰的次數,Microsoft .NET Data Provider for SQL Server 會將純文字資料行加密金鑰快取於記憶體中。 提供者從資料庫中繼資料中接收加密資料行加密金鑰值之後,驅動程式會先嘗試尋找對應加密金鑰值的純文字資料行加密金鑰。 只有在快取中找不到已加密的資料行加密金鑰值時,驅動程式才會呼叫包含資料行主金鑰的金鑰存放區。

基於安全性考量,快取項目會在可設定的存留期間(TTL)到期後移除。 預設的存留時間值是 2 小時。 如果您對資料行加密金鑰以純文字形式在應用程式中快取的時間長度有更嚴格的安全性需求,則可以使用 SqlConnection.ColumnEncryptionKeyCacheTtl 屬性 來變更該時間。

使用 SqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnectionSqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand 註冊的自訂金鑰存放區提供者,其已解密的資料行加密金鑰不會由 Microsoft .NET Data Provider for SQL Server 快取。 反之,自訂金鑰存放區提供者必須實作自己的快取機制。 Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider v3.0.0 及以上版本內建自己的快取實作。

為了支援相同應用程式的不同使用者可能執行多個查詢的案例,自訂金鑰存放區提供者可以對應至使用者,並在該使用者專屬的連線或命令執行個體上註冊。 以下範例顯示如何針對同一位使用者,在不同的 SqlCommand 物件之間重用 Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 的執行個體。 其資料行加密金鑰快取會在多個查詢之間保存,以減少金鑰存放區的往返次數:

using Azure.Identity;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
using System.Collections.Generic;

class Program
{
    // Maps a SqlColumnEncryptionAzureKeyVaultProvider to some object that represents a user
    static Dictionary<object, SqlColumnEncryptionAzureKeyVaultProvider> providerByUser = new();

    void ExecuteSelectQuery(object user, SqlConnection connection)
    {
        // Check if the user already has a SqlColumnEncryptionAzureKeyVaultProvider
        SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = providerByUser[user];
        if (azureKeyVaultProvider is null)
        {
            // Create a new SqlColumnEncryptionAzureKeyVaultProvider with the user's credentials and save it for future use
            azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
            providerByUser[user] = azureKeyVaultProvider;
        }

        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customProviders = new();
        customProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);

        using SqlCommand command = new("SELECT * FROM Customers", connection);
        command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customProviders);
        // Perform database operations
        // Any decrypted column encryption keys will be cached by azureKeyVaultProvider
    }

    void ExecuteUpdateQuery(object user, SqlConnection connection)
    {
        // Check if the user already has a SqlColumnEncryptionAzureKeyVaultProvider
        SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = providerByUser[user];
        if (azureKeyVaultProvider is null)
        {
            // Create a new SqlColumnEncryptionAzureKeyVaultProvider with the user's credentials and save it for future use
            azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
            providerByUser[user] = azureKeyVaultProvider;
        }

        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customProviders = new();
        customProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);

        using SqlCommand command = new("UPDATE Customers SET Name = 'NewName' WHERE CustomerId = 1", connection);
        command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customProviders);
        // Perform database operations
        // Any decrypted column encryption keys will be cached by azureKeyVaultProvider
    }
}

對遭入侵的 SQL Server 啟用額外保護

根據預設,Microsoft .NET Data Provider for SQL Server 依賴資料庫系統 (SQL Server 或 Azure SQL Database) 提供有關資料庫中哪些資料行已加密及加密方法的中繼資料。 加密中繼資料可讓 Microsoft .NET Data Provider for SQL Server 加密查詢參數及解密查詢結果,而不需任何應用程式輸入,這可大幅降低需要在應用程式中進行變更的次數。 不過,如果 SQL Server 處理序遭到入侵,且攻擊者竄改了 SQL Server 傳送到 Microsoft .NET Data Provider for SQL Server 的中繼資料,該攻擊者或許就能竊取敏感性資訊。 本章節描述針對此類攻擊提供多一層防護的 API,代價是降低透明度。

強制參數加密

Microsoft .NET Data Provider for SQL Server 在將參數化查詢傳送到 SQL Server 之前,會先要求 SQL Server (藉由呼叫 sys.sp_describe_parameter_encryption) 分析查詢陳述式,並提供查詢中哪些參數應該加密的相關資訊。 遭入侵的 SQL Server 執行個體可能會傳送中繼資料,指出該參數並非以加密資料行為目標,藉此誤導 Microsoft .NET Data Provider for SQL Server,即使該資料行在資料庫中已加密。 因此,Microsoft .NET Data Provider for SQL Server 不會加密參數值,並會將其以純文字形式傳送到遭入侵的 SQL Server 執行個體。

為避免這類的攻擊,應用程式可將參數的 SqlParameter.ForceColumnEncryption 屬性 設為 true。 如果 Microsoft .NET Data Provider for SQL Server 從伺服器接收到的中繼資料指出參數不需要加密,則此設定將導致其擲回例外狀況。

雖然使用 SqlParameter.ForceColumnEncryption 屬性有助於改善安全性,但也會降低對用戶端應用程式加密的透明度。 如果更新資料庫結構描述來變更加密的資料行集合,您可能也需要變更應用程式。

下列程式碼範例示範如何使用 SqlParameter.ForceColumnEncryption 屬性,來防止將社會安全號碼以純文字形式傳送到資料庫。

using (SqlCommand cmd = _sqlconn.CreateCommand())
{
    // Use parameterized queries to access Always Encrypted data.

    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [SSN] = @SSN;";

    SqlParameter paramSSN = cmd.CreateParameter();
    paramSSN.ParameterName = @"@SSN";
    paramSSN.DbType = DbType.AnsiStringFixedLength;
    paramSSN.Direction = ParameterDirection.Input;
    paramSSN.Value = ssn;
    paramSSN.Size = 11;
    paramSSN.ForceColumnEncryption = true;
    cmd.Parameters.Add(paramSSN);

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        // Do something.
    }
}

設定受信任的資料行主要金鑰路徑

SQL Server 針對以加密資料行為目標的查詢參數及擷取自加密資料行的結果所傳回的加密中繼資料,包括識別金鑰存放區之資料行主要金鑰的金鑰路徑,以及金鑰存放區的金鑰位置。 如果 SQL Server 執行個體遭到入侵,它可能會傳送金鑰路徑,以將 Microsoft .NET Data Provider for SQL Server 導向到受攻擊者控制的位置。 如果金鑰存放區要求應用程式驗證,此流程可能會洩漏金鑰存放區認證。

為了避免此類攻擊,應用程式可以使用 SqlConnection.ColumnEncryptionTrustedMasterKeyPaths 屬性 \(英文\),為指定的伺服器指定受信任的金鑰路徑清單。 如果 Microsoft .NET Data Provider for SQL Server 接收到受信任金鑰路徑清單以外的金鑰路徑,就會擲回例外狀況。

雖然設定信任的金鑰路徑可提升應用程式安全性,但您每次變換資料行主要金鑰 (每次資料行主要金鑰路徑變更) 時,都必須變更應用程式的程式碼和 (或) 設定。

下例示範如何設定受信任的資料行主要金鑰路徑︰

// Configure trusted key paths to protect against fake key paths sent by a compromised SQL Server instance
// First, create a list of trusted key paths for your server
List<string> trustedKeyPathList = new List<string>();
trustedKeyPathList.Add("CurrentUser/my/425CFBB9DDDD081BB0061534CE6AB06CB5283F5Ea");

// Register the trusted key path list for your server
SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.Add(serverName, trustedKeyPathList);

使用 SqlBulkCopy 複製加密的資料

使用 SqlBulkCopy,您可以將已加密並儲存在某份資料表的資料,複製到另一份資料表,而不需要解密資料。 若要這樣做:

  • 請確定目標資料表的加密組態與來源資料表的組態完全一致。 特別是,這兩份資料表都必須加密相同的資料行,且這些資料行必須使用相同的加密類型和相同的加密金鑰來加密。 如果任一目標資料行的加密方式不同於其對應的來源資料行,您將無法在複製作業後,解密目標資料表中的資料。 資料會損毀。
  • 將連線到來源資料表和目標資料表的兩個資料庫連線都設定為不啟用 Always Encrypted。
  • 設定 AllowEncryptedValueModifications 選項 (請參閱 SqlBulkCopyOptions \(英文\))。

注意

請謹慎指定 AllowEncryptedValueModifications。 此設定可能會導致資料庫損毀,因為 Microsoft .NET Data Provider for SQL Server 不會檢查資料是否確實加密,或是否使用與目標資料行相同的加密類型、演算法和金鑰正確加密。

以下是將資料從某份資料表複製到另一份資料表的範例。 假設 SSNBirthDate 資料行均已加密。

static public void CopyTablesUsingBulk(string sourceTable, string targetTable)
{
    string sourceConnectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true";
    string targetConnectionString = "Data Source=server64; Initial Catalog=Clinic; Integrated Security=true";
    using (SqlConnection connSource = new SqlConnection(sourceConnectionString))
    {
        connSource.Open();
        using (SqlCommand cmd = new SqlCommand(string.Format("SELECT [PatientID], [SSN], [FirstName], [LastName], [BirthDate] FROM {0}", sourceTable), connSource))
        {
            using (SqlDataReader reader = cmd.ExecuteReader())
            using (SqlBulkCopy copy = new SqlBulkCopy(targetConnectionString, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.AllowEncryptedValueModifications))
            {
                copy.EnableStreaming = true;
                copy.DestinationTableName = targetTable;
                copy.WriteToServer(reader);
            }
        }
    }
}

Always Encrypted API 參考文件

命名空間:Microsoft.Data.SqlClient

組件: Microsoft.Data.SqlClient.dll

名稱 描述
SqlColumnEncryptionCertificateStoreProvider 類別 Windows 憑證存放區的金鑰存放區提供者。
SqlColumnEncryptionCngProvider 類別 Microsoft 新一代密碼編譯 API (CNG) 的金鑰儲存提供者。
SqlColumnEncryptionCspProvider 類別 適用於以 Microsoft CAPI 為基礎的密碼編譯服務提供者 (CSP) 的金鑰儲存提供者。
SqlColumnEncryptionKeyStoreProvider 類別 金鑰存放區提供者的基底類別。
SqlCommandColumnEncryptionSetting 列舉 用來控制個別查詢之 Always Encrypted 功能行為的設定。
SqlConnectionAttestationProtocol 枚舉 指定在搭配安全記憶體保護區使用 Always Encrypted 時所使用的證明通訊協定值
SqlConnectionColumnEncryptionSetting 列舉 讓資料庫連接得以加密和解密的設定。
SqlConnectionStringBuilder.ColumnEncryptionSetting 屬性 在連接字串中取得及設定 [永遠加密]。
SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled 屬性 啟用和停用加密查詢中繼資料快取。
SqlConnection.ColumnEncryptionKeyCacheTtl 屬性 取得並設定資料行加密金鑰快取中項目的存留期。
SqlConnection.ColumnEncryptionTrustedMasterKeyPaths 屬性 可讓您為資料庫伺服器設定受信任的金鑰路徑清單。 如果驅動程式在處理應用程式查詢時,接收到不在清單上的金鑰路徑,則查詢會失敗。 此屬性會提供額外保護,防範安全性攻擊入侵 SQL Server 並提供假的金鑰路徑,從而導致金鑰存放區認證外洩。
SqlConnection.RegisterColumnEncryptionKeyStoreProviders 方法 可讓您註冊自訂金鑰存放區提供者。 它是一個字典,用來將金鑰存放區提供者名稱對應到金鑰存放區提供者實作。
SqlCommand 建構函式 (String、SqlConnection、SqlTransaction、SqlCommandColumnEncryptionSetting) 可讓您控制個別查詢的 Always Encrypted 行為。
SqlParameter.ForceColumnEncryption 屬性 強制將參數加密。 如果 SQL Server 向驅動程式告知參數不需要加密,使用該參數的查詢就會失敗。 此屬性會提供額外保護,防範安全性攻擊入侵 SQL Server 並向用戶端提供不正確的加密中繼資料,從而導致資料暴露。
連接字串 關鍵字:Column Encryption Setting=enabled 啟用或停用連線的永遠加密功能。

另請參閱