搭配 JDBC 驅動程式使用Always Encrypted

下載 JDBC 驅動程式

此頁面提供如何開發 JAVA 應用程式以搭配 Microsoft JDBC Driver 6.0 (或更新) 版本的SQL Server 使用Always Encrypted的資訊。

Always Encrypted 可讓用戶端加密敏感性資料,而且永遠不會向 SQL Server 或 Azure SQL Database 顯示資料或加密金鑰。 Microsoft JDBC Driver 6.0 (或更高版本) for SQL Server 等啟用了 Always Encrypted 的驅動程式,以清晰簡明的方式加密與解密用戶端應用程式中的敏感性資料,達成此行為。 驅動程式會找出哪些查詢參數對應至Always Encrypted資料庫資料行,並在這些參數傳送至資料庫之前加密這些參數的值。 同樣地,驅動程式會以清晰簡明的方式,將擷取自查詢結果的加密資料庫資料行資料進行解密。 如需詳細資訊,請參閱 Always Encrypted (資料庫引擎)JDBC 驅動程式的 Always Encrypted API 參考

Prerequisites

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

為了針對加密的資料行將資料加密或解密,SQL Server 會維護資料行加密金鑰。 資料行加密金鑰是以加密形式存放在資料庫中繼資料。 每個資料行加密金鑰都有對應的資料行主要金鑰,主要金鑰是用於加密資料行加密金鑰。

資料庫中繼資料不會包含資料行主要金鑰。 只有用戶端才會保存那些金鑰。 不過,資料庫中繼資料會包含資料行主要金鑰針對用戶端之相對儲存位置的相關資訊。 例如,資料庫中繼資料可能會說保存資料行主要金鑰的金鑰存放區是 Windows 憑證存放區,而用來加密和解密的特定憑證位於 Windows 憑證存放區內的特定路徑。

如果用戶端可以存取 Windows 憑證存放區中的憑證,其便能取得該憑證。 憑證接著便可以用來將資料行加密金鑰解密。 然後該加密金鑰便可以用來針對使用該資料行加密金鑰的加密資料行,進行資料的解密或加密。

Microsoft JDBC Driver for SQL Server會與使用資料行主要金鑰存放區提供者的金鑰存放區通訊,這是衍生自 SQLServerColumnEncryptionKeyStoreProvider 的類別實例。

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

Microsoft JDBC Driver for SQL Server 隨附下列內建的資料行主要金鑰存放區提供者。 其中有些提供者會預先向特定提供者名稱註冊, (用來查閱提供者) ,有些則需要額外的認證或明確註冊。

類別 描述 提供者 (查閱) 名稱 是否已預先註冊? 平台
SQLServerColumnEncryptionAzureKeyVaultProvider Azure Key Vault 金鑰儲存區的提供者。 AZURE_KEY_VAULT JDBC 驅動程式 7.4.1 版之前否,但從 JDBC 驅動程式 7.4.1 版開始 Windows、Linux、macOS
SQLServerColumnEncryptionCertificateStoreProvider Windows 憑證存放區的提供者。 MSSQL_CERTIFICATE_STORE Windows
SQLServerColumnEncryptionJavaKeyStoreProvider Java 金鑰儲存區的提供者。 MSSQL_JAVA_KEYSTORE Windows、Linux、macOS

針對預先註冊的金鑰存放區提供者,您不需要變更任何應用程式程式碼,即可使用這些提供者,但請注意下列專案:

  • 您必須確定在資料行主要金鑰中繼資料中設定的提供者名稱正確,且資料行主要金鑰路徑遵循指定提供者有效的金鑰路徑格式。 建議您使用 SQL Server Management Studio 之類的工具來設定金鑰,這會自動產生有效的提供者名稱和金鑰路徑,以發出 CREATE COLUMN MASTER KEY (Transact-SQL) 語句。
  • 確定您的應用程式可以存取金鑰儲存區中的金鑰。 這項工作可能牽涉到將金鑰和/或金鑰存放區的存取權授與您的應用程式。 視金鑰存放區而定,這可能牽涉到其他金鑰存放區特定的組態步驟。 例如,若要使用 SQLServerColumnEncryptionJavaKeyStoreProvider ,您必須在連接屬性中提供金鑰存放區的位置和密碼。

下列各節將會更加詳細地描述這些金鑰儲存區提供者。 您只需要實作單一金鑰儲存區提供者以使用 Always Encrypted。

使用 Azure 金鑰保存庫 提供者

Azure Key Vault 是存放和管理 Always Encrypted 資料行主要金鑰的方便選項 (尤其是當應用程式裝載在 Azure 時)。 Microsoft JDBC Driver for SQL Server包含內建提供者, SQLServerColumnEncryptionAzureKeyVaultProvider 適用于儲存在 Azure 金鑰保存庫中金鑰的應用程式。 此提供者的名稱為 AZURE_KEY_VAULT。

注意

JDBC 驅動程式內建的 Azure 金鑰保存庫 提供者支援Azure 金鑰保存庫 中的保存庫和受控 HSM

若要使用 Azure 金鑰保存庫市集提供者,應用程式開發人員必須在 Azure 金鑰保存庫中建立保存庫和金鑰,並在 Azure Active Directory 中建立應用程式註冊。 必須在針對搭配 Always Encrypted 使用所建立的金鑰保存庫的已定義存取原則中,為註冊的應用程式授與 [取得]、[解密]、[加密]、[將金鑰解除包裝]、[包裝金鑰] 及 [驗證] 權限。 如需如何設定金鑰保存庫及建立資料行主要金鑰的詳細資訊,請參閱Azure 金鑰保存庫— 逐步說明和在 Azure 中建立資料行主要金鑰金鑰保存庫

針對 Azure 金鑰保存庫 提供者,JDBC 驅動程式會根據信任端點清單驗證資料行主要金鑰路徑。 從 8.2.2 版開始,此清單是可設定的:在應用程式的工作目錄中建立 mssql-jdbc.properties 檔案,將 AKVTrustedEndpoints 屬性設定為以分號分隔的清單。 如果值以分號開頭,它會擴充預設清單。 否則,它會取代預設清單。

預設的受信任端點為:

  • *vault.azure.net
  • *vault.azure.cn
  • *vault.usgovcloudapi.net
  • *vault.microsoftazure.de
  • *managedhsm.azure.net (v9.2+)
  • *managedhsm.azure.cn (v9.2+)
  • *managedhsm.usgovcloudapi.net (v9.2+)
  • *managedhsm.microsoftazure.de (v9.2+)

針對此頁面上的範例,如果您已使用 SQL Server Management Studio 建立 Azure 金鑰保存庫型資料行主要金鑰和資料行加密金鑰,則要重新建立的 T-SQL 腳本看起來可能類似于此範例,其專屬KEY_PATHENCRYPTED_VALUE

CREATE COLUMN MASTER KEY [MyCMK]
WITH
(
    KEY_STORE_PROVIDER_NAME = N'AZURE_KEY_VAULT',
    KEY_PATH = N'https://<MyKeyVaultName>.vault.azure.net:443/keys/Always-Encrypted-Auto1/c61f01860f37302457fa512bb7e7f4e8'
);

