使用 Always Encrypted 搭配適用於 SQL Server 的 ODBC 驅動程式

下載 ODBC 驅動程式

適用於

  • SQL Server 的 ODBC 驅動程式 13.1+

簡介

此文章提供有關如何使用 Always Encrypted (資料庫引擎) 或具有安全記憶體保護區的 Always Encrypted 與 ODBC Driver for SQL Server 來開發 ODBC 應用程式的資訊。

[永遠加密] 可讓用戶端應用程式加密敏感性資料,且永遠不會顯示資料或 SQL Server 或 Azure SQL Database 的加密金鑰。 ODBC Driver for SQL Server 等啟用了 Always Encrypted 的驅動程式,以清晰簡明的方式加密與解密用戶端應用程式中的敏感性資料來達成此安全性。 驅動程式會自動判斷哪一個查詢參數對應至敏感性資料庫資料行 (使用 [永遠加密] 保護),然後加密這些參數值後再將資料傳遞至 SQL Server 或 Azure SQL Database。 同樣地,驅動程式會以清晰簡明的方式,將擷取自查詢結果的加密資料庫資料行資料進行解密。 Always Encrypted with secure enclaves 會擴充此功能,讓您能對敏感性資料使用更豐富的功能,同時維持資料的機密性。

如需詳細資訊,請參閱 Always Encrypted (資料庫引擎) 與具有安全記憶體保護區的 Always Encrypted。

Prerequisites

在資料庫中設定永遠加密。 此流程牽涉到佈建 Always Encrypted 金鑰,以及設定所選資料庫資料行的加密。 如果您沒有已設定 Always Encrypted 的資料庫,請遵循教學課程:開始使用 Always Encrypted 中的指示。 特別是,您的資料庫應該包含下列項目的中繼資料定義:「資料行主要金鑰」(CMK)、「資料行加密金鑰」(CEK),以及包含一或多個使用該 CEK 來加密之資料行的資料表。

如果您使用搭配安全記憶體保護區的 Always Encrypted,請參閱使用搭配安全記憶體保護區的 Always Encrypted 開發應用程式,以了解更多先決條件。

在 ODBC 應用程式中啟用 Always Encrypted

若要同時啟用參數加密和結果集加密資料行解密,最簡單的方式是將 ColumnEncryption 連接字串關鍵字的值設定為 Enabled。 下列程式碼是可啟用 Always Encrypted 連接字串的範例:

SQLWCHAR *connString = L"Driver={ODBC Driver 18 for SQL Server};Server={myServer};Encrypt=yes;Trusted_Connection=yes;ColumnEncryption=Enabled;";

也可以在 DSN 設定中啟用 Always Encrypted,使用相同的索引鍵和值(如果有連線字串設定,則會由連線字串設定覆寫),或以程式設計方式搭配 SQL_COPT_SS_COLUMN_ENCRYPTION 連線前屬性啟用。 如此設定會覆寫連線字串或 DSN 中設定的值:

 SQLSetConnectAttr(hdbc, SQL_COPT_SS_COLUMN_ENCRYPTION, (SQLPOINTER)SQL_COLUMN_ENCRYPTION_ENABLE, 0);

針對連線啟用 Always Encrypted 之後,便可以針對個別查詢調整其行為。 如需詳細資訊,請參閱下方的控制 Always Encrypted 的效能影響。

啟用 Always Encrypted 並不足以確保加密或解密成功;您還必須確定:

  • 應用程式擁有 VIEW ANY COLUMN MASTER KEY DEFINITION 和 VIEW ANY COLUMN ENCRYPTION KEY DEFINITION 資料庫權限,這些權限是存取資料庫中關於 Always Encrypted 金鑰的元資料所必需的。 如需詳細資料,請參閱資料庫權限。

  • 應用程式可以存取用來保護所查詢加密資料行之 CEK 的 CMK。 此行為取決於儲存 CMK 的金鑰儲存區提供者。 如需詳細資訊,請參閱使用資料行主要金鑰存放區。

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

注意

在 Linux 和 macOS 上,若要使用搭配安全記憶體保護區的 Always Encrypted,必須使用 OpenSSL 1.0.1 或更新版本。

Important

Always Encrypted with Intel Software Guard Extensions(Intel SGX)安全區域將於 2027 年 10 月 31 日結束支援。 在此日期前遷移受影響的資料庫。 2027 年 10 月 31 日後,Azure 會自動將仍屬於 DC 系列運算層的任何資料庫移至支援的標準系列(非 DC)運算層,並啟用虛擬化式安全性(VBS)飛地。 關於遷移選項,請參閱 《Always Encrypted with Intel SGX enclaves》遷移指南。

自 17.4 版起,驅動程式支援具有安全區域的 Always Encrypted。 若要在連線到資料庫時啟用 enclave 的使用,請將 ColumnEncryption DSN 索引鍵、連線字串關鍵字或連線屬性設為下列值:<attestation protocol>,<attestation URL>,其中:

  • <attestation protocol> - 指定用於記憶體保護區證明的通訊協定。

    • 如果您使用的是 SQL Server 和主機守護者服務 (HGS),<attestation protocol> 應為 VBS-HGS。
    • 如果您使用的是 Azure SQL Database 和 Microsoft Azure 證明,則 <attestation protocol> 應為 SGX-AAS。
    • 如果您不需要證明,則 <attestation-protocol> 應為 VBS-NONE。 (18.1+ 版)
  • <attestation URL> - 指定證明 URL (證明服務端點)。 您必須向您的證明服務管理員取得您環境的證明資料 URL。

啟用記憶體保護區計算以進行資料庫連線的連接字串範例:

  • SQL Server:

    "Driver=ODBC Driver 18 for SQL Server;Server=myServer.myDomain;Encrypt=yes;Database=myDataBase;Trusted_Connection=Yes;ColumnEncryption=VBS-HGS,http://myHGSServer.myDomain/Attestation"
    
  • Azure SQL Database:

    "Driver=ODBC Driver 18 for SQL Server;Server=myServer.database.windows.net;Database=myDataBase;Uid=myUsername;Pwd=<password>;Encrypt=yes;ColumnEncryption=SGX-AAS,https://myAttestationProvider.uks.attest.azure.net/"
    
  • 無驗證證明 (v18.1+):

    "Driver=ODBC Driver 18 for SQL Server;Server=myServer.database.windows.net;Database=myDataBase;Uid=myUsername;Pwd=<password>;Encrypt=yes;ColumnEncryption=VBS-NONE"
    

如果伺服器和證明服務,以及用於加密資料行的已啟用記憶體保護區 CMK 和 CEK,都已正確設定,則除了 Always Encrypted 提供的現有功能之外,您還可以執行會使用記憶體保護區的查詢,例如就地加密和進階計算。 如需詳細資訊,請參閱設定使用安全記憶體保護區域的 Always Encrypted。

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

當您在連線上啟用 Always Encrypted 之後,就可以使用標準 ODBC API。 ODBC API 可以擷取或修改加密資料庫資料行中的資料。 下列文件項目可能會有幫助:

