Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

SQL Server 2016 :Dampak Kinerja Selalu Terenkripsi

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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara Menggunakan OBJECT_ID() pada Objek Lintas Database di SQL Server

  2. Impor Beberapa File CSV ke SQL Server dari Folder

  3. Cara mencari string di database SQL Server

  4. Cara Memformat Angka Sebagai Mata Uang di SQL Server (T-SQL)

  5. SQL Server:Dapatkan kunci utama tabel menggunakan kueri sql