Som en del av T-SQL Tuesday #69 har jag bloggat om begränsningarna för Always Encrypted, och jag nämnde där att prestandan kan påverkas negativt av dess användning (som du kan förvänta dig, starkare säkerhet har ofta avvägningar). I det här inlägget ville jag ta en snabb titt på detta, med tanke på (igen) att dessa resultat är baserade på CTP 2.2-kod, så mycket tidigt i utvecklingscykeln, och inte nödvändigtvis reflekterar prestandan du kommer att få. se kom RTM.
Först ville jag visa att Always Encrypted fungerar från klientapplikationer även om den senaste versionen av SQL Server 2016 inte är installerad där. Du måste dock installera .NET Framework 4.6-förhandsgranskningen (senaste versionen här, och det kan komma att ändras) för att stödja Column Encryption Setting
anslutningssträngsattribut. Om du kör Windows 10, eller har installerat Visual Studio 2015, är detta steg inte nödvändigt, eftersom du redan borde ha en tillräckligt ny version av .NET Framework.
Därefter måste du se till att det alltid krypterade certifikatet finns på alla klienter. Du skapar huvud- och kolumnkrypteringsnycklarna i databasen, som alla alltid krypterade tutorials visar dig, sedan måste du exportera certifikatet från den maskinen och importera det till de andra där applikationskoden kommer att köras. Öppna certmgr.msc
, och expandera Certifikat – Current User> Personal> Certifikat, och det borde finnas ett där som heter Always Encrypted Certificate
. Högerklicka på det, välj Alla uppgifter> Exportera och följ anvisningarna. Jag exporterade den privata nyckeln och angav ett lösenord, vilket gav en .pfx-fil. Sedan upprepar du bara den motsatta processen på klientdatorerna:Öppna certmgr.msc
, expandera Certifikat – Aktuell användare> Personligt, högerklicka på Certifikat, välj Alla uppgifter> Importera och peka på .pfx-filen som du skapade ovan. (Officiell hjälp här.)
(Det finns säkrare sätt att hantera dessa certifikat – det är inte troligt att du bara vill distribuera certifikatet så här till alla maskiner, eftersom du snart kommer att fråga dig själv vad som var poängen? Jag gjorde bara detta i min isolerade miljö för denna demo – jag ville se till att min applikation hämtade data över tråden och inte bara i lokalt minne.)
Vi skapar två databaser, en med krypterad tabell och en utan. Vi gör detta för att isolera anslutningssträngar och även för att mäta utrymmesanvändning. Naturligtvis finns det mer detaljerade sätt att kontrollera vilka kommandon som behöver använda en krypteringsaktiverad anslutning – se anteckningen med titeln "Kontrollera prestandapåverkan ..." i den här artikeln.
Tabellerna ser ut så här:
-- krypterad kopia, i databasen Krypterad CREATE TABLE dbo.Employees( ID INT IDENTITY(1,1) PRIMARY KEY, Efternamn NVARCHAR(32) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE =DETERMINISTIC_6KEYNISTIC_2KEYN_CRYPTION_TYPE =DETERMINISTIC_6CHMACCEN_2CHMACCEN_2AEGOADRINISTIC, '5GOADRITHAM_2C_2001 ColumnKey) NOT NULL, Lön INT KRYPTAD MED (ENCRYPTION_TYPE =RANDOMIZED, ALGORITHM ='AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY =ColumnKey) INTE NULL); -- okrypterad kopia, i databasen Normal CREATE TABLE dbo.Employees( ID INT IDENTITY(1,1) PRIMARY KEY, LastName NVARCHAR(32) COLLATE Latin1_General_BIN2 NOT NULL, Lön INT NOT NULL);
Med dessa tabeller på plats ville jag ställa in ett mycket enkelt kommandoradsprogram för att utföra följande uppgifter mot både de krypterade och okrypterade versionerna av tabellen:
- Infoga 100 000 anställda, en i taget
- Läs igenom 100 slumpmässiga rader, 1 000 gånger
- Utmatade tidsstämplar före och efter varje steg
Så vi har en lagrad procedur i en helt separat databas som används för att producera slumpmässiga heltal för att representera löner, och slumpmässiga Unicode-strängar av varierande längd. Vi kommer att göra detta en i taget för att bättre simulera verklig användning av 100 000 inlägg som sker oberoende (men inte samtidigt, eftersom jag inte är modig nog att försöka utveckla och hantera en flertrådig C#-applikation, eller försöka koordinera och synkronisera flera instanser av en enda applikation).
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 @Salary =CONVERT(INT, RAND()*100000)/100*100;ENDGO
Ett par rader med exempelutdata (vi bryr oss inte om det faktiska innehållet i strängen, bara att det varierar):
酹2“.Sedan kommer de lagrade procedurerna som applikationen till slut anropar (dessa är identiska i båda databaserna, eftersom dina frågor inte behöver ändras för att stödja Always Encrypted):
SKAPA PROCEDUR dbo.AddPerson @Efternamn NVARCHAR(32), @Salary INTASBEGIN SET NOCOUNT ON; INFOGA dbo.Anställda(Efternamn, Lön) VÄLJ @Efternamn, @Lön;AVSL VÄLJ TOP (100) ID, Efternamn, Lön FRÅN dbo.Anställda BESTÄLLS AV NEWID();ENDGONu, C#-koden, som börjar med connectionStrings-delen av App.config. Den viktiga delen är
Column Encryption Setting
alternativet för endast databasen med de krypterade kolumnerna (för korthetens skull, anta att alla tre anslutningssträngarna innehåller sammaData Source
, och samma SQL-autentiseringUser ID
ochPassword
):Och Program.cs (förlåt, för sådana här demos är jag dålig på att gå in och byta namn på saker logiskt):
använda System;använda System.Collections.Generic;använda System.Text;använda System.Configuration;använda System.Data;använda System.Data.SqlClient; namnutrymme AEDemo{ class Program { static void Main(string[] args) { using (SqlConnection con1 =new SqlConnection()) { Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff")); strängnamn; string EmptyString =""; int lön; int i =1; while (i <=100000) { con1.ConnectionString =ConfigurationManager.ConnectionStrings["Utility"].ToString(); använder (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.Parameters.Add(n); cmd1.Parameters.Add(s); con1.Open(); cmd1.ExecuteNonQuery(); name =n.Value.ToString(); lön =Convert.ToInt32(s.Value); con1.Close(); } med (SqlConnection con2 =new SqlConnection()) { con2.ConnectionString =ConfigurationManager.ConnectionStrings[args[0]].ToString(); använder (SqlCommand cmd2 =new SqlCommand("dbo.AddPerson", con2)) { cmd2.CommandType =CommandType.StoredProcedure; SqlParameter n =new SqlParameter("@Efternamn", SqlDbType.NVarChar, 32); SqlParameter s =new SqlParameter("@Salary", SqlDbType.Int); n.Värde =namn; s.Värde =lön; cmd2.Parameters.Add(n); cmd2.Parameters.Add(s); con2.Open(); cmd2.ExecuteNonQuery(); con2.Close(); } } i++; } Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.ffffffff")); i =1; while (i <=1000) { using (SqlConnection con3 =new SqlConnection()) { con3.ConnectionString =ConfigurationManager.ConnectionStrings[args[0]].ToString(); använder (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.ffffffff")); } } }}Sedan kan vi anropa .exe med följande kommandorader:
AEDemoConsole.exe "Normal" AEDemoConsole.exe "Kryptera"Och det kommer att producera tre rader av utdata för varje samtal:starttiden, tiden efter att 100 000 rader infogats och tiden efter att 100 rader lästes 1 000 gånger. Här var resultaten jag såg på mitt system, i genomsnitt över 5 körningar vardera:
Längd (sekunder) för att skriva och läsa data
Det finns en tydlig inverkan på att skriva data – inte riktigt 2X, men mer än 1,5X. Det var ett mycket lägre delta vid läsning och dekryptering av data – åtminstone i dessa tester – men det var inte heller gratis.
När det gäller utrymmesanvändning finns det ungefär 3X straff för lagring av krypterad data (med tanke på karaktären hos de flesta krypteringsalgoritmer borde detta inte vara chockerande). Tänk på att detta var på en tabell med bara en enda klustrad primärnyckel. Här var siffrorna:
Utrymme (MB) som används för att lagra data
Så uppenbarligen finns det vissa påföljder med att använda Alltid krypterad, som det vanligtvis är med nästan alla säkerhetsrelaterade lösningar (ordspråket "ingen gratis lunch" kommer att tänka på). Jag ska upprepa att dessa tester utfördes mot CTP 2.2, vilket kan vara radikalt annorlunda än den slutliga versionen av SQL Server 2016. Dessa skillnader som jag har observerat kan också återspegla karaktären hos testerna jag skapade; självklart hoppas jag att du kan använda detta tillvägagångssätt för att testa dina resultat mot ditt schema, på din hårdvara och med dina dataåtkomstmönster.