您的應用程式必須具有必要的資料庫權限,而且必須能夠存取資料行主要金鑰。 然後,驅動程式會將任何針對加密資料行的查詢參數加密。 驅動程式也會將從加密資料行擷取的資料解密。 驅動程式會執行所有此類加密及解密作業,不需要原始程式碼的協助。 對您的程式來說,就如同資料行未經加密。

若未啟用 Always Encrypted,使用目標加密資料行參數的查詢就會失敗。 只要查詢中沒有針對加密資料行的參數,仍然可以從加密資料行擷取資料。 不過,驅動程式不會嘗試進行任何解密,而應用程式則會收到二進位加密資料 (以位元組陣列的形式)。

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

查詢特性 [永遠加密] 已啟用,且應用程式可以存取金鑰和金鑰中繼資料 Always Encrypted 已啟用,但應用程式不能存取金鑰或金鑰中繼資料 [永遠加密] 已停用
針對加密資料行的參數。 以清晰簡明的方式加密參數值。 錯誤 錯誤
在不使用針對加密資料行之參數的情況下,從加密資料行擷取資料。 來自加密資料行的結果會以透明方式解密。 應用程式會收到純文字資料行值。 錯誤 來自加密資料行的結果不會被解密。 應用程式收到位元組陣列形態的加密值。

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

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 資料表中。 請注意下列詳細資料:

  • 範例程式碼中沒有任何需要加密的特定項目。 驅動程式會自動偵測並加密目標為加密資料行的 SSN 和日期參數值。 此行為可讓加密對應用程式變得透明化。

  • 插入至資料庫資料行的值,包括加密的資料行,會傳遞為繫結參數 (請參閱 SQLBindParameter 函式)。 雖然在將值傳送至未加密的資料行時,使用參數是可選的(但仍強烈建議這麼做,因為這有助於防止 SQL 插入攻擊),但對於要寫入加密資料行的值,則必須使用參數。 如果將插入 SSN 或 BirthDate 資料行中的值當作內嵌在查詢陳述式中的常值傳遞,則查詢會失敗,因為驅動程式不會嘗試加密或處理查詢中的常數。 因此,伺服器會認定它們與加密資料行不相容而拒絕它們。

  • 插入 SSN 資料行中之參數的 SQL 類型會設定為 SQL_CHAR,這會對應至 char SQL Server 資料類型 (rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 11, 0, (SQLPOINTER)SSN, 0, &cbSSN);)。 如果參數類型設定為 SQL_WCHAR (對應至 nchar),則查詢會失敗,因為 Always Encrypted 不支援在伺服器端進行從已加密 nchar 值到已加密 char 值的轉換。 如需資料類型對應的相關資訊,請參閱 ODBC 程式設計師參考 -- 附錄 D:資料類型。

    SQL_DATE_STRUCT date;
    SQLLEN cbdate;   // size of date structure  

    SQLCHAR SSN[12];
    strcpy_s((char*)SSN, _countof(SSN), "987-65-4320");

    SQLWCHAR* firstName = L"Catherine";
    SQLWCHAR* lastName = L"Abel";
    SQLINTEGER cbSSN = SQL_NTS, cbFirstName = SQL_NTS, cbLastName = SQL_NTS;

    // Initialize the date structure  
    date.day = 10;
    date.month = 9;
    date.year = 1996;

    // Size of structures   
    cbdate = sizeof(SQL_DATE_STRUCT);

    SQLRETURN rc = 0;

    string queryText = "INSERT INTO [dbo].[Patients] ([SSN], [FirstName], [LastName], [BirthDate]) VALUES (?, ?, ?, ?) ";

    rc = SQLPrepare(hstmt, (SQLCHAR *)queryText.c_str(), SQL_NTS);

    //SSN
    rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 11, 0, (SQLPOINTER)SSN, 0, &cbSSN);
    //FirstName
    rc = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WCHAR, 50, 0, (SQLPOINTER)firstName, 0, &cbFirstName);
    //LastName
    rc = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WCHAR, 50, 0, (SQLPOINTER)lastName, 0, &cbLastName);
    //BirthDate
    rc = SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT, SQL_C_TYPE_DATE, SQL_TYPE_DATE, 10, 0, (SQLPOINTER)&date, 0, &cbdate);

    rc = SQLExecute(hstmt);

純文字資料擷取範例

下例示範根據加密值篩選資料,以及從加密資料行擷取純文字資料。 請注意下列詳細資料:

  • 在 WHERE 子句中用來篩選 SSN 資料行的值,必須使用 SQLBindParameter 傳遞,讓驅動程式先以透明方式將其加密,再傳送到伺服器。

  • 程式列印的所有值都是純文字格式,因為驅動程式會以清晰簡明方式來解密從 SSN 和 BirthDate 資料行擷取的資料。

注意

只有當加密具確定性,或已啟用安全記憶體保護區時,查詢才能在加密資料行上執行相等比較。

SQLCHAR SSN[12];
strcpy_s((char*)SSN, _countof(SSN), "987-65-4320");

SQLWCHAR* firstName = L"Catherine";
SQLWCHAR* lastName = L"Abel";
SQLINTEGER cbSSN = SQL_NTS, cbFirstName = SQL_NTS, cbLastName = SQL_NTS;

SQLRETURN rc = 0;
string empty = "";
string queryText = "SELECT [SSN], [FirstName], [LastName], [BirthDate] " + empty +
    "FROM  [dbo].[Patients]" +
    "WHERE " +
    "[SSN] = ? ";

rc = SQLPrepare(hstmt, (SQLCHAR *)queryText.c_str(), SQL_NTS);

//SSN
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 11, 0, (SQLPOINTER)SSN, 0, &cbSSN);

rc = SQLExecute(hstmt);
HandleDiagnosticRecord(hstmt, SQL_HANDLE_STMT, rc);

SQL_DATE_STRUCT dateVal;
SQLWCHAR firstNameVal[50];
SQLWCHAR lastNameVal[50];
SQLCHAR SSNVal[12];
SQLLEN cbdate;   // size of date structure  

int rowcount = 0;
while (SQL_SUCCEEDED(SQLFetch(hstmt)))
{
    rowcount++;
    SQLGetData(hstmt, 1, SQL_C_CHAR, &SSNVal, 11, &cbSSN);
    SQLGetData(hstmt, 2, SQL_C_WCHAR, &firstNameVal, 50, &cbFirstName);
    SQLGetData(hstmt, 3, SQL_C_WCHAR, &lastNameVal, 50, &cbLastName);
    SQLGetData(hstmt, 4, SQL_C_TYPE_DATE, &dateVal, 10, &cbdate);        
}

加密文字資料擷取範例

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