CREATE COLUMN ENCRYPTION KEY [MyCEK]
WITH VALUES
(
    COLUMN_MASTER_KEY = [MyCMK],
    ALGORITHM = 'RSA_OAEP',
    ENCRYPTED_VALUE = 0x01BA000001680074507400700073003A002F002F006400610076006...
);

使用 JDBC 驅動程式的應用程式可以使用 Azure Key Vault。 使用 Azure 金鑰保存庫的語法或語句已從 JDBC 驅動程式 7.4.1 版變更。

JDBC 驅動程式 7.4.1 或更新版本

此節會涉及 JDBC 驅動程式 7.4.1 版或更新版本。

使用 JDBC 驅動程式的用戶端應用程式,可以透過在 JDBC 連接字串中提及 keyVaultProviderClientId=<ClientId>;keyVaultProviderClientKey=<ClientKey> 來設定以使用 Azure Key Vault。

以下是在 JDBC 連接字串中提供此組態資訊的範例。

String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;user=<user>;password=<password>;columnEncryptionSetting=Enabled;keyVaultProviderClientId=<ClientId>;keyVaultProviderClientKey=<ClientKey>";

當這些認證存在於連接屬性之間時,JDBC 驅動程式會自動具現化 SQLServerColumnEncryptionAzureKeyVaultProvider 物件。

重要

連線屬性 keyVaultProviderClientIdkeyVaultProviderClientKey 自 v8.4.1 起已被取代。 建議使用者改用 keyStoreAuthenticationKeyStorePrincipalIdKeyStoreSecret

7.4.1 之前的 JDBC 驅動程式版本

本節牽涉到 7.4.1 之前的 JDBC 驅動程式版本。

使用 JDBC 驅動程式的用戶端應用程式必須具現化 SQLServerColumnEncryptionAzureKeyVaultProvider 物件,然後向驅動程式註冊物件。

SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(clientID, clientKey);

clientID 是 Azure Active Directory 實例中應用程式註冊的應用程式識別碼。 clientKey是在該應用程式下註冊的金鑰密碼,可提供 Azure 金鑰保存庫的 API 存取權。

應用程式建立 的 SQLServerColumnEncryptionAzureKeyVaultProvider 實例之後,應用程式必須使用 方法向驅動程式 SQLServerConnection.registerColumnEncryptionKeyStoreProviders() 註冊實例。 強烈建議使用預設查閱名稱來註冊 實例,AZURE_KEY_VAULT,此名稱可由 API 取得 SQLServerColumnEncryptionAzureKeyVaultProvider.getName() 。 預設名稱可讓您使用SQL Server Management Studio或 PowerShell 之類的工具來布建和管理Always Encrypted金鑰, (工具會使用預設名稱來產生中繼資料物件至資料行主要金鑰) 。 下列範例顯示註冊 Azure Key Vault 提供者。 如需 方法的詳細資訊 SQLServerConnection.registerColumnEncryptionKeyStoreProviders() ,請參閱JDBC 驅動程式的 Always Encrypted API 參考

Map<String, SQLServerColumnEncryptionKeyStoreProvider> keyStoreMap = new HashMap<String, SQLServerColumnEncryptionKeyStoreProvider>();
keyStoreMap.put(akvProvider.getName(), akvProvider);
SQLServerConnection.registerColumnEncryptionKeyStoreProviders(keyStoreMap);

重要

如果您使用 Azure Key Vault 金鑰儲存區提供者,JDBC 驅動程式的 Azure Key Vault 實作會具有針對這些 (來自 GitHub 的) 程式庫的相依性,您必須將其包含在您的應用程式中:

azure-sdk-for-java

microsoft-authentication-library-for-java 程式庫

如需如何在 Maven 專案中包含這些相依性的範例,請參閱 使用 Apache Maven 下載 MSAL4J 和 AKV 相依性

搭配受控識別使用 Azure 金鑰保存庫驗證

從 JDBC Driver 8.4.1開始,驅動程式已新增支援,以使用受控識別向 Azure Key Vault 進行驗證。

如果應用程式裝載在 Azure 中,您可以使用受控識別向 Azure 金鑰保存庫進行驗證。 這樣就不需要在程式碼中提供和公開任何認證。

適用於搭配受控識別之 Key Vault 驗證的連線屬性

針對 JDBC 驅動程式 8.4.1 與更新版本,驅動程式已引進下列連線屬性:

ConnectionProperty 可能的值配對 1 可能的值配對 2 可能的值配對 3
keyStoreAuthentication KeyVaultClientSecret KeyVaultManagedIdentity JavaKeyStorePassword
keyStorePrincipalId <Azure AD 應用程式用戶端識別碼> <Azure AD Application 物件識別碼 > (選擇性) n/a
keyStoreSecret <Azure AD 應用程式用戶端密碼> n/a <JAVA 金鑰存放區的秘密/密碼>

下列範例顯示如何在連接字串中使用連線屬性。

使用受控識別來驗證至 AKV

"jdbc:sqlserver://<server>:<port>;encrypt=true;columnEncryptionSetting=Enabled;keyStoreAuthentication=KeyVaultManagedIdentity;"

使用受控識別和主體識別碼來驗證至 AKV

"jdbc:sqlserver://<server>:<port>;encrypt=true;columnEncryptionSetting=Enabled;keyStoreAuthentication=KeyVaultManagedIdentity;keyStorePrincipal=<principalId>"

使用 clientId 與 clientSecret 來驗證至 AKV

"jdbc:sqlserver://<server>:<port>;encrypt=true;columnEncryptionSetting=Enabled;keyStoreAuthentication=KeyVaultClientSecret;keyStorePrincipalId=<clientId>;keyStoreSecret=<clientSecret>"

建議使用者使用這些連線屬性來指定金鑰存放區所使用的驗證類型,而不是 SQLServerColumnEncryptionAzureKeyVaultProvider API。

先前新增的連接屬性 keyVaultProviderClientIdkeyVaultProviderClientKey 已被上述連接屬性取代並取代。

如需如何設定受控識別的相關資訊,請參閱使用 Azure 入口網站在 VM 上設定 Azure 資源的受控識別

使用 Windows 憑證存放區提供者

SQLServerColumnEncryptionCertificateStoreProvider可用來在 Windows 憑證存放區中儲存資料行主要金鑰。 使用 [SQL Server Management Studio (SSMS) Always Encrypted] 精靈或其他支援的工具來在資料庫中建立資料行主要金鑰和資料行加密金鑰定義。 相同的精靈也可以用來在 Windows 憑證存放區中產生自我簽署憑證,以作為 Always Encrypted 資料的資料行主要金鑰使用。 如需資料行主要金鑰和資料行加密金鑰 T-SQL 語法的詳細資訊,請分別參閱建立資料行主要金鑰建立資料行加密金鑰

的名稱 SQLServerColumnEncryptionCertificateStoreProvider MSSQL_CERTIFICATE_STORE,而且可由提供者物件的 getName () API 查詢。 其會由驅動程式自動註冊,並可以在無需任何應用程式變更的情況下順暢地使用。

