Sebagai bagian dari T-SQL Tuesday #69, saya telah membuat blog tentang batasan Always Encrypted, dan saya menyebutkan di sana bahwa kinerja dapat dipengaruhi secara negatif oleh penggunaannya (seperti yang Anda duga, keamanan yang lebih kuat sering kali memiliki trade-off). Dalam posting ini, saya ingin melihat sekilas tentang ini, dengan mengingat (sekali lagi) bahwa hasil ini didasarkan pada kode CTP 2.2, jadi sangat awal dalam siklus pengembangan, dan tidak selalu mencerminkan kinerja yang akan Anda buat. lihat datang RTM.
Pertama, saya ingin menunjukkan bahwa Always Encrypted berfungsi dari aplikasi klien meskipun versi terbaru SQL Server 2016 tidak diinstal di sana. Namun, Anda harus menginstal pratinjau .NET Framework 4.6 (versi terbaru di sini, dan itu mungkin berubah) untuk mendukung Column Encryption Setting
atribut string koneksi. Jika Anda menjalankan Windows 10, atau telah menginstal Visual Studio 2015, langkah ini tidak diperlukan, karena Anda seharusnya sudah memiliki versi .NET Framework yang cukup baru.
Selanjutnya, Anda perlu memastikan sertifikat Selalu Dienkripsi ada di semua klien. Anda membuat kunci enkripsi master dan kolom dalam database, seperti yang akan ditunjukkan oleh tutorial Selalu Terenkripsi, maka Anda perlu mengekspor sertifikat dari mesin itu, dan mengimpornya ke mesin lain tempat kode aplikasi akan dijalankan. Buka certmgr.msc
, dan perluas Sertifikat – Pengguna Saat Ini> Pribadi> Sertifikat, dan seharusnya ada yang bernama Always Encrypted Certificate
. Klik kanan itu, pilih Semua Tugas> Ekspor, dan ikuti petunjuknya. Saya mengekspor kunci pribadi dan memberikan kata sandi, yang menghasilkan file .pfx. Kemudian Anda hanya mengulangi proses sebaliknya pada mesin klien:Buka certmgr.msc
, perluas Sertifikat – Pengguna Saat Ini> Pribadi, klik kanan Sertifikat, pilih Semua Tugas> Impor, dan arahkan ke file .pfx yang Anda buat di atas. (Bantuan resmi di sini.)
(Ada cara yang lebih aman untuk mengelola sertifikat ini – sepertinya Anda tidak hanya ingin menyebarkan sertifikat seperti ini ke semua mesin, karena Anda akan segera bertanya pada diri sendiri apa gunanya? Saya hanya melakukan ini di lingkungan saya yang terisolasi untuk keperluan demo ini – saya ingin memastikan aplikasi saya mengambil data melalui kabel dan tidak hanya di memori lokal.)
Kami membuat dua database, satu dengan tabel terenkripsi, dan satu tanpa. Kami melakukan ini untuk mengisolasi string koneksi dan juga untuk mengukur penggunaan ruang. Tentu saja, ada cara yang lebih terperinci untuk mengontrol perintah mana yang perlu menggunakan koneksi berenkripsi – lihat catatan berjudul "Mengontrol dampak kinerja..." dalam artikel ini.
Tabelnya terlihat seperti ini:
-- encrypted copy, in database Encrypted CREATE TABLE dbo.Employees ( ID INT IDENTITY(1,1) PRIMARY KEY, LastName NVARCHAR(32) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = ColumnKey) NOT NULL, Salary INT ENCRYPTED WITH (ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = ColumnKey) NOT NULL ); -- unencrypted copy, in database Normal CREATE TABLE dbo.Employees ( ID INT IDENTITY(1,1) PRIMARY KEY, LastName NVARCHAR(32) COLLATE Latin1_General_BIN2 NOT NULL, Salary INT NOT NULL );
Dengan adanya tabel ini, saya ingin menyiapkan aplikasi baris perintah yang sangat sederhana untuk melakukan tugas berikut terhadap versi tabel terenkripsi dan tidak terenkripsi:
- Masukkan 100.000 karyawan, satu per satu
- Baca 100 baris acak, 1.000 kali
- Stempel waktu keluaran sebelum dan sesudah setiap langkah
Jadi kami memiliki prosedur tersimpan dalam database yang benar-benar terpisah yang digunakan untuk menghasilkan bilangan bulat acak untuk mewakili gaji, dan string Unicode acak dengan panjang yang bervariasi. Kami akan melakukan ini satu per satu untuk lebih mensimulasikan penggunaan nyata 100.000 sisipan yang terjadi secara independen (meskipun tidak secara bersamaan, karena saya tidak cukup berani untuk mencoba mengembangkan dan mengelola aplikasi C# multi-utas dengan benar, atau mencoba mengoordinasikan dan menyinkronkan beberapa instance dari satu aplikasi).
CREATE DATABASE Utility; GO USE Utility; GO CREATE PROCEDURE dbo.GenerateNameAndSalary @Name NVARCHAR(32) OUTPUT, @Salary INT OUTPUT AS BEGIN SET NOCOUNT ON; SELECT @Name = LEFT(CONVERT(NVARCHAR(32), CRYPT_GEN_RANDOM(64)), RAND() * 32 + 1); SELECT @Salary = CONVERT(INT, RAND()*100000)/100*100; END GO
Beberapa baris output sampel (kami tidak peduli dengan konten string yang sebenarnya, hanya saja bervariasi):
酹2ዌ륒㦢㮧羮怰㉤盿⚉嗝䬴敏⽁캘♜鼹䓧 98600 贓峂쌄탠❼缉腱蛽☎뱶 72000
Kemudian prosedur tersimpan yang pada akhirnya akan dipanggil aplikasi (ini identik di kedua database, karena kueri Anda tidak perlu diubah untuk mendukung Always Encrypted):
CREATE PROCEDURE dbo.AddPerson @LastName NVARCHAR(32), @Salary INT AS BEGIN SET NOCOUNT ON; INSERT dbo.Employees(LastName, Salary) SELECT @LastName, @Salary; END GO CREATE PROCEDURE dbo.RetrievePeople AS BEGIN SET NOCOUNT ON; SELECT TOP (100) ID, LastName, Salary FROM dbo.Employees ORDER BY NEWID(); END GO
Sekarang, kode C#, dimulai dengan bagian connectionStrings dari App.config. Bagian yang penting adalah Column Encryption Setting
opsi hanya untuk database dengan kolom terenkripsi (untuk singkatnya, asumsikan ketiga string koneksi berisi Data Source
yang sama , dan otentikasi SQL yang sama User ID
dan Password
):
<connectionStrings> <add name="Utility" connectionString="Initial Catalog=Utility;..."/> <add name="Normal" connectionString="Initial Catalog=Normal;..."/> <add name="Encrypt" connectionString="Initial Catalog=Encrypted; Column Encryption Setting=Enabled;..."/> </connectionStrings>
Dan Program.cs (maaf, untuk demo seperti ini, saya tidak bisa masuk dan mengganti nama secara logis):
using System; using System.Collections.Generic; using System.Text; using System.Configuration; using System.Data; using System.Data.SqlClient; namespace AEDemo { class Program { static void Main(string[] args) { using (SqlConnection con1 = new SqlConnection()) { Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff")); string name; string EmptyString = ""; int salary; 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.Parameters.Add(n); cmd1.Parameters.Add(s); con1.Open(); cmd1.ExecuteNonQuery(); name = n.Value.ToString(); salary = 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 = name; s.Value = salary; cmd2.Parameters.Add(n); cmd2.Parameters.Add(s); con2.Open(); cmd2.ExecuteNonQuery(); con2.Close(); } } i++; } Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff")); 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.fffffff")); } } } }
Kemudian kita dapat memanggil .exe dengan baris perintah berikut:
AEDemoConsole.exe "Normal" AEDemoConsole.exe "Encrypt"
Dan itu akan menghasilkan tiga baris output untuk setiap panggilan:waktu mulai, waktu setelah 100.000 baris dimasukkan, dan waktu setelah 100 baris dibaca 1.000 kali. Berikut adalah hasil yang saya lihat di sistem saya, rata-rata lebih dari 5 kali berjalan:
Durasi (detik) penulisan dan pembacaan data
Ada dampak yang jelas untuk menulis data – tidak cukup 2X, tetapi lebih dari 1,5X. Ada delta yang jauh lebih rendah dalam membaca dan mendekripsi data – setidaknya dalam pengujian ini – tetapi itu juga tidak gratis.
Sejauh penggunaan ruang, kira-kira ada hukuman 3X untuk menyimpan data terenkripsi (mengingat sifat sebagian besar algoritma enkripsi, ini seharusnya tidak mengejutkan). Perlu diingat ini ada di atas meja dengan hanya satu kunci utama berkerumun. Berikut angka-angkanya:
Spasi (MB) digunakan untuk menyimpan data
Jadi jelas ada beberapa hukuman dengan menggunakan Selalu Terenkripsi, karena biasanya ada hampir semua solusi terkait keamanan (pepatah "tidak ada makan siang gratis" muncul di benak). Saya akan ulangi bahwa tes ini dilakukan terhadap CTP 2.2, yang mungkin sangat berbeda dari rilis final SQL Server 2016. Selain itu, perbedaan yang saya amati ini mungkin hanya mencerminkan sifat tes yang saya buat; jelas saya berharap Anda dapat menggunakan pendekatan ini untuk menguji hasil Anda terhadap skema Anda, pada perangkat keras Anda, dan dengan pola akses data Anda.