下列範例會示範從加密資料行擷取二進位的加密資料。 請注意下列詳細資料:

  • 因為連接字串未啟用 Always Encrypted,所以查詢會以位元組陣列 (程式會將值轉換為字串) 傳回加密的 SSN 和 BirthDate 值。
  • 在已停用 [一律加密] 的情況下,從加密資料行擷取資料的查詢可以包含參數,只要這些參數都不是針對加密資料行。 上述依 LastName 篩選的查詢,在資料庫中未加密。 如果依 SSN 或 BirthDate 篩選查詢,查詢會失敗。
SQLCHAR SSN[12];
strcpy_s((char*)SSN, _countof(SSN), "987-65-4320");

SQLWCHAR* firstName = L"Catherine";
SQLWCHAR* lastName = L"Abel";
SQLINTEGER cbSSN = SQL_NTS, cbFirstName = SQL_NTS, cbLastName = SQL_NTS;

SQLRETURN rc = 0;
string empty = "";
string queryText = "SELECT [SSN], [FirstName], [LastName], [BirthDate] " + empty +
    "FROM  [dbo].[Patients]" +
    "WHERE " +
    "[LastName] = ?";

rc = SQLPrepare(hstmt, (SQLCHAR *)queryText.c_str(), SQL_NTS);

//LastName
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WCHAR, 50, 0, (SQLPOINTER)lastName, 0, &cbLastName);

rc = SQLExecute(hstmt);
HandleDiagnosticRecord(hstmt, SQL_HANDLE_STMT, rc);

SQL_DATE_STRUCT dateVal;
SQLWCHAR firstNameVal[50];
SQLWCHAR lastNameVal[50];
SQLCHAR SSNVal[12];
SQLLEN cbdate;   // size of date structure  

int rowcount = 0;
while (SQL_SUCCEEDED(SQLFetch(hstmt)))
{
    rowcount++;
    SQLGetData(hstmt, 1, SQL_C_CHAR, &SSNVal, 11, &cbSSN);
    SQLGetData(hstmt, 2, SQL_C_WCHAR, &firstNameVal, 50, &cbFirstName);
    SQLGetData(hstmt, 3, SQL_C_WCHAR, &lastNameVal, 50, &cbLastName);
    SQLGetData(hstmt, 4, SQL_C_TYPE_DATE, &dateVal, 10, &cbdate);        
}

Money/SmallMoney 加密

從驅動程式 17.7 版開始,可以搭配 MONEY 和 SMALLMONEY 來使用 Always Encrypted。 不過,還需要採取一些額外的步驟。 插入至已加密的 MONEY 或 SMALLMONEY 資料行時,請使用下列其中一種 C 類型:

SQL_C_CHAR
SQL_C_WCHAR
SQL_C_SHORT
SQL_C_LONG
SQL_C_FLOAT
SQL_C_DOUBLE
SQL_C_BIT
SQL_C_TINYINT
SQL_C_SBIGINT
SQL_C_NUMERIC

以及使用 SQL_NUMERIC 或 SQL_DOUBLE 的 SQL 類型 (使用這個類型時,可能會遺失精確度)。

繫結變數

每當在加密資料行中繫結 MONEY/SMALLMONEY 變數時,都必須設定下列描述項欄位:

// n is the descriptor record of the MONEY/SMALLMONEY parameter
// the type is assumed to be SMALLMONEY if isSmallMoney is true and MONEY otherwise

SQLHANDLE ipd = 0;
SQLGetStmtAttr(hStmt, SQL_ATTR_IMP_PARAM_DESC, (SQLPOINTER)&ipd, SQL_IS_POINTER, NULL);
SQLSetDescField(ipd, n, SQL_CA_SS_SERVER_TYPE, isSmallMoney ? (SQLPOINTER)SQL_SS_TYPE_SMALLMONEY :
                                                              (SQLPOINTER)SQL_SS_TYPE_MONEY, SQL_IS_INTEGER);

// If the variable is bound as SQL_NUMERIC, additional descriptor fields have to be set
// var is SQL_NUMERIC_STRUCT containing the value to be inserted

SQLHDESC   hdesc = NULL;
SQLGetStmtAttr(hStmt, SQL_ATTR_APP_PARAM_DESC, &hdesc, 0, NULL);
SQLSetDescField(hdesc, n, SQL_DESC_PRECISION, (SQLPOINTER)(var.precision), 0);
SQLSetDescField(hdesc, n, SQL_DESC_SCALE, (SQLPOINTER)(var.scale), 0);
SQLSetDescField(hdesc, n, SQL_DESC_DATA_PTR, &var, 0);

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

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

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

Always Encrypted 僅支援少數加密資料類型轉換。 如需支援的類型轉換詳細清單,請參閱 Always Encrypted (資料庫引擎)。 為了避免資料類型轉換錯誤,請確定您在使用 SQLBindParameter 搭配以加密資料行為目標的參數時,注意到下列幾點:

  • 參數的 SQL 類型與目標資料行的類型完全相同,或是支援從 SQL 類型轉換成資料行的類型。

  • 以 decimal 和 numeric SQL Server 資料類型資料行為目標之參數的有效位數和小數位數,和為目標資料行設定的有效位數和小數位數相同。

  • 在修改目標資料行的查詢中,針對屬於 datetime2、datetimeoffset 或 time SQL Server 資料類型之資料行的參數,其精確度不會大於目標資料行的精確度。

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

任何要寫入加密資料行的值,都必須先加密,才能傳送到伺服器。 嘗試對加密資料行插入、修改或以純文字值進行篩選時,會導致發生錯誤。 若要避免這類錯誤,請確定:

  • Always Encrypted 已啟用(可在 DSN 或連線字串中啟用,或在連線前為特定連線設定 SQL_COPT_SS_COLUMN_ENCRYPTION 連線屬性,或為特定陳述式設定 SQL_SOPT_SS_COLUMN_ENCRYPTION 陳述式屬性)。

  • 您可使用 SQLBindParameter 將資料傳送至加密資料行。 下列範例顯示一個查詢:它錯誤地以常值/常數篩選加密資料行 (SSN),而不是將該常值作為引數傳遞給 SQLBindParameter。

string queryText = "SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE SSN='987-65-4320'";

使用 SQLSetPos 和 SQLMoreResults 時的預防措施

SQLSetPos

SQLSetPos API 可讓應用程式使用已與 SQLBindCol 繫結,且先前已擷取資料列資料至其中的緩衝區,來更新結果集中的資料列。 由於加密固定長度類型具有非對稱的填充行為,因此在更新資料列中的其他資料行時,可能會意外變更這些資料行中的資料。 使用 AE 時,如果該值小於緩衝區大小,固定長度的字元值將會被補齊。

若要減少這種行為的影響,請使用 SQL_COLUMN_IGNORE 旗標來忽略不會在 SQLBulkOperations 中更新的資料行,以及在使用 SQLSetPos 進行以游標為基礎的更新時不會更新的資料行。 基於效能考量,並為避免截斷那些繫結至小於其實際 (DB) 大小之緩衝區的資料行,所有未由應用程式直接修改的資料行都應予以忽略。 如需詳細資訊,請參閱 SQLSetPos 函式參考資料。