針對此頁面上的範例,如果您已使用 SQL Server Management Studio 建立以 Windows 憑證存放區為基礎的資料行主要金鑰和資料行加密金鑰,則要重新建立的 T-SQL 腳本看起來可能類似于此範例,其專屬KEY_PATHENCRYPTED_VALUE

CREATE COLUMN MASTER KEY [MyCMK]
WITH
(
    KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
    KEY_PATH = N'CurrentUser/My/A2A91F59C461B559E4D962DA9D2BC6131B32CB91'
);

CREATE COLUMN ENCRYPTION KEY [MyCEK]
WITH VALUES
(
    COLUMN_MASTER_KEY = [MyCMK],
    ALGORITHM = 'RSA_OAEP',
    ENCRYPTED_VALUE = 0x016E000001630075007200720065006E0074007500730065007200...
);

重要

雖然本文中的其他金鑰存放區提供者可在驅動程式支援的所有平臺上取得, SQLServerColumnEncryptionCertificateStoreProvider 但 JDBC 驅動程式的實作僅適用于 Windows 作業系統。 它相依于 mssql-jdbc_auth-<version>-<arch>.dll 驅動程式套件中可用的 。 若要使用此提供者,請將 mssql-jdbc_auth-<version>-<arch>.dll 檔案複製到安裝 JDBC 驅動程式之電腦上的 Windows 系統路徑目錄。 或者,您也可以設定 java.library.path 系統屬性,以指定 的 mssql-jdbc_auth-<version>-<arch>.dll 目錄。 如果您要執行 32 位 JAVA 虛擬機器 (JVM) , mssql-jdbc_auth-<version>-x86.dll 請使用 x86 資料夾中的檔案,即使作業系統是 x64 版本也一樣。 如果您在 x64 處理器上執行 64 位 JVM,請使用 mssql-jdbc_auth-<version>-x64.dll x64 資料夾中的檔案。 例如,如果您使用 32 位 JVM,且 JDBC 驅動程式已安裝在預設目錄中,您可以在 JAVA 應用程式啟動時,使用下列虛擬機器指定 DLL 的位置 (VM) 引數: -Djava.library.path=C:\Microsoft JDBC Driver <version> for SQL Server\sqljdbc_<version>\enu\auth\x86

使用 JAVA 金鑰存放區提供者

JDBC 驅動程式隨附於 Java 金鑰存放區的內建金鑰存放區提供者實作。 keyStoreAuthentication如果連接字串屬性存在於連接字串中,且它設定 JavaKeyStorePassword 為 ,驅動程式會自動具現化並註冊 JAVA Key Store 的提供者。 Java Key Store 提供者的名稱是 MSSQL_JAVA_KEYSTORE。 此名稱也可以由 SQLServerColumnEncryptionJavaKeyStoreProvider.getName() API 查詢。

有三個連接字串屬性允許用戶端應用程式指定驅動程式針對 Java Key Store 進行驗證所需的認證。 驅動程式會根據連接字串中這三個屬性的值來初始化提供者。

keyStoreAuthentication識別要使用的 JAVA 金鑰存放區。 在使用 Microsoft JDBC Driver 6.0 (和更新版本) for SQL Server 的情況下,您只能透過此屬性來針對 Java Key Store 進行驗證。 針對 Java Key Store,此屬性的值必須是 JavaKeyStorePassword

keyStoreLocation儲存資料行主要金鑰的 JAVA 金鑰存放區檔案路徑。 路徑包含金鑰儲存區檔案名稱。

keyStoreSecret用於金鑰存放區和金鑰的秘密/密碼。 若要使用 JAVA 金鑰存放區,金鑰存放區和金鑰密碼必須相同。

以下是在連接字串中提供這些認證的範例:

String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;user=<user>;password=<password>;columnEncryptionSetting=Enabled;keyStoreAuthentication=JavaKeyStorePassword;keyStoreLocation=<path_to_the_keystore_file>;keyStoreSecret=<keystore_key_password>";

您也可以使用 SQLServerDataSource 物件取得或設定這些設定。 如需詳細資訊,請參閱 JDBC 驅動程式的 Always Encrypted API 參考

JDBC 驅動程式會在連接屬性中出現這些認證時自動具現化 SQLServerColumnEncryptionJavaKeyStoreProvider

建立 Java Key Store 的資料行主要金鑰

SQLServerColumnEncryptionJavaKeyStoreProvider可以搭配 JKS 或 PKCS12 金鑰存放區類型使用。 若要建立或匯入金鑰以搭配此提供者使用,請使用 Java keytool \(英文\) 公用程式。 金鑰的密碼必須和金鑰儲存區本身相同。 以下是如何使用公用程式建立公開金鑰及其相關聯私密金鑰的 keytool 範例:

keytool -genkeypair -keyalg RSA -alias AlwaysEncryptedKey -keystore keystore.jks -storepass mypassword -validity 360 -keysize 2048 -storetype jks

此命令會建立公開金鑰並將其包裝在 X.509 自我簽署憑證中,該憑證會連同其相關聯的私密金鑰儲存在金鑰儲存區 keystore.jks 中。 此項目在金鑰儲存區中是透過別名 AlwaysEncryptedKey 來識別。

以下是與 PKCS12 存放區類型相同的範例:

keytool -genkeypair -keyalg RSA -alias AlwaysEncryptedKey -keystore keystore.pfx -storepass mypassword -validity 360 -keysize 2048 -storetype pkcs12 -keypass mypassword

如果金鑰存放區的類型為 PKCS12,keytool 公用程式不會提示輸入金鑰密碼,而且必須提供 -keypass 金鑰密碼選項,因為 SQLServerColumnEncryptionJavaKeyStoreProvider 需要金鑰存放區和金鑰具有相同的密碼。

您也可以使用 .pfx 格式從 Windows 憑證存放區匯出憑證,並搭配 SQLServerColumnEncryptionJavaKeyStoreProvider 使用。 匯出的憑證也可以作為 JKS 金鑰儲存區類型匯入 Java Key Store。

建立 keytool 專案之後,請在資料庫中建立資料行主要金鑰中繼資料,這需要金鑰存放區提供者名稱和金鑰路徑。 如需如何建立資料行主要金鑰中繼資料的詳細資訊,請參閱建立資料行主要金鑰。 針對 SQLServerColumnEncryptionJavaKeyStoreProvider ,索引鍵路徑只是索引鍵的別名,而 的名稱 SQLServerColumnEncryptionJavaKeyStoreProviderMSSQL_JAVA_KEYSTORE 。 您也可以使用 getName() 類別的 SQLServerColumnEncryptionJavaKeyStoreProvider 公用 API 查詢此名稱。

建立資料行主要金鑰的 T-SQL 語法為:

CREATE COLUMN MASTER KEY [<CMK_name>]
WITH
(
    KEY_STORE_PROVIDER_NAME = N'MSSQL_JAVA_KEYSTORE',
    KEY_PATH = N'<key_alias>'
);

針對上面所建立的 'AlwaysEncryptedKey',資料行主要金鑰定義將會是:

