V rámci T-SQL Tuesday #69 jsem blogoval o omezeních Always Encrypted a zmínil jsem se tam o tom, že výkon může být negativně ovlivněn jeho používáním (jak byste mohli očekávat, silnější zabezpečení má často kompromisy). V tomto příspěvku jsem se na to chtěl rychle podívat a mít na paměti (znovu), že tyto výsledky jsou založeny na kódu CTP 2.2, takže velmi brzy ve vývojovém cyklu a nemusí nutně odrážet výkon, který budete mít. viz come RTM.
Nejprve jsem chtěl demonstrovat, že Always Encrypted funguje z klientských aplikací, i když tam není nainstalována nejnovější verze SQL Server 2016. Musíte si však nainstalovat náhled .NET Framework 4.6 (nejnovější verze zde a může se změnit), abyste podporovali Column Encryption Setting
atribut spojovacího řetězce. Pokud používáte Windows 10 nebo jste nainstalovali Visual Studio 2015, tento krok není nutný, protože byste již měli mít dostatečně aktuální verzi rozhraní .NET Framework.
Dále se musíte ujistit, že certifikát Always Encrypted existuje na všech klientech. V databázi vytvoříte hlavní a sloupcový šifrovací klíč, jak vám ukáže jakýkoli výukový program Always Encrypted, pak musíte exportovat certifikát z tohoto počítače a importovat jej do ostatních, kde poběží kód aplikace. Otevřete certmgr.msc
a rozbalte položku Certifikáty – Aktuální uživatel> Osobní> Certifikáty a měl by tam být jeden s názvem Always Encrypted Certificate
. Klikněte na něj pravým tlačítkem, vyberte Všechny úkoly> Exportovat a postupujte podle pokynů. Exportoval jsem soukromý klíč a poskytl heslo, což vytvořilo soubor .pfx. Poté stačí opakovat opačný proces na klientských počítačích:Otevřete certmgr.msc
, rozbalte Certifikáty – Aktuální uživatel> Osobní, klepněte pravým tlačítkem na Certifikáty, vyberte Všechny úkoly> Importovat a namiřte na soubor .pfx, který jste vytvořili výše. (Oficiální nápověda zde.)
(Existují bezpečnější způsoby správy těchto certifikátů – není pravděpodobné, že byste chtěli certifikát takto nasadit na všechny počítače, protože si brzy položíte otázku, k čemu to bylo? Dělal jsem to pouze v mém izolovaném prostředí pro účely této ukázky – chtěl jsem se ujistit, že moje aplikace načítá data po drátě a ne pouze v místní paměti.)
Vytváříme dvě databáze, jednu se šifrovanou tabulkou a jednu bez. Děláme to, abychom izolovali připojovací řetězce a také pro měření využití prostoru. Samozřejmě existují podrobnější způsoby, jak řídit, které příkazy potřebují používat připojení s povoleným šifrováním – viz poznámka s názvem „Řízení dopadu na výkon…“ v tomto článku.
Tabulky vypadají takto:
-- zašifrovaná kopie, v databázi Zašifrováno CREATE TABLE dbo.Zaměstnanci (ID INT IDENTITY(1,1) PRIMÁRNÍ KLÍČ, Příjmení NVARCHAR(32) COLLATE Latin1_General_BIN2 ENCRYPTION_TYPE =DETERMINACTHUMS_TYPE =DETERMINACMTH6_TYPE_DETERMINACTH6_ENHAESALGORI_6 ColumnKey) NENÍ NULL, Plat INT ŠIFROVANÝ S (ENCRYPTION_TYPE =NÁHODNĚ, ALGORITHM ='AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY =ColumnKey) NENÍ NULL); -- nešifrovaná kopie, v databázi Normal CREATE TABLE dbo.Employees( ID INT IDENTITY(1,1) PRIMÁRNÍ KLÍČ, Příjmení NVARCHAR(32) COLLATE Latin1_General_BIN2 NOT NULL, Plat INT NOT NULL);
S těmito tabulkami jsem chtěl nastavit velmi jednoduchou aplikaci příkazového řádku, která by prováděla následující úkoly proti šifrované i nešifrované verzi tabulky:
- Vložte 100 000 zaměstnanců, jednoho po druhém
- Přečtěte si 100 náhodných řádků 1000krát
- Výstup časových razítek před a po každém kroku
Máme tedy uloženou proceduru ve zcela samostatné databázi, která se používá k vytváření náhodných celých čísel reprezentujících platy a náhodných řetězců Unicode různé délky. Budeme to dělat jeden po druhém, abychom lépe simulovali skutečné použití 100 000 vložek, které se odehrávají nezávisle (i když ne současně, protože nejsem dost odvážný, abych se pokusil správně vyvinout a spravovat vícevláknovou aplikaci v C# nebo se pokusil koordinovat a synchronizovat více instancí jedné aplikace).
CREATE DATABASE Utility;GO USE Utility;GO CREATE PROCEDURE dbo.GenerateNameAndSalary @Name NVARCHAR(32) OUTPUT, @Salary INT OUTPUTASBEGIN SET NOCOUNT ON; SELECT @Name =LEFT(CONVERT(NVARCHAR(32), CRYPT_GEN_RANDOM(64)), RAND() * 32 + 1); SELECT @Plat =CONVERT(INT, RAND()*100000)/100*100;ENDGO
Pár řádků ukázkového výstupu (nezajímá nás skutečný obsah řetězce, jen to, že se liší):
酹2ዌ륒㦢㮧羮怰㉤盿⚉嗝䬴敏⽁캘♜鼹䓧98600 贓峂쌄탠❼缉腱豶7200 腱豶7200?Potom uložené procedury, které aplikace nakonec zavolá (ty jsou identické v obou databázích, protože vaše dotazy není třeba měnit, aby podporovaly Always Encrypted):
CREATE PROCEDURE dbo.AddPerson @LastName NVARCHAR(32), @Salary INTASBEGIN SET NOCOUNT ON; INSERT dbo.Employees(Příjmení, Plat) SELECT @Příjmení, @Plat;ENDGO CREATE PROCEDURE dbo.RetrievePeopleASBEGIN SET NOCOUNT ON; SELECT TOP (100) ID, Příjmení, Mzda OD dbo.Zaměstnanci ORDER BY NEWID();ENDGONyní kód C#, počínaje částí connectionStrings souboru App.config. Důležitou součástí je
Column Encryption Setting
možnost pouze pro databázi se zašifrovanými sloupci (pro stručnost předpokládejme, že všechny tři připojovací řetězce obsahují stejnýData Source
a stejnou autentizaci SQLUser ID
aPassword
):A Program.cs (omlouvám se, ale u takových dem jsem hrozný, když do toho jdu a věci logicky přejmenovávám):
použitím System;použitím System.Collections.Generic;použitím System.Text;použitím System.Configuration;použitím System.Data;použitím System.Data.SqlClient; jmenný prostor AEDemo{ class Program { static void Main(string[] args) { using (SqlConnection con1 =new SqlConnection()) { Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.ffffffff")); název řetězce; string EmptyString =""; int plat; int i =1; while (i <=100000) { con1.ConnectionString =ConfigurationManager.ConnectionStrings["Utility"].ToString(); using (SqlCommand cmd1 =new SqlCommand("dbo.GenerateNameAndSalary", con1)) { cmd1.CommandType =CommandType.StoredProcedure; SqlParameter n =new SqlParameter("@Name", SqlDbType.NVarChar, 32) { Direction =ParameterDirection.Output }; SqlParameter s =new SqlParameter("@Salary", SqlDbType.Int) { Direction =ParameterDirection.Output }; cmd1.Parametry.Add(n); cmd1.Parameters.Add(s); con1.Open(); cmd1.ExecuteNonQuery(); name =n.Value.ToString(); plat =Convert.ToInt32(s.Value); con1.Close(); } using (SqlConnection con2 =new SqlConnection()) { con2.ConnectionString =ConfigurationManager.ConnectionStrings[args[0]].ToString(); using (SqlCommand cmd2 =new SqlCommand("dbo.AddPerson", con2)) { cmd2.CommandType =CommandType.StoredProcedure; SqlParameter n =new SqlParameter("@LastName", SqlDbType.NVarChar, 32); SqlParameter s =new SqlParameter("@Salary", SqlDbType.Int); n.Value =jméno; s.Value =plat; cmd2.Parametry.Add(n); cmd2.Parameters.Add(s); con2.Open(); cmd2.ExecuteNonQuery(); con2.Close(); } } i++; } Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.ffffff")); i =1; while (i <=1000) { using (SqlConnection con3 =new SqlConnection()) { con3.ConnectionString =ConfigurationManager.ConnectionStrings[args[0]].ToString(); using (SqlCommand cmd3 =new SqlCommand("dbo.RetrievePeople", con3)) { cmd3.CommandType =CommandType.StoredProcedure; con3.Open(); SqlDataReader rdr =cmd3.ExecuteReader(); while (rdr.Read()) { EmptyString +=rdr[0].ToString(); } con3.Close(); } } i++; } Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.ffffff")); } } }}Potom můžeme zavolat .exe pomocí následujících příkazových řádků:
AEDemoConsole.exe "Normální"AEDemoConsole.exe "Šifrovat"A pro každé volání vytvoří tři řádky výstupu:čas zahájení, čas po vložení 100 000 řádků a čas po 1000 přečtení 100 řádků. Zde byly výsledky, které jsem viděl na svém systému, v průměru za 5 spuštění:
Trvání (v sekundách) zápisu a čtení dat
Zapisování dat má jasný dopad – ne úplně 2X, ale více než 1,5X. Při čtení a dešifrování dat byl mnohem nižší rozdíl – alespoň v těchto testech –, ale ani to nebylo zadarmo.
Pokud jde o využití prostoru, existuje zhruba 3násobný trest za ukládání šifrovaných dat (vzhledem k povaze většiny šifrovacích algoritmů by to nemělo být šokující). Mějte na paměti, že to bylo na tabulce pouze s jedním seskupeným primárním klíčem. Zde jsou čísla:
Prostor (MB) používaný k ukládání dat
Je tedy zřejmé, že používání Always Encrypted má určité sankce, jako obvykle téměř u všech řešení souvisejících s bezpečností (napadá mě rčení „žádný oběd zdarma“). Budu opakovat, že tyto testy byly provedeny proti CTP 2.2, což se může radikálně lišit od finálního vydání SQL Serveru 2016. Tyto rozdíly, které jsem pozoroval, také mohou odrážet pouze povahu testů, které jsem vymyslel; samozřejmě doufám, že tento přístup můžete použít k otestování svých výsledků proti vašemu schématu, na vašem hardwaru a s vašimi vzory přístupu k datům.