SQLMoreResults 與 SQLDescribeCol

應用程式可以呼叫 SQLDescribeCol 來傳回準備陳述式中資料行的相關中繼資料。 已啟用 Always Encrypted 時,在呼叫 SQLMoreResults之前先呼叫 SQLDescribeCol 會導致呼叫 sp_describe_first_result_set,這不會正確地傳回加密資料行的純文字中繼資料。 若要避免此問題,請在對已準備的陳述式呼叫 SQLDescribeCol之前 呼叫 SQLMoreResults。

控制 Always Encrypted 的效能影響

因為 Always Encrypted 是用戶端加密技術,大部分的效能額外負荷是在用戶端觀察到,不是在資料庫觀察到。 除了加密和解密作業成本之外,用戶端其他的效能額外負荷來源如下:

  • 額外反覆存取資料庫,以擷取查詢參數的中繼資料。

  • 呼叫資料行主要金鑰存放區以存取資料行主要金鑰。

本節描述 JDBC Driver for SQL Server 的內建效能最佳化,以及如何控制上述兩個因素對效能的影響。

控制反覆存取以擷取查詢參數的中繼資料

如果連線已啟用 Always Encrypted,此驅動程式預設會針對每個參數化查詢呼叫 sys.sp_describe_parameter_encryption,將查詢陳述式 (不含任何參數值) 傳遞至 SQL Server。 這個預存程序會分析查詢陳述式,以查明是否有任何參數需要加密,如果有,便傳回每個參數的加密相關資訊,以便讓驅動程式加密參數。 上述行為可為用戶端應用程式提供高度透明性:只要要寫入加密資料行的值是透過參數傳遞給驅動程式,應用程式 (以及應用程式開發人員) 就不需要知道哪些查詢會存取加密資料行。

從 17.6 版開始,該驅動程式也會快取預備陳述式的加密中繼資料,讓未來對 SQLExecute 的呼叫無須額外往返即可擷取加密中繼資料,藉此提升效能。

個別陳述式的 Always Encrypted 行為

若要控制擷取參數化查詢之加密中繼資料的效能影響,您可以改變個別查詢的 Always Encrypted 行為 (如果已在連線上啟用此行為)。 如此一來,您便可以確保只有在查詢含有您已知會針對加密資料行的參數時,才會呼叫 sys.sp_describe_parameter_encryption。 不過請注意,如此一來會降低加密的透明度︰如果您將資料庫中的其他資料行加密,可能需要變更應用程式的程式碼,以配合結構描述的變更。

若要控制陳述式的 Always Encrypted 行為,請呼叫 SQLSetStmtAttr 以將 SQL_SOPT_SS_COLUMN_ENCRYPTION 陳述式屬性設定為下列其中一個值:

值 描述
SQL_CE_DISABLED (0) 針對陳述式停用 Always Encrypted
SQL_CE_RESULTSETONLY (1) 僅解密。 將結果集和傳回值解密,但不將參數解密
SQL_CE_ENABLED (3) Always Encrypted 已啟用,並用於參數和結果

新陳述式控制代碼若是從已啟用 Always Encrypted 的連線所建立,就會預設為 SQL_CE_ENABLED。 若控制碼是從已停用 Always Encrypted 的連線所建立,則預設為 SQL_CE_DISABLED,且無法對這些控制碼啟用 Always Encrypted。

如果用戶端應用程式的大多數查詢都會存取加密資料行,則以下幾點是建議事項:

  • 將 ColumnEncryption 連接字串關鍵字設定為 Enabled。

  • 針對不會存取任何加密資料行的陳述式,將 SQL_SOPT_SS_COLUMN_ENCRYPTION 屬性設定為 SQL_CE_DISABLED。 此設定會導致無法呼叫 sys.sp_describe_parameter_encryption,並且無法嘗試解密結果集內的任何值。

  • 針對沒有任何需要加密的參數、但會從加密資料行擷取資料的陳述式,將 SQL_SOPT_SS_COLUMN_ENCRYPTION 屬性設定為 SQL_CE_RESULTSETONLY。 此設定會導致無法呼叫 sys.sp_describe_parameter_encryption 和進行參數加密。 包含加密資料行的結果仍會被解密。

  • 請針對會多次執行的查詢使用已備妥的陳述式;使用 SQLPrepare 來準備該查詢並儲存陳述式控制代碼,並在每次執行時,搭配 SQLExecute 重複加以使用。 此為針對效能的建議方法 (即便沒有任何已加密的資料行也一樣),且能允許驅動程式利用快取的中繼資料。

Always Encrypted 安全性設定

強制資料行加密

若要強制將參數加密,請透過呼叫 SQLSetDescField 函式來設定 SQL_CA_SS_FORCE_ENCRYPT 實作參數描述項 (IPD) 欄位。 值若不是零,會導致驅動程式在系統未針對相關聯參數傳回任何加密中繼資料時傳回錯誤。

SQLHDESC ipd;
SQLGetStmtAttr(hStmt, SQL_ATTR_IMP_PARAM_DESC, &ipd, 0, 0);
SQLSetDescField(ipd, paramNum, SQL_CA_SS_FORCE_ENCRYPT, (SQLPOINTER)TRUE, SQL_IS_SMALLINT);   

如果 SQL Server 向驅動程式告知參數不需要加密,使用該參數的查詢就會失敗。 此行為可針對涉及已遭入侵的 SQL Server 向用戶端提供不正確加密中繼資料的安全性攻擊,提供額外保護,進而避免可能的資料外洩。

資料行加密金鑰快取

為了將呼叫資料行主要金鑰存放區來解密資料行加密金鑰的次數減少,驅動程式會將純文字 CEK 快取至記憶體中。 CEK 快取對驅動程式而言是全域的,未與任何一個連線相關聯。 從資料庫中繼資料收到 ECEK 之後,驅動程式會先嘗試尋找與快取中加密金鑰值對應的純文字 CEK。 只有在快取中找不到對應的純文字 CEK 時,驅動程式才會呼叫包含 CMK 的金鑰存放區。

注意

在 ODBC Driver for SQL Server 中,快取中的項目會在兩小時逾時之後被收回。 此行為表示,對於特定的 ECEK,驅動程式在應用程式的生命週期內只會與金鑰存放區聯絡一次,或每兩小時聯絡一次,兩者取較短者。

從 ODBC Driver 17.1 for SQL Server 開始,即可使用 SQL_COPT_SS_CEKCACHETTL 連線屬性來調整 CEK 快取逾時,此屬性會指定 CEK 將在快取中存留的時間 (秒)。 由於快取的全域本質,因此從對驅動程式有效的任何連線控制代碼都可以調整此屬性。 當快取 TTL 降低時,其有效期會超過新 TTL 的現有 CEK 也會一併遭到淘汰。 如果是 0,則不會快取任何 CEK。

受信任的金鑰路徑