CREATE COLUMN MASTER KEY [MyCMK]
WITH
(
    KEY_STORE_PROVIDER_NAME = N'MSSQL_JAVA_KEYSTORE',
    KEY_PATH = N'AlwaysEncryptedKey'
);

注意

內建的 SQL Server Management Studio 功能無法針對 Java Key Store 建立資料行主要金鑰定義。 T-SQL 命令必須以程式設計方式使用。

建立 JAVA 金鑰存放區的資料行加密金鑰

SQL Server Management Studio 或任何其他工具皆無法用來透過 Java Key Store 中的資料行主要金鑰建立資料行加密金鑰。 用戶端應用程式必須使用 類別,以程式設計方式 SQLServerColumnEncryptionJavaKeyStoreProvider 建立資料行加密金鑰。 如需詳細資訊,請參閱 使用資料行主要金鑰存放區提供者進行程式設計金鑰布建

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

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

  • SQLServerConnection.registerColumnEncryptionKeyStoreProviders
  • SQLServerConnection.registerColumnEncryptionKeyStoreProvidersOnConnection (JDBC 10.2 版中新增)
  • SQLServerStatement.registerColumnEncryptionKeyStoreProvidersOnStatement (JDBC 10.2 版中新增)
public class MyCustomKeyStore extends SQLServerColumnEncryptionKeyStoreProvider{
    private String name = "MY_CUSTOM_KEYSTORE";

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, byte[] plainTextColumnEncryptionKey)
    {
        // Logic for encrypting the column encryption key
    }

    public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
    {
        // Logic for decrypting the column encryption key
    }
}

向 註冊提供者 SQLServerConnection.registerColumnEncryptionKeyStoreProviders

SQLServerColumnEncryptionKeyStoreProvider storeProvider = new MyCustomKeyStore();
Map<String, SQLServerColumnEncryptionKeyStoreProvider> keyStoreMap = new HashMap<String, SQLServerColumnEncryptionKeyStoreProvider>();
keyStoreMap.put(storeProvider.getName(), storeProvider);
SQLServerConnection.registerColumnEncryptionKeyStoreProviders(keyStoreMap);

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

本節適用于 JDBC 驅動程式 10.2 版和更新版本。

Microsoft JDBC Driver for SQL Server不會快取 CEK 資料行 (加密金鑰) 由在連線或語句實例上註冊的自訂金鑰存放區提供者解密。 自訂金鑰存放區提供者應該實作自己的 CEK 快取機制。

自 10.2 版起,具有 SQLServerColumnEncryptionAzureKeyVaultProvider 自己的 CEK 快取實作。 在連線或語句實例上註冊時,實例解密的 SQLServerColumnEncryptionAzureKeyVaultProvider CEK 會在該實例超出範圍時清除:

try (SQLServerConnection conn = getConnection(); SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) {

    Map<String, SQLServerColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new HashMap<>();
    SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(clientID, clientKey);
    customKeyStoreProviders.put(akvProvider.getName(), akvProvider);
    stmt.registerColumnEncryptionKeyStoreProvidersOnStatement(customKeyStoreProviders);
    // Perform database operation with Azure Key Vault Provider
    // Any decrypted column encryption keys will be cached              
} // Column encryption key cache of "akvProvider" is cleared when "akvProvider" goes out of scope

注意

如果金鑰存放區提供者實例是全域向 方法註冊驅動程式中的金鑰存放區提供者實例,則驅動程式會停用自訂金鑰存放區提供者所實作的 SQLServerConnection.registerColumnEncryptionKeyStoreProviders CEK 快取。 任何 CEK 快取實作都應該在快取 CEK 之前參考存留時間的值,如果值為零,則不要快取它。 這可避免在嘗試設定金鑰快取時重複的快取和可能的使用者混淆。 快取的存留時間值可以使用 方法設定 SQLServerColumnEncryptionKeyStoreProvider.setColumnEncryptionCacheTtl

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

本節適用于 JDBC 驅動程式 10.2 版和更新版本。

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

  • 檢查每個語句註冊是否不是空的。
  • 如果每個語句註冊是空的,則會檢查個別連線註冊是否不是空的。
  • 如果個別連線註冊是空的,則會檢查全域註冊。

在註冊層級找到任何金鑰存放區提供者之後, 驅動程式將不會回復 至其他註冊來搜尋提供者。 如果提供者已註冊,但在層級找不到適當的提供者,則會擲回例外狀況,只包含已檢查之註冊中的已註冊提供者。

Windows 憑證存放區可用的內建資料行主要金鑰存放區提供者已預先註冊。 如果事先提供認證,Microsoft JAVA Keystore 提供者和 Azure 金鑰保存庫金鑰存放區提供者可以隱含地預先向連線實例註冊。

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

在多個使用者之間共用 SQLServerConnection 實例的應用程式可能會想要使用 SQLServerStatement.registerColumnEncryptionKeyStoreProvidersOnStatement 。 每個使用者都必須在實例上 SQLServerStatement 註冊金鑰存放區提供者,才能執行查詢來存取加密的資料行。 如果金鑰存放區提供者能夠存取使用使用者指定認證的金鑰存放區中所需的資料行主要金鑰,查詢將會成功。

為每個使用者建立 SQLServerConnection 實例的應用程式可能會想要使用 SQLServerConnection.registerColumnEncryptionKeyStoreProvidersOnConnection 。 透過這個方法註冊的金鑰存放區提供者,可供連線用於存取加密資料的任何查詢。

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

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

Map<String, SQLServerColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new HashMap<>();
MyCustomKeyStore myProvider = new MyCustomKeyStore();
customKeyStoreProviders.put(myProvider.getName(), myProvider);
// Registers the provider globally
SQLServerConnection.registerColumnEncryptionKeyStoreProviders(customKeyStoreProviders);

try (SQLServerConnection conn = getConnection()) {
    customKeyStoreProviders.clear();
    SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(clientID, clientKey);
    customKeyStoreProviders.put(akvProvider.getName(), akvProvider);
    
    // Registers the provider on the connection
    // These providers will take precedence over globally registered providers
    conn.registerColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);              
}

下列範例顯示在語句實例上註冊的自訂資料行主要金鑰存放區提供者的優先順序:

Map<String, SQLServerColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new HashMap<>();
MyCustomKeyStore firstProvider = new MyCustomKeyStore();
customKeyStoreProviders.put("FIRST_CUSTOM_STORE", firstProvider);
// Registers the provider globally
SQLServerConnection.registerColumnEncryptionKeyStoreProviders(customKeyStoreProviders);

try (SQLServerConnection conn = getConnection()) {
    customKeyStoreProviders.clear();
    MyCustomKeyStore secondProvider = new MyCustomKeyStore();
    customKeyStoreProviders.put("SECOND_CUSTOM_STORE", secondProvider);    
    // Registers the provider on the connection
    conn.registerColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);

    try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) {
        customKeyStoreProviders.clear();
        SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(clientID, clientKey);
        customKeyStoreProviders.put(akvProvider.getName(), akvProvider);

        // Registers the provider on the statement
        // These providers will take precedence over connection-level providers and globally registered providers
        stmt.registerColumnEncryptionKeyStoreProvidersOnStatement(customKeyStoreProviders);
    }             
}

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