從 ODBC Driver 17.1 for SQL Server 開始,SQL_COPT_SS_TRUSTEDCMKPATHS 連線屬性可讓應用程式要求 Always Encrypted 作業只使用指定的 CMK (以其金鑰路徑識別) 清單。 此屬性預設為 NULL,表示驅動程式會接受任何金鑰路徑。 若要使用此功能,請將 SQL_COPT_SS_TRUSTEDCMKPATHS 設定成指向以 Null 分隔、以 Null 結尾並列出所允許金鑰路徑的寬字元字串。 此屬性所指向的記憶體在使用已設定此屬性的連線控制代碼執行加密或解密作業期間,必須保持有效 --- 驅動程式會在該控制代碼上檢查伺服器中繼資料指定的 CMK 路徑是否以不區分大小寫的方式存在於此清單中。 如果 CMK 路徑不在清單中,作業就會失敗。 應用程式可以變更此屬性所指向的記憶體內容以變更其受信任的 CMK 清單,而無須重新設定屬性。

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

若要將資料加密或解密,驅動程式必須取得為目標資料行設定的 CEK。 CEK 會以加密形式 (ECEK) 儲存在資料庫中繼資料中。 每個 CEK 都有一個用來加密它的對應 CMK。 資料庫中繼資料本身並不儲存 CMK;其只包含金鑰存放區的名稱,以及金鑰存放區可用來尋找 CMK 的資訊。

為了取得 ECEK 的純文字值,驅動程式會先取得 CEK 及其相對應 CMK 的相關中繼資料,然後使用此資訊來聯絡包含 CMK 的金鑰存放區,並要求它將 ECEK 解密。 驅動程式會使用金鑰存放區提供者來與金鑰存放區進行通訊。

內建金鑰儲存庫提供者

ODBC Driver for SQL Server 隨附以下內建金鑰存放區提供者:

名稱 描述 提供者 (中繼資料) 名稱 可用性
Azure Key Vault 將 CMK 儲存在 Azure Key Vault 中 AZURE_KEY_VAULT Windows、macOS、Linux
Windows 憑證存放區 將 CMK 儲存在本機 Windows 金鑰存放區中 MSSQL_CERTIFICATE_STORE Windows
  • 您 (或您的 DBA) 需要確認設定在資料行主要金鑰中繼資料的提供者名稱是否正確,且資料行主要金鑰路徑符合指定提供者的金鑰路徑格式。 建議你使用像 SQL Server Management Studio 這類工具來設定金鑰,當發出 CREATE COLUMN MASTER KEY (Transact-SQL) 語句時,這些工具會自動產生有效的提供者名稱和金鑰路徑。

  • 確定您的應用程式可以存取金鑰儲存區中的金鑰。 此程序可能牽涉到授與應用程式金鑰及/或金鑰儲存區的存取權 (視金鑰儲存區而定),或執行其他的金鑰儲存區特定設定步驟。 例如,若要存取 Azure Key Vault,您必須將正確的認證提供給金鑰存放區。

使用 Azure Key Vault 提供者

Azure Key Vault (AKV) 是存放和管理 Always Encrypted 資料行主要金鑰的方便選項 (尤其是當應用程式裝載在 Azure 中時)。 適用於 Linux、macOS 和 Windows 的 SQL Server ODBC 驅動程式,包含適用於 Azure Key Vault 的內建資料行主金鑰存放區提供者。 如需更多關於設定用於 Always Encrypted 的 Azure Key Vault 之資訊,請參閱 Azure Key Vault - 逐步解說、金鑰保存庫 使用者入門及 在 Azure Key Vault 中建立資料行主要金鑰。

注意

ODBC 驅動程式只支援直接針對 Microsoft Entra ID (前身為 Azure Active Directory)的 AKV 驗證。 如果您使用 Microsoft Entra 向 AKV 進行驗證,而您的組態需要向 Active Directory 同盟服務 端點進行驗證,則驗證可能會失敗。 在 Linux 和 macOS 上,針對驅動程式 17.2 版和更新版本,必須要有 libcurl,才能使用此提供者,但這不是明確相依性,因為驅動程式的其他作業並不需要它。 如果您遇到有關 libcurl 的錯誤,請確定它已安裝。

驅動程式支援使用下列認證類型向 Azure Key Vault 進行驗證:

  • 使用者名稱/密碼 - 使用此方法時,認證係指 Microsoft Entra 使用者名稱及其密碼。

  • 用戶端識別碼/祕密 - 使用此方法時,認證係指應用程式用戶端識別碼和應用程式祕密。

  • 受控識別 (17.5.2+) - 系統或使用者指派;如需詳細資訊,請參閱 Azure 資源受控識別。

  • Azure 金鑰保存庫互動式(17.7+ Windows 驅動程式)- 使用此方法時,認證資訊會透過 Microsoft Entra ID 並使用登入 ID 進行驗證。

為了允許驅動程式使用儲存在 AKV 中的 CMK 來進行資料行加密,請使用下列僅限連接字串的關鍵字:

認證類型 KeyStoreAuthentication KeyStorePrincipalId KeyStoreSecret
使用者名稱/密碼 KeyVaultPassword 使用者主體名稱 密碼
用戶端識別碼/祕密 KeyVaultClientSecret 用戶端識別碼 祕密
受控識別 KeyVaultManagedIdentity 物件識別碼 (選擇性,僅適用於使用者指派) (未指定)
AKV 互動 KeyVaultInteractive (未設定) (未設定)

從 v17.8 開始,您可以使用 ODBC 資料來源管理員中的 DSN 設定 UI,來編輯 KeystoreAuthentication 和 KeystorePrincipalId。

範例連接字串

下列連接字串顯示如何使用兩種認證類型向 Azure Key Vault 進行驗證:

用戶端識別碼/祕密
"DRIVER=ODBC Driver 18 for SQL Server;SERVER=myServer;Encrypt=yes;Trusted_Connection=Yes;DATABASE=myDB;ColumnEncryption=Enabled;KeyStoreAuthentication=KeyVaultClientSecret;KeyStorePrincipalId=<clientId>;KeyStoreSecret=<secret>"
使用者名稱/密碼
"DRIVER=ODBC Driver 18 for SQL Server;SERVER=myServer;Encrypt=yes;Trusted_Connection=Yes;DATABASE=myDB;ColumnEncryption=Enabled;KeyStoreAuthentication=KeyVaultPassword;KeyStorePrincipalId=<username>;KeyStoreSecret=<password>"
受控識別 (系統指派)
"DRIVER=ODBC Driver 18 for SQL Server;SERVER=myServer;Encrypt=yes;Trusted_Connection=Yes;DATABASE=myDB;ColumnEncryption=Enabled;KeyStoreAuthentication=KeyVaultManagedIdentity"
受控識別 (使用者指派)
"DRIVER=ODBC Driver 18 for SQL Server;SERVER=myServer;Encrypt=yes;Trusted_Connection=Yes;DATABASE=myDB;ColumnEncryption=Enabled;KeyStoreAuthentication=KeyVaultManagedIdentity;KeyStorePrincipalId=<objectID>"
AKV 互動
"DRIVER=ODBC Driver 18 for SQL Server;SERVER=myServer;Encrypt=yes;Trusted_Connection=Yes;DATABASE=myDB;ColumnEncryption=Enabled;KeyStoreAuthentication=KeyVaultInteractive;UID=<userID>;PWD=<password>"

無須進行其他 ODBC 應用程式變更,即可使用 AKV 來儲存 CMK。

注意

驅動程式包含其信任的 AKV 端點清單。 從驅動程式 17.5.2 版開始,您可設定這份清單:在驅動程式或 DSN 的 ODBCINST.INI 或 ODBC.INI 登錄機碼 (Windows),或在 AKVTrustedEndpoints、odbcinst.ini 檔案區段 (Linux/macOS) 中,將 odbc.ini 屬性設定為以分號分隔的清單。 在 DSN 中設定該屬性會優先於驅動程式中的設定。 如果值以分號開頭,則會延伸預設清單;否則會取代預設清單。 預設清單 (從 17.5 版開始) 為 vault.azure.net;vault.azure.cn;vault.usgovcloudapi.net;vault.microsoftazure.de。 從 17.7 開始,清單也包含 managedhsm.azure.net;managedhsm.azure.cn;managedhsm.usgovcloudapi.net;managedhsm.microsoftazure.de。

注意

內建於 ODBC 驅動程式中的 Azure Key Vault 提供者支援 Azure Key Vault 中的保存庫和受控 HSM。

使用 Windows 憑證存放區提供者

Windows 上的 SQL Server 的 ODBC 驅動程式包含適用於 Windows 憑證存放區的內建資料行主索引鍵存放區提供者,名為 MSSQL_CERTIFICATE_STORE。 (macOS 或 Linux 上無法使用此提供者。) 使用此提供者時,CMK 會儲存在本機用戶端電腦上,應用程式無須進行任何額外設定,即可將其與驅動程式搭配使用。 不過,應用程式必須能夠存取存放區中的憑證及其私密金鑰。 如需詳細資訊,請參閱 建立及儲存資料行主要金鑰 (永遠加密)。

使用自訂金鑰儲存區提供者

ODBC Driver for SQL Server 也支援透過 CEKeystoreProvider 介面使用自訂的第三方金鑰存放區提供者。 此功能可讓應用程式載入、查詢及設定金鑰存放區提供者,以便供驅動程式用來存取加密資料行。 應用程式也可以直接與金鑰存放區提供者進行互動,來加密 CEK 以儲存在 SQL Server 中,以及執行以 ODBC 存取加密資料行以外的工作;如需詳細資訊,請參閱自訂金鑰存放區提供者。

有兩個連線屬性會用來與自訂金鑰存放區提供者進行互動。 其中包括:

  • SQL_COPT_SS_CEKEYSTOREPROVIDER

  • SQL_COPT_SS_CEKEYSTOREDATA

前者可用來載入和列舉已載入的金鑰存放區提供者,後者則可啟用應用程式提供者通訊。 這些連線屬性隨時都可使用,不論是在建立連線之前還是之後,因為應用程式提供者互動並未牽涉到與 SQL Server 進行通訊。 不過,由於尚未載入驅動程式,因此在連線前設定及取得這些屬性,將促使「驅動程式管理員」對屬性進行處理,而可能不會產生預期的結果。

載入金鑰儲存庫提供者

設定 SQL_COPT_SS_CEKEYSTOREPROVIDER 連線屬性可讓用戶端應用程式載入提供者程式庫,讓其中所含的金鑰存放區提供者變成可供使用。

SQLRETURN SQLSetConnectAttr( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);
引數 描述
ConnectionHandle [輸入] 連線控制代碼。 必須是有效的連線控制代碼,但若提供者是透過某個連線控制代碼載入,則可從同一處理序中的任何其他連線控制代碼存取。
Attribute [輸入] 要設定的屬性:SQL_COPT_SS_CEKEYSTOREPROVIDER 常數。
ValuePtr [輸入] 指向以空字元終止之字元字串的指標,用以指定提供者程式庫的檔名。 就 SQLSetConnectAttrA 而言,此值會是 ANSI (多位元組) 字串。 就 SQLSetConnectAttrW 而言,此值會是 Unicode (wchar_t) 字串。
StringLength [輸入] 輸入 ValuePtr 字串或 SQL_NTS 的長度。

驅動程式會使用平台定義的動態程式庫載入機制 (在 Linux 和 macOS 上是 dlopen(),在 Windows 上是 LoadLibrary()) 來嘗試載入 ValuePtr 參數所識別的程式庫,然後將該處定義的任何提供者新增至驅動程式已知的提供者清單。 以下是可能發生的錯誤:

錯誤 描述
CE203 無法載入動態程式庫。
CE203 在程式庫中找不到 "CEKeyStoreProvider" 匯出的符號。
CE203 程式庫中已有一個或多個提供者載入。

SQLSetConnectAttr 會傳回一般錯誤或成功值,且針對任何已發生的錯誤,都會透過標準 ODBC 診斷機制提供額外的資訊。

注意

應用程式程式設計人員必須確保在透過任何連線傳送需要任何自訂提供者的任何查詢之前,先載入這些提供者。 無法執行這項操作時,會導致發生錯誤:

錯誤 描述
CE200 找不到金鑰存放區提供者 %1。 請確定已載入適當的金鑰存放區提供者程式庫。

注意

金鑰存放區提供者實施者應該避免在其自訂提供者的名稱中使用 MSSQL。 此詞彙僅供 Microsoft 使用,且可能與未來的內建提供者發生衝突。 在自訂提供者的名稱中使用這個詞彙可能會造成 ODBC 警告。

取得載入的提供者清單

取得此連線屬性可讓用戶端應用程式判斷目前在驅動程式中載入的金鑰存放區提供者 (包括內建的提供者)。此程序只能在連線後執行。

SQLRETURN SQLGetConnectAttr( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER * StringLengthPtr);
引數 描述
ConnectionHandle [輸入] 連線控制代碼。 必須是有效的連線控制代碼,但若提供者是透過某個連線控制代碼載入,則可從同一處理序中的任何其他連線控制代碼存取。
Attribute [輸入] 要擷取的屬性:SQL_COPT_SS_CEKEYSTOREPROVIDER 常數。
ValuePtr [輸出] 指向記憶體的指標,用來傳回下一個已載入的提供者名稱。
BufferLength [輸入] 緩衝區 ValuePtr 的長度。
StringLengthPtr [輸出] 指向緩衝區的指標,用來傳回可在 *ValuePtr 中傳回的位元組總數(不包括 Null 終止字元)。 如果 ValuePtr 是 Null 指標,則不會傳回任何長度。 如果屬性值是字元字串,且可供傳回的位元組數大於 BufferLength 減去以 Null 結尾的字元長度所得的數目,則 *ValuePtr 中的資料會依據 BufferLength 減去以 Null 結尾的字元長度所得的數目截斷,並由驅動程式以 Null 結尾。