若要存取加密的資料行,Microsoft JDBC Driver for SQL Server會透明地尋找並呼叫正確的資料行主要金鑰存放區提供者來解密資料行加密金鑰。 一般來說,一般應用程式程式碼不會直接呼叫資料行主要金鑰存放區提供者。 不過,您可以透過程式設計方式具現化並呼叫提供者,來佈建及管理 Always Encrypted 金鑰。 例如,您可執行此步驟來產生加密的資料行加密金鑰,並以資料行主要金鑰變換之一部分的方式將資料行加密金鑰解密。 如需詳細資訊,請參閱 永遠加密的金鑰管理概觀

如果使用自訂的金鑰存放區提供者,則可能需要實作您自己的金鑰管理工具。 若要使用儲存在 Windows 憑證存放區或 Azure 金鑰保存庫中的金鑰,您可以使用現有的工具,例如SQL Server Management Studio或 PowerShell 來管理和布建金鑰。 若要使用儲存在 JAVA 金鑰存放區中的金鑰,您需要以程式設計方式布建金鑰。 下列範例說明如何使用 SQLServerColumnEncryptionJavaKeyStoreProvider 類別,以儲存在 JAVA 金鑰存放區中的金鑰來加密金鑰。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider;
import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionKeyStoreProvider;
import com.microsoft.sqlserver.jdbc.SQLServerException;

/**
 * This program demonstrates how to create a column encryption key programmatically for the Java Key Store.
 */
public class AlwaysEncrypted {
    // Alias of the key stored in the keystore.
    private static String keyAlias = "<provide key alias>";

    // Name by which the column master key will be known in the database.
    private static String columnMasterKeyName = "MyCMK";

    // Name by which the column encryption key will be known in the database.
    private static String columnEncryptionKey = "MyCEK";

    // The location of the keystore.
    private static String keyStoreLocation = "C:\\Dev\\Always Encrypted\\keystore.jks";

    // The password of the keystore and the key.
    private static char[] keyStoreSecret = "********".toCharArray();

    /**
     * Name of the encryption algorithm used to encrypt the value of the column encryption key. The algorithm for the system providers must be
     * RSA_OAEP.
     */
    private static String algorithm = "RSA_OAEP";

    public static void main(String[] args) {
        String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=<databaseName>;user=<user>;password=<password>;columnEncryptionSetting=Enabled;";

        try (Connection connection = DriverManager.getConnection(connectionUrl);
                Statement statement = connection.createStatement();) {

            // Instantiate the Java Key Store provider.
            SQLServerColumnEncryptionKeyStoreProvider storeProvider = new SQLServerColumnEncryptionJavaKeyStoreProvider(keyStoreLocation,
                    keyStoreSecret);

            byte[] encryptedCEK = getEncryptedCEK(storeProvider);

            /**
             * Create column encryption key For more details on the syntax, see:
             * https://learn.microsoft.com/sql/t-sql/statements/create-column-encryption-key-transact-sql Encrypted column encryption key first needs
             * to be converted into varbinary_literal from bytes, for which byteArrayToHex() is used.
             */
            String createCEKSQL = "CREATE COLUMN ENCRYPTION KEY "
                    + columnEncryptionKey
                    + " WITH VALUES ( "
                    + " COLUMN_MASTER_KEY = "
                    + columnMasterKeyName
                    + " , ALGORITHM =  '"
                    + algorithm
                    + "' , ENCRYPTED_VALUE =  0x"
                    + byteArrayToHex(encryptedCEK)
                    + " ) ";
            statement.executeUpdate(createCEKSQL);
            System.out.println("Column encryption key created with name : " + columnEncryptionKey);
        }
        // Handle any errors that may have occurred.
        catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private static byte[] getEncryptedCEK(SQLServerColumnEncryptionKeyStoreProvider storeProvider) throws SQLServerException {
        String plainTextKey = "You need to give your plain text";

        // plainTextKey has to be 32 bytes with current algorithm supported
        byte[] plainCEK = plainTextKey.getBytes();

        // This will give us encrypted column encryption key in bytes
        byte[] encryptedCEK = storeProvider.encryptColumnEncryptionKey(keyAlias, algorithm, plainCEK);

        return encryptedCEK;
    }

    public static String byteArrayToHex(byte[] a) {
        StringBuilder sb = new StringBuilder(a.length * 2);
        for (byte b : a)
            sb.append(String.format("%02x", b).toUpperCase());
        return sb.toString();
    }
}

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

啟用參數加密和加密資料行查詢結果解密的最簡單方式,就是將連接字串關鍵字的值 columnEncryptionSetting 設定為 Enabled

下列連接字串是在 JDBC 驅動程式中啟用 Always Encrypted 的範例:

String connectionUrl = "jdbc:sqlserver://<server>:<port>;user=<user>;encrypt=true;password=<password>;databaseName=<database>;columnEncryptionSetting=Enabled;";
SQLServerConnection connection = (SQLServerConnection) DriverManager.getConnection(connectionUrl);

下列程式碼是使用 SQLServerDataSource 物件的對等範例:

SQLServerDataSource ds = new SQLServerDataSource();
ds.setServerName("<server>");
ds.setPortNumber(<port>);
ds.setUser("<user>");
ds.setPassword("<password>");
ds.setDatabaseName("<database>");
ds.setColumnEncryptionSetting("Enabled");
SQLServerConnection con = (SQLServerConnection) ds.getConnection();

個別查詢也可以啟用 [永遠加密]。 如需詳細資訊,請參閱控制 Always Encrypted 的效能影響。 啟用 Always Encrypted 並不足以保證加密或解密成功。 您還需要確定︰

  • 應用程式具有 VIEW ANY COLUMN MASTER KEY DEFINITIONVIEW ANY COLUMN ENCRYPTION KEY DEFINITION 資料庫許可權,需要存取資料庫中Always Encrypted金鑰的相關中繼資料。 如需詳細資料,請參閱 Always Encrypted (資料庫引擎) 的權限
  • 應用程式可以存取保護資料行加密金鑰的資料行主要金鑰,這些加密金鑰會加密查詢的資料庫資料行。 若要使用 JAVA 金鑰存放區提供者,您必須在連接字串中提供額外的認證。 如需詳細資訊,請參閱 使用 JAVA 金鑰存放區提供者

設定 java.sql.Time 值如何傳送給伺服器

連接 sendTimeAsDatetime 屬性可用來設定 java.sql.Time 值如何傳送至伺服器。 設定為 false 時,會以 SQL Server 時間類型的形式傳送時間值。 設定為 true 時,會以日期時間類型的形式傳送時間值。 如果時間資料行已加密,屬性 sendTimeAsDatetime 必須是 false,因為加密的資料行不支援從時間到日期時間的轉換。 另請注意,此屬性預設為 true,因此使用加密的時間資料行將它設定為 false。 否則,驅動程式將會擲回例外狀況。 從驅動程式 6.0 版開始,類別 SQLServerConnection 有兩種方法,以程式設計方式設定此屬性的值:

  • public void setSendTimeAsDatetime(boolean sendTimeAsDateTimeValue)
  • public boolean getSendTimeAsDatetime()

如需此屬性的詳細資訊,請參閱設定 java.sql.Time 值如何傳送給伺服器

設定 String 值如何傳送給伺服器

連接 sendStringParametersAsUnicode 屬性可用來設定字串值傳送至SQL Server的方式。 如果設定為 true,則以 Unicode 格式將 String 參數傳送到伺服器。 如果設定為 false,String 參數就會以非 Unicode 格式 (例如 ASCII 或 MBCS) 傳送,而非 Unicode 格式。 這個屬性的預設值是 True。 啟用Always Encrypted且 char//varcharvarchar(max) 資料行已加密時,的值 sendStringParametersAsUnicode 必須設定為 false。 如果此屬性設定為 true,則驅動程式會在解密具有 Unicode 字元之加密 char//varcharvarchar(max) 資料行的資料時擲回例外狀況。 如需此屬性的詳細資訊,請參閱設定連線屬性

重要

如果 sendStringParametersAsUnicode 設定 true 為 ,且 unicode 資料會 varcharchar/ 插入以Always Encrypted加密的資料行中,可能會發生資料遺失,而不會回報錯誤。 只有在從伺服器讀取資料後嘗試解密資料時,才會偵測到資料遺失。 類似 Decryption failed. The last 10 bytes of the encrypted column encryption key are: 'C3-D9-10-4E-C1-45-8B-94-A2-43'. The first 10 bytes of ciphertext are: '01-9B-9D-A6-3E-40-22-53-15-9B'. 的錯誤可能是結果。

請務必使用正確的資料行資料類型,並在插入加密資料時指定參數的正確資料類型。 如果預期 Unicode 資料,請使用 nchar/nvarchar 資料行和 setNString() 方法。 伺服器無法執行隱含資料轉換,而且啟用Always Encrypted時偵測資料錯誤的能力有限。

擷取和修改加密資料行中的資料

在您針對應用程式查詢啟用 Always Encrypted 之後,您可以使用標準 JDBC API 來擷取或修改加密資料庫資料行中的資料。 如果您的應用程式具有必要的資料庫許可權,而且可以存取資料行主要金鑰,驅動程式會加密任何以加密資料行為目標的查詢參數,以及解密從加密資料行擷取的資料。

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

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

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

插入及擷取加密的資料範例

以下範例將說明擷取和修改加密資料行中的資料。 這些範例假設具有下列結構描述及加密 SSN 和 BirthDate 資料行的目標資料表。 如果您已設定名為 「MyCMK」 的資料行主要金鑰,以及名為 「MyCEK」 的資料行加密金鑰 (,如上述金鑰存放區提供者) 一節所述,您可以使用下列腳本來建立資料表:

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 = MyCEK) 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 = MyCEK) NOT NULL
 PRIMARY KEY CLUSTERED ([PatientId] ASC) ON [PRIMARY]);
 GO

針對每個 Java 程式碼範例,您必須在所註記的位置中插入金鑰儲存區特定的程式碼。

若要使用 Azure 金鑰保存庫金鑰存放區提供者:

    String clientID = "<Azure Application ID>";
    String clientKey = "<Azure Application API Key Password>";
    SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(clientID, clientKey);
    Map<String, SQLServerColumnEncryptionKeyStoreProvider> keyStoreMap = new HashMap<String, SQLServerColumnEncryptionKeyStoreProvider>();
    keyStoreMap.put(akvProvider.getName(), akvProvider);
    SQLServerConnection.registerColumnEncryptionKeyStoreProviders(keyStoreMap);
    String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=<databaseName>;user=<user>;password=<password>;columnEncryptionSetting=Enabled;";

若要使用 Windows 憑證存放區金鑰存放區提供者:

    String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=<databaseName>;user=<user>;password=<password>;columnEncryptionSetting=Enabled;";

若要使用 JAVA 金鑰存放區金鑰存放區提供者:

    String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=<databaseName>;user=<user>;password=<password>;columnEncryptionSetting=Enabled;keyStoreAuthentication=JavaKeyStorePassword;keyStoreLocation=<path to jks or pfx file>;keyStoreSecret=<keystore secret/password>";

插入資料範例

本例會將資料列插入病患資料表。 請注意下列項目:

  • 範例程式碼中沒有任何需要加密的特定項目。 The Microsoft JDBC Driver for SQL Server 會自動偵測以加密資料行為目標的參數並加以加密。 此行為可讓加密對應用程式變得透明化。
  • 插入資料庫資料行的值,包括加密的資料行,會以 參數 SQLServerPreparedStatement 的形式傳遞。 雖然參數在將值傳送至非加密資料行時是選擇性的 (,但強烈建議您這麼做,因為它有助於防止 SQL 插入) ,但目標為加密資料行的值需要參數。 如果將插入加密資料行中的值當作內嵌在查詢陳述式中的常值傳遞,則查詢會失敗,因為驅動程式無法判斷目標加密資料行中的值,且其不會將那些值加密。 結果,伺服器會因與加密資料行不相容而拒絕它們。
  • 程式列印的所有值都是純文字格式,Microsoft JDBC Driver for SQL Server 會以清晰簡明的方式解密從已加密資料行擷取的資料。
  • 如果您要使用 WHERE 子句進行查閱,WHERE 子句中使用的值必須傳遞為參數,以便驅動程式可以在將它傳送至資料庫之前,以透明方式加密它。 在下列範例中,會以參數形式傳遞 SSN,但會以常值形式傳遞 LastName,因為 LastName 不會加密。
  • 用於以 SSN 資料行為目標之參數的 setter 方法為 setString() ,其會對應至 char/varchar SQL Server資料類型。 如果針對此參數,使用的 setter 方法會 setNString() 對應至 nvarcharnchar/ ,因此查詢會失敗,因為Always Encrypted不支援從加密 nvarcharnchar/ 值轉換為加密 char/varchar 值。
// <Insert keystore-specific code here>
try (Connection sourceConnection = DriverManager.getConnection(connectionUrl);
        PreparedStatement insertStatement = sourceConnection.prepareStatement("INSERT INTO [dbo].[Patients] VALUES (?, ?, ?, ?)")) {
    insertStatement.setString(1, "795-73-9838");
    insertStatement.setString(2, "Catherine");
    insertStatement.setString(3, "Abel");
    insertStatement.setDate(4, Date.valueOf("1996-09-10"));
    insertStatement.executeUpdate();
    System.out.println("1 record inserted.\n");
}
// Handle any errors that may have occurred.
catch (SQLException e) {
    e.printStackTrace();
}

擷取純文字資料範例

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

  • WHERE 子句中用來篩選 SSN 資料行的值必須傳遞為參數,讓 Microsoft JDBC Driver for SQL Server可以透明地加密它,再將它傳送至資料庫。
  • 程式列印的所有值都是純文字格式,Microsoft JDBC Driver for SQL Server 會以清晰簡明的方式解密從 SSN 和 BirthDate 資料行擷取的資料。