為了能擷取整個清單,每次 Get 作業都會傳回目前提供者的名稱,並將內部計數器遞增至下一個提供者。 當此計數器到達清單結尾時,就會傳回空字串 (""),然後計數器會重設;後續的 Get 作業會再次從清單開頭繼續進行。

與金鑰存放區提供者進行通訊

SQL_COPT_SS_CEKEYSTOREDATA 連線屬性可讓用戶端應用程式與已載入的金鑰存放區提供者進行通訊,以設定其他的參數、金鑰產製原料等。 用戶端應用程式與提供者之間的通訊會根據使用此連線屬性的 Get 和 Set 要求,依循簡單的「要求-回應」通訊協定。 通訊只會由用戶端應用程式起始。

注意

由於 CEKeyStoreProvider 所回應的 ODBC 呼叫性質為 SQLGet/SetConnectAttr,ODBC 介面僅支援在連線內容層級設定資料。

應用程式會透過驅動程式、經由 CEKeystoreData 結構與金鑰存放區提供者進行通訊:

typedef struct CEKeystoreData {
wchar_t *name;
unsigned int dataSize;
char data[];
} CEKEYSTOREDATA;
引數 描述
name [輸入] 設定時,資料傳送目標提供者的名稱。 在 Get 時會被忽略。 以 Null 結尾的寬字元字串。
dataSize [輸入] 結構後方的資料陣列大小。
data [InOut] 在 Set 時,傳送至提供者的資料。 這可以是任意資料;驅動程式不會嘗試解譯資料。 執行 Get 作業時,用來接收從提供者讀取之資料的緩衝區。

將資料寫入至提供者

使用 SQLSetConnectAttr 屬性的 SQL_COPT_SS_CEKEYSTOREDATA 呼叫會將資料「封包」寫入至指定的金鑰存放區提供者。

SQLRETURN SQLSetConnectAttr( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);
引數 描述
ConnectionHandle [輸入] 連線控制代碼。 必須是有效的連線控制代碼,但若提供者是透過某個連線控制代碼載入,則可從同一處理序中的任何其他連線控制代碼存取。
Attribute [輸入] 要設定的屬性:SQL_COPT_SS_CEKEYSTOREDATA 常數。
ValuePtr [輸入] 指向 CEKeystoreData 結構的指標。 結構的名稱欄位會識別要作為資料適用對象的提供者。
StringLength [輸入] SQL_IS_POINTER 常數

其他的詳細錯誤資訊可透過 SQLGetDiacRec 取得。

注意

如果需要,提供者可以使用連線控制代碼將寫入的資料與特定連線建立關聯。 此功能對於實作個別連線設定而言,相當實用。 它也可以忽略連線內容,而不論用來傳送資料的連線為何,都以一致的方式處理資料。 如需詳細資訊,請參閱內容關聯。

從提供者讀取資料

使用 SQLGetConnectAttr 屬性呼叫 SQL_COPT_SS_CEKEYSTOREDATA 時,會從 上一個被寫入的提供者讀取一個「資料封包」。 如果沒有任何項目,就會發生「函式順序錯誤」。 建議金鑰儲存庫提供者的實作者支援對 0 位元組進行「虛擬寫入」,作為在不造成其他副作用的情況下為讀取作業選取提供者的方法,如果這樣做合理。

SQLRETURN SQLGetConnectAttr( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER * StringLengthPtr);
引數 描述
ConnectionHandle [輸入] 連線控制代碼。 必須是有效的連線控制代碼,但若提供者是透過某個連線控制代碼載入,則可從同一處理序中的任何其他連線控制代碼存取。
Attribute [輸入] 要擷取的屬性:SQL_COPT_SS_CEKEYSTOREDATA 常數。
ValuePtr [輸出] 指向 CEKeystoreData 結構的指標,用來放置從提供者讀取的資料。
BufferLength [輸入] SQL_IS_POINTER 常數
StringLengthPtr [輸出] 指向用來傳回 BufferLength 之緩衝區的指標。 如果 *ValuePtr 是 Null 指標,則不會傳回任何長度。

呼叫端必須確保在 CEKEYSTOREDATA 結構之後配置長度足夠的緩衝區以供提供者寫入。 傳回時,其 dataSize 欄位中會填入從提供者讀取之資料的實際長度。 其他的詳細錯誤資訊可透過 SQLGetDiacRec 取得。

此介面對於在應用程式與金鑰存放區提供者之間傳輸的資料格式沒有任何額外的需求。 每個提供者都可以依據其需求,定義自己的通訊協定/資料格式。

如需有關實作您自己金鑰存放區提供者的範例,請參閱自訂金鑰存放區提供者

使用 Always Encrypted 時的 ODBC 驅動程式限制

非同步作業

雖然 ODBC 驅動程式支援 Always Encrypted 的 非同步操作 ,但啟用 Always Encrypted 會影響這些操作的效能。 呼叫 sys.sp_describe_parameter_encryption 以取得該陳述式的加密中繼資料時會發生阻塞。 驅動程式會等待伺服器回傳元資料後才返回 SQL_STILL_EXECUTING。

使用 SQLGetData 來分段擷取資料

在 ODBC Driver 17 for SQL Server 之前,無法使用 SQLGetData 來分段擷取加密字元和二進位資料行。 只能呼叫 SQLGetData 一次,且所使用的緩衝區長度必須足以容納整個資料行的資料。

使用 SQLPutData 來分段傳送資料

在 ODBC Driver 17.3 for SQL Server 之前,無法使用 SQLPutData 來分段傳送用於插入或比較的資料。 只能使用包含完整資料的緩衝區呼叫 SQLPutData 一次。 若要將長資料插入至加密資料行,請使用「大量複製 API」(下一節會提供說明) 搭配輸入資料檔。

加密貨幣和零錢

加密的 money 或 smallmoney 資料行無法做為參數目標,因為沒有與這些類型對應的特定 ODBC 資料類型,所以會導致發生「運算元類型衝突」錯誤。

大量複製已加密的資料行

從 ODBC Driver 17 for SQL Server 開始,即支援在 Always Encrypted 中使用 SQL 大量複製函式和 bcp 公用程式。 不論是純文字 (在插入時加密,然後在擷取時解密) 還是加密文字 (逐字傳輸),您都可以使用「大量複製 (bcp_*) API」和 bcp 公用程式來插入和擷取。

  • 若要擷取 varbinary(max) 形式的加密文字 (例如,用於大量載入至不同的資料庫),請在不使用 ColumnEncryption 選項 (若將其設定為 Disabled) 的情況下連線,然後執行 BCP OUT 作業。

  • 若要插入和擷取純文字,並讓驅動程式視需要在背景中自動執行加密和解密,則將 ColumnEncryption 設定為 Enabled 即已足夠。 除此之外,BCP API 的功能則不變。

  • 若要插入 varbinary(max) 形式的加密文字 (例如,如上述所擷取),請將 BCPMODIFYENCRYPTED 選項設定為 TRUE,然後執行 BCP IN 作業。 為了讓產生的資料可供解密,請確定目的地資料行的 CEK 與原先取得加密文字時的來源 CEK 相同。