注意

如果資料行使用決定性加密進行加密,查詢就可以對其執行相等比較。 如需詳細資訊,請參閱 決定性加密

// <Insert keystore-specific code here>
try (Connection connection = DriverManager.getConnection(connectionUrl);
        PreparedStatement selectStatement = connection
                .prepareStatement("\"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE SSN = ?;\"");) {
    selectStatement.setString(1, "795-73-9838");
    ResultSet rs = selectStatement.executeQuery();
    while (rs.next()) {
        System.out.println("SSN: " + rs.getString("SSN") + ", FirstName: " + rs.getString("FirstName") + ", LastName:"
                + rs.getString("LastName") + ", Date of Birth: " + rs.getString("BirthDate"));
    }
}
// Handle any errors that may have occurred.
catch (SQLException e) {
    e.printStackTrace();
}

擷取加密的資料範例

若未啟用 Always Encrypted,只要查詢沒有以加密資料行為目標的參數,查詢就仍然可以從加密資料行擷取資料。

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

  • 因為連接字串未啟用 Always Encrypted,所以查詢會以位元組陣列 (程式會將值轉換為字串) 傳回加密的 SSN 和 BirthDate 值。
  • 從加密資料行擷取資料但停用 [永遠加密] 的查詢可以有參數,只要沒有任何參數以加密資料行為目標。 下列查詢依 LastName 篩選,在資料庫中未加密。 如果依 SSN 或 BirthDate 篩選查詢,查詢會失敗。
try (Connection sourceConnection = DriverManager.getConnection(connectionUrl);
        PreparedStatement selectStatement = sourceConnection
                .prepareStatement("SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE LastName = ?;");) {

    selectStatement.setString(1, "Abel");
    ResultSet rs = selectStatement.executeQuery();
    while (rs.next()) {
        System.out.println("SSN: " + rs.getString("SSN") + ", FirstName: " + rs.getString("FirstName") + ", LastName:"
                + rs.getString("LastName") + ", Date of Birth: " + rs.getString("BirthDate"));
    }
}
// Handle any errors that may have occurred.
catch (SQLException e) {
    e.printStackTrace();
}

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

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

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

[永遠加密] 支援極少數的加密資料類型轉換。 如需支援的類型轉換詳細清單,請參閱 Always Encrypted (資料庫引擎)。 以下是您可以執行的動作,以避免資料類型轉換錯誤。 請確認:

  • 您在針對以加密資料行為目標的參數傳遞值時,是使用適當的 setter 方法。 確定參數的 SQL Server 資料類型和目標資料行完全相同,或支援將參數的 SQL Server 資料類型轉換成資料行的目標類型。 API 方法已新增至 、 SQLServerCallableStatementSQLServerResultSet 類別,以傳遞對應至 SQLServerPreparedStatement 特定SQL Server資料類型的參數。 如需新 API 的完整清單,請參閱Always Encrypted JDBC 驅動程式的 API 參考。 以下是使用Always Encrypted時可能需要的一些調整範例:
    • 您可以使用 setTimestamp() 方法,將參數傳遞至非加密的 datetime2 或 datetime 資料行。 但是,當資料行加密時,您必須使用代表資料庫中資料行類型的確切方法。 使用 setTimestamp() 將值傳遞至加密的 datetime2 資料行,並使用 setDateTime() 將值傳遞至加密的 datetime 資料行。
    • 您可以使用 setBinary() 方法,將參數傳遞至非加密 varbinary(max)binary 資料行。 驅動程式預設為參數的資料類型 setBinary()BINARY 而且伺服器可以隱含地轉換資料以插入資料行 varbinary(max) 。 但是,當資料行 varbinary(max) 加密時,您必須為參數資料指定更精確的類型。 範例: preparedStatement.setObject(1, binaryData, java.sql.JDBCType.LONGVARBINARY)
  • 以小數和數值 SQL Server 資料類型資料行為目標之參數的有效位數和小數位數,和為目標資料行設定的有效位數和小數位數相同。 API 方法已新增至 SQLServerPreparedStatementSQLServerCallableStatementSQLServerResultSet 類別,以接受有效位數和小數位數,以及代表十進位和數值資料類型的參數/資料行的資料值。 請參閱 JDBC 驅動程式的 Always Encrypted API 參考以取得新的/多載 API 的完整清單。
  • 以 、 datetimeoffset 或 時間SQL Server資料類型為目標 datetime2 之參數的小數秒有效位數/小數位數,不會大於修改目標資料行值的查詢中目標資料行的小數秒有效位數/小數位數。 API 方法已新增至 SQLServerPreparedStatementSQLServerCallableStatementSQLServerResultSet 類別,以接受小數秒有效位數/小數位數,以及代表這些資料類型之參數的資料值。 如需新的/多載 API 的完整清單,請參閱 JDBC 驅動程式的 Always Encrypted API 參考

不正確連線屬性所導致的錯誤

此節說明如何正確設定連線設定以使用 Always Encrypted 資料。 由於加密資料類型支援有限的轉換, sendTimeAsDatetime 因此 和 sendStringParametersAsUnicode 連線設定需要適當的設定,才能使用加密的資料行。 請確認:

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

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

com.microsoft.sqlserver.jdbc.SQLServerException: 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 = 'MyCEK', column_encryption_key_database_name = 'ae') collation_name = 'SQL_Latin1_General_CP1_CI_AS'

若要避免這類錯誤,請確定︰

  • (針對連接字串或特定查詢) 以加密資料行為目標的應用程式查詢已啟用 Always Encrypted。
  • 您使用陳述式及參數來傳送以加密資料行為目標的資料。 下列範例示範對加密資料行 (SSN) 以常值/常數錯誤篩選 (不是以參數形式在內部傳遞常值)。 此查詢將會失敗:
ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM Customers WHERE SSN='795-73-9838'");

針對輸入參數強制加密

強制加密功能會使用 Always Encrypted 來強制執行參數的加密。 如果使用強制加密並SQL Server通知驅動程式不需要加密參數,則使用 參數的查詢將會失敗。 此屬性提供對安全性攻擊的新增保護,這些攻擊牽涉到遭入侵SQL Server提供不正確的加密中繼資料給用戶端,這可能會導致資料洩漏。 和 SQLServerCallableStatement 類別和 SQLServerPreparedStatement 類別 update* 中的 SQLServerResultSet set* 方法會多載,以接受布林值引數來指定強制加密設定。 如果此引數的值為 false,驅動程式不會針對參數強制加密。 如果強制加密已設定為 true,查詢參數將只會在連線或陳述式上的目的地資料行已加密且 Always Encrypted 已啟用的情況下加以傳送。 此屬性提供額外的安全性層,確保驅動程式在預期加密時不會誤將資料傳送至SQL Server為純文字。

如需使用強制加密設定多載之 和 SQLServerCallableStatement 方法的詳細資訊 SQLServerPreparedStatement ,請參閱JDBC 驅動程式的 Always Encrypted API 參考

控制 Always Encrypted 的效能影響

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

  • 已新增往返資料庫以擷取查詢參數的中繼資料。
  • 呼叫資料行主要金鑰存放區以存取資料行主要金鑰。

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

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

如果為連線啟用Always Encrypted,則驅動程式預設會針對每個參數化查詢呼叫sys.sp_describe_parameter_encryption, (傳遞查詢語句,而不需將任何參數值) 至資料庫。 sys.sp_describe_parameter_encryption 分析查詢語句,以找出是否需要加密任何參數,如果是的話,則會針對每個參數傳回加密相關資訊,讓驅動程式加密參數值。 此行為可對用戶端應用程式確保高透明度。 只要應用程式是使用參數來將以加密資料行為目標的值傳遞到驅動程式,應用程式 (及應用程式開發人員) 便不需要知道哪些查詢會存取加密的資料行。

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

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

若要控制個別查詢的 Always Encrypted 行為,您需要透過傳遞列舉 SQLServerStatementColumnEncryptionSetting 來設定個別陳述式物件,該列舉會指定在針對該特定陳述式讀取及寫入加密的資料行時,資料的傳送及接收方式。 以下是一些實用的方針:

  • 如果用戶端應用程式透過資料庫連線傳送的大多數查詢都會存取加密資料行,請使用下列指導方針:

    • columnEncryptionSetting 連接字串關鍵字設定為 Enabled
    • 針對未存取任何加密資料行的個別查詢設定 SQLServerStatementColumnEncryptionSetting.Disabled 。 此設定會停用呼叫 sys.sp_describe_parameter_encryption 和解密結果集中的任何值。
    • 針對不需要加密但從加密資料行擷取資料之任何參數的個別查詢進行設定 SQLServerStatementColumnEncryptionSetting.ResultSet 。 此設定會停用呼叫 sys.sp_describe_parameter_encryption 和參數加密。 此查詢會將來自加密資料行的結果解密。
  • 如果用戶端應用程式透過資料庫連線傳送的大多數查詢都不會存取加密資料行,請使用下列指導方針:

    • columnEncryptionSetting 連接字串關鍵字設定為 Disabled
    • 針對具有任何需要加密之參數的個別查詢進行設定 SQLServerStatementColumnEncryptionSetting.Enabled 。 此設定可同時呼叫 sys.sp_describe_parameter_encryption 和解密從加密資料行擷取的任何查詢結果。
    • 針對沒有任何參數需要加密但從加密資料行擷取資料的查詢進行設定 SQLServerStatementColumnEncryptionSetting.ResultSet 。 此設定會停用呼叫 sys.sp_describe_parameter_encryption 和參數加密。 此查詢會將來自加密資料行的結果解密。

這些 SQLServerStatementColumnEncryptionSetting 設定無法用來略過加密,並取得純文字資料的存取權。 如需如何設定陳述式上資料行加密的詳細資訊,請參閱 JDBC 驅動程式的 Always Encrypted API 參考

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

// Assumes the same table definition as in Section "Retrieving and modifying data in encrypted columns"
// where only SSN and BirthDate columns are encrypted in the database.
String connectionUrl = "jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=<database>;user=<user>;password=<password>;"
        + "keyStoreAuthentication=JavaKeyStorePassword;"
        + "keyStoreLocation=<keyStoreLocation>"
        + "keyStoreSecret=<keyStoreSecret>;";

String filterRecord = "SELECT FirstName, LastName, SSN, BirthDate FROM " + tableName + " WHERE LastName = ?";

try (SQLServerConnection connection = (SQLServerConnection) DriverManager.getConnection(connectionUrl);
        PreparedStatement selectStatement = connection.prepareStatement(filterRecord, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY,
                connection.getHoldability(), SQLServerStatementColumnEncryptionSetting.ResultSetOnly);) {

    selectStatement.setString(1, "Abel");
    ResultSet rs = selectStatement.executeQuery();
    while (rs.next()) {
        System.out.println("First name: " + rs.getString("FirstName"));
        System.out.println("Last name: " + rs.getString("LastName"));
        System.out.println("SSN: " + rs.getString("SSN"));
        System.out.println("Date of Birth: " + rs.getDate("BirthDate"));
    }
}
// Handle any errors that may have occurred.
catch (SQLException e) {
    e.printStackTrace();
}

查詢參數中繼資料快取

為了減少資料庫的往返次數,Microsoft JDBC Driver for SQL Server可以快取查詢參數的加密相關資訊。 自 11.2.0 版起,如果相關聯的SQL Server進程不使用安全記憶體保護區,則驅動程式會快取從sys.sp_describe_parameter_encryption呼叫傳回之參數的加密相關資訊。 若要使用安全記憶體保護區進行快取,伺服器必須支援在會話不再有效的情況下重新建立記憶體保護區會話。

資料行加密金鑰快取

為減少資料行主要金鑰存放區的呼叫次數以解密資料行加密金鑰,Microsoft JDBC Driver for SQL Server 會快取記憶體中的純文字資料行加密金鑰。 驅動程式從資料庫中繼資料收到加密的資料行加密金鑰值之後,驅動程式會先嘗試尋找對應至加密金鑰值的純文字資料行加密金鑰。 只有在快取中找不到加密的資料行加密金鑰值時,驅動程式才會呼叫包含資料行主要金鑰的金鑰存放區。

您可以使用 類別中的 API setColumnEncryptionKeyCacheTtl()SQLServerConnection ,為快取中的資料行加密金鑰專案設定存留時間值。 快取中資料行加密金鑰項目的預設存留時間值是兩小時。 若要關閉快取,請使用 0 的值。 若要設定任何存留時間值,請使用下列 API:

SQLServerConnection.setColumnEncryptionKeyCacheTtl (int columnEncryptionKeyCacheTTL, TimeUnit unit)

例如,若要設定 10 分鐘的存留時間值,請使用:

SQLServerConnection.setColumnEncryptionKeyCacheTtl (10, TimeUnit.MINUTES)

僅支援使用 DAYS、HOURS、MINUTES 或 SECONDS 作為時間單位。

使用 SQLServerBulkCopy 複製加密的資料

透過 SQLServerBulkCopy ,您可以將已經加密並儲存在一個資料表中的資料複製到另一個資料表,而不需解密資料。 若要這樣做:

  • 請確定目標資料表的加密組態與來源資料表的組態完全一致。 特別是,這兩個資料表都必須加密相同的資料行,且這些資料行必須使用相同的加密類型和相同的加密金鑰來加密。 如果任一目標資料行的加密方式不同於其對應的來源資料行,您將無法在複製作業後,解密目標資料表中的資料。 資料會損毀。
  • 設定這兩個資料庫對來源資料表和目標資料表的連線,但不啟用 Always Encrypted。
  • allowEncryptedValueModifications設定 選項。 如需詳細資訊,請參閱搭配 JDBC 驅動程式使用大量複製

注意

當指定 AllowEncryptedValueModifications 為此選項時,請小心,因為 Microsoft JDBC Driver for SQL Server 不會檢查資料是否確實加密,或是否使用與目標資料行相同的加密類型、演算法和金鑰正確加密。

另請參閱

Always Encrypted (資料庫引擎)