使用 bcp 公用程式時:若要控制 ColumnEncryption 設定,請使用 -D 選項並指定包含所需值的 DSN。 若要插入加密文字,請確定已啟用使用者的 ALLOW_ENCRYPTED_VALUE_MODIFICATIONS 設定。

下表提供在加密資料行上操作時的動作摘要:

ColumnEncryption BCP 方向 描述
Disabled OUT (至用戶端) 擷取加密文字。 觀察到的資料型態為 varbinary(max)。
Enabled OUT (至用戶端) 擷取純文字。 驅動程式會將資料行資料解密。
Disabled IN (至伺服器) 插入加密文字。 此設定是用來以隱含方式移動加密資料,而不要求將其解密。 如果未在使用者上設定 ALLOW_ENCRYPTED_VALUE_MODIFICATIONS 選項,或未在連線控制代碼上設定 BCPMODIFYENCRYPTED,此作業就會失敗。 如需詳細資訊,請參閱下列內容。
Enabled IN (至伺服器) 插入純文字。 驅動程式會將資料行資料加密。

BCPMODIFYENCRYPTED 選項

為了防止資料損毀,伺服器通常不允許將加密文字直接插入至加密資料行,因此嘗試這麼做會失敗;不過,針對使用 BCP API 來大量載入加密資料,將 BCPMODIFYENCRYPTEDbcp_control 選項設定為 TRUE 將可允許直接插入加密文字,並降低有關在使用者帳戶上設定 ALLOW_ENCRYPTED_VALUE_MODIFICATIONS 選項所造成的加密資料損毀風險。 不過,金鑰必須與資料相符,而理想的做法是在進行大量插入後及在進一步使用之前,對插入的資料執行一些唯讀檢查。

如需詳細資訊,請參閱移轉透過 Always Encrypted 保護的機密資料。

Always Encrypted API 概觀

連接字串關鍵字

名稱 描述
ColumnEncryption 接受的值為 Enabled/Disabled。
Enabled - 啟用連線的 Always Encrypted 功能。
Disabled - 停用連線的 Always Encrypted 功能。
證明通訊協定、證明 URL - (17.4 版及更新版本) 可使用指定的證明通訊協定和證明 URL 來啟用具有安全記憶體保護區的 Always Encrypted。

預設值為 Disabled。
KeyStoreAuthentication 有效值KeyVaultPassword、KeyVaultClientSecret、KeyVaultInteractive、KeyVaultManagedIdentity
KeyStorePrincipalId 當 KeyStoreAuthentication = KeyVaultPassword 時,將此值設定為有效的 Microsoft Entra 使用者主體名稱。
當 KeyStoreAuthentication = KeyVaultClientSecret 時,將此值設定為有效的 Microsoft Entra 應用程式用戶端識別碼。
當 KeyStoreAuthentication = KeyVaultManagedIdentity 時,請將此值設定為使用者指派受控身份識別的物件識別碼。 如果未提供任何值,則會使用系統指派的受控識別。
KeyStoreSecret 當 KeyStoreAuthentication = KeyVaultPassword 時,請將此值設定為相對應使用者名稱的密碼。
當 KeyStoreAuthentication = KeyVaultClientSecret 時,請將此值設定為與有效「 Microsoft Entra 應用程式用戶端識別碼」相關聯的「應用程式祕密」

連線屬性

名稱 類型 描述
SQL_COPT_SS_COLUMN_ENCRYPTION 預先連線 SQL_COLUMN_ENCRYPTION_DISABLE (0) - 停用 Always Encrypted
SQL_COLUMN_ENCRYPTION_ENABLE (1) - 啟用 Always Encrypted
指向 *attestation protocol*,*attestation URL* 字串的指標 -(17.4 版及以上版本)啟用安全隔離區
SQL_COPT_SS_CEKEYSTOREPROVIDER 連線後 [Set] - 嘗試載入 CEKeystoreProvider
[Get] - 傳回 CEKeystoreProvider 的名稱
SQL_COPT_SS_CEKEYSTOREDATA 連線後 [Set] - 將資料寫入至 CEKeystoreProvider
[Get] - 從 CEKeystoreProvider 讀取資料
SQL_COPT_SS_CEKCACHETTL 連線後 [Set] - 設定 CEK 快取存留時間
[Get] - 取得目前的 CEK 快取 TTL
SQL_COPT_SS_TRUSTEDCMKPATHS 連線後 [Set] - 設定受信任的 CMK 路徑指標
[Get] - 取得目前受信任的 CMK 路徑指標

陳述式屬性

名稱 描述
SQL_SOPT_SS_COLUMN_ENCRYPTION SQL_CE_DISABLED (0) - 此陳述式已停用 Always Encrypted
SQL_CE_RESULTSETONLY (1) -- 僅解密。 將結果集和傳回值解密,但不將參數解密
SQL_CE_ENABLED (3) -- 同時針對參數和結果啟用並使用 Always Encrypted

描述項欄位

IPD 欄位 大小/類型 預設值 描述
SQL_CA_SS_FORCE_ENCRYPT (1236) WORD(2 位元組) 0 若為 0 (預設):加密此參數的決定會取決於加密中繼資料的可用性。

若不為 0:如果有加密中繼資料可供此參數使用,就會加密。 否則,要求會因以下錯誤而失敗:[CE300] [Microsoft][ODBC Driver 17 for SQL Server]已為參數指定了強制加密,但伺服器沒有提供任何加密中繼資料。

bcp_control 選項

選項名稱 預設值 描述
BCPMODIFYENCRYPTED (21) FALSE 若為 TRUE,允許將 varbinary(max) 值插入至加密資料行。 若為 FALSE,除非提供正確的類型和加密中繼資料,否則會防止插入。

疑難排解

如果在使用 Always Encrypted 時遇到困難,請先檢查下列幾點:

  • 會將所需資料行加密的 CEK 已存在,且可以在伺服器上存取。

  • 會將 CEK 加密的 CMK 在伺服器上具有可存取的中繼資料,而且也可從用戶端存取。

  • ColumnEncryption 已在 DSN、連線字串或連線屬性中啟用,且如果使用安全記憶體保護區,其格式也必須正確。

此外,使用安全隔離區時,若發生證明失敗,則可如下表所示識別失敗發生於證明程序的哪個步驟:

步驟 描述
0-99 無效的證明回應或簽章驗證錯誤。
100-199 從證明 URL 擷取憑證時發生錯誤。 請確定 <attestation URL>/v2.0/signingCertificates 有效且可供存取。
200-299 安全區域的身分識別格式非預期或不正確。
300-399 使用記憶體保護區建立安全通道時發生錯誤。