SQL Server Always Encrypted

过去SQL Server有多种加密数据的方式,如透明数据加密(TDE)。这种技术是在数据库文件或者备份被盗用时,保护静态数据。然而对于可以访问数据库本身,或者任何拥有数据库的用户,可以获取秘钥、证书和密码(系统管理员、黑客诸如此类),是没有效果的。

SQL Server 2016新引入了Always Encrypted 功能,其设计的目的即时保护敏感数据,如手机号、身份证、银行卡号等等,可以同时加密静态和动态数据(内存中的数据也会被加密)。因此,这可以保护数据免受流氓管理员、备份窃贼和中间人攻击。和TDE不同,即Always Encrypted 允许你仅仅加密某些列,而不是整个数据库。

客户端库确保纯文本只在应用程序或中间层中显示,而不出现在应用程序和数据库之间。下面的说明中,我试图表明,无论是在数据库中,还是在应用程序和数据库之间的两个方向上(数据写入数据库,和数据库数据输出),数据都是密文形式:

SQL Server Always Encrypted_第1张图片

这就带来了Always Encrypted的第一个限制:目前所有的客户端库都不支持它。事实上,目前,Always Encrypted 线程的提供者是ADO.NET 4.6,因此,你需要确保.NET Framework 4.6 被安装到任何需要和Always Encrypted 数据交互的客户端应用服务器上。

本文将介绍Always Encrypted的基础配置,给出一些示例,解释其所受限制。

SQL Server 2016 Always Encrypted概念

使用Always Encrypted 有几个核心概念:

  • 列主秘钥:这是用于保护列加密秘钥的秘钥。在加密任何列之前,你必须拥有至少一个列主秘钥。

  • 列加密秘钥:这个加密秘钥是实际上保护加密列的。

  • 列级别加密设置:必须将列设置为加密,并使用特定的列加密密钥、算法(目前只支持一种算法)和要使用的加密类型。

    • Deterministic:相同的字符串加密后为相同的密文,这可以用于特定的操作(如点查询,distinct查询,分组),可以创建索引

    • Randomized:更安全,但不能用于计量或任何操作(写/展示),并且不能创建索引

  • 连接字符串:为了使得客户端驱动能够理解正在使用列加密,连接字符串必须有下面的属性:


Column Encryption Setting = enabled;

应用代码本身,除连接字符串设置外,没有任何改变,因为不需要知道哪个列真正的加密过。

Always Encrypted 应用场景:

  • 数据库在组织内部,管理人员为组织外部人员

  • 数据库搭建在云上,如Azure数据库等

  • 数据库搭建在云上,管理人员亦是组织外部人员

Always Encrypted 示例

为了简化过程,我将在单个、本地服务器上展示示例。首先让我们创建一个数据库:

CREATE DATABASE AlwaysEncrypted
ON PRIMARY
(NAME='AlwaysEncrypted',FILENAME='D:\database\AlwaysEncrypted.mdf')
LOG ON
(NAME='AlwaysEncrypted_log',FILENAME='D:\database\AlwaysEncrypted_log.ldf')

创建列主秘钥

现在我将创建一个列主秘钥和列加密秘钥。在对象资源管理器中,展开数据库→安全→始终加密的秘钥。在那里你将看到两个节点,你可以右击第一个“列主秘钥”,创建列主秘钥。

SQL Server Always Encrypted_第2张图片

对话框中并没有给出太多的选项,给予一个名称,选择秘钥存储源。我选择当前用户。注意,你可以创建多个列主秘钥(用于支持秘钥轮换)。

SQL Server Always Encrypted_第3张图片

列主秘钥存储的位置有四种:

  1. 当前用户

  2. 本地计算机

  3. Azure 秘钥保管库

  4. 秘钥存储提供程序(CNG)

SQL Server Always Encrypted_第4张图片

注意:在上面创建过程中,要生成证书,该证书可供拥有证书的账户通过客户端查看Always Encrypted 解密后的数据(后期文章将会阐述)。

创建列加密秘钥

接着,创建列加密秘钥:

SQL Server Always Encrypted_第5张图片

类似的,这个对话框仅仅让你为列加密秘钥提供一个名称,并选择列主秘钥:

SQL Server Always Encrypted_第6张图片

在我的机器,可以生成如下创建脚本(但是请不要试图拷贝这些脚本在你自己的机器上运行):


USE [AlwaysEncrypted]
/****** Object:  ColumnMasterKey [ColumnMasterKey]    Script Date: 2020/4/10 17:09:09 ******/
CREATE COLUMN MASTER KEY [ColumnMasterKey]
WITH
(
       KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
       KEY_PATH = N'LocalMachine/My/52B3AFBC309633E9256AFCA35D0D33203AC9BD51'
)
GO
USE [AlwaysEncrypted]
CREATE COLUMN ENCRYPTION KEY [ColumnEncryptedKey]
WITH VALUES
(
       COLUMN_MASTER_KEY = [ColumnMasterKey],
       ALGORITHM = 'RSA_OAEP',
       ENCRYPTED_VALUE = 0x01700000016C006F00630061006C006D0061006300680069006E0065002F006D0079002F00350032006200330061006600620063003300300039003600330033006500390032003500360061006600630061003300350064003000640033003300320030003300610063003900620064003500310065F54E706262859512B9990D2FDA8CD0DA8B71120875AA1B096CECBCD14822B6E0931789DE37C670063E83F2067B57E6C7AFC8A9D5442DD3EA0EF4E2D22B606645682A3B638F2E1A8C4984188783FFCC5A8047CD87BF9709FFC5F191DED58DA5288F347D2EE70171BC4356111F76023C06E0D47291D7C9BADECAED976CBCA628029F80DAE0706D8FD3530F40BFE39D05B93AF01D930ED0665A2AD93DC65A8FDDAA9EC3E263FBCE20359F4A343E715CC43C81559DC60220C040B28BFC94A1CBB5EB5E000CAB43A65787F580DA73E34F03074BD8B8B30491742CCB7BE323A169D8120BB4045D3CD1407B4AD9F5B3ECBEB051D2270813E59A04C11CD524E9D79AFA07E350A1E0E67B6378EB7DECD66CF587D9BC7096E6EC61437D96F8B991D32F7FC07D85BADA7E79769A3BF1FBAF6525AD049EF99FD18EA3D9F9DA3BFA48E85F70596D203558517F17F0450C0D6C705980DF2D03430854C498FA9A2AF67FE866932EE8DE429C788CF2B3DDEC91AF9116A94D958F754F12562EAEDA2E6E1F8E1330C61CF8DC1BB99C07DA26D314D2A5BEFC9FF949FA1E02443CCA0FB78D2753CC7146DA37A04B838D2A6C8ED89960D561AC20E7D0055CBCDA6F157D0EF040F7608C2BDC92840B1EECFEDC95FE6B094E2E16DFB3702D3560697F77F5953ADB3BA07898DE5B81A48CBAB3E072387FE56299E472679D9A847846B8DAF2B38C18B05F39
)
GO

现在秘钥已经创建好,我们可以创建一个表来使用它们。假设我们有员工表,我们想对员工姓名和工资加密。

指定加密列的语法有点晦涩。如我前面提到的,仅仅只支持一个加密算法,引用有点拗口:AEAD_AES_256_CBC_HMAC_SHA_256。同时,任何字符型数据列决定使用DETERMINISTIC类型加密时,必须使用BIN2字符集。

CREATE TABLE Employee(
       ID INT IDENTITY(1,1) PRIMARY KEY
       ,Name NVARCHAR(10) COLLATE Chinese_PRC_BIN2
              ENCRYPTED WITH(
                     ENCRYPTION_TYPE=DETERMINISTIC
                     ,ALGORITHM='AEAD_AES_256_CBC_HMAC_SHA_256'
                     ,COLUMN_ENCRYPTION_KEY=ColumnEncryptedKey
              )NOT NULL
       ,Salary INT
              ENCRYPTED WITH(
                     ENCRYPTION_TYPE=RANDOMIZED
                     ,ALGORITHM='AEAD_AES_256_CBC_HMAC_SHA_256'
                     ,COLUMN_ENCRYPTION_KEY=ColumnEncryptedKey
              ) NOT NULL
);
GO

在客户端SSMS中执行新增员工信息语句:

INSERT dbo.Employee(Name,Salary) SELECT N'Jack',20000;

SQL Server Always Encrypted_第7张图片

消息 206,级别 16,状态 2,第 70 行

操作数类型冲突: nvarchar 与 nvarchar(4000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'ColumnEncryptedKey', column_encryption_key_database_name = 'AlwaysEncrypted') 不兼容

我修正临时ad hoc SQL中的数据类型,我得到了另外一个错误:

DECLARE @Name NVARCHAR(15)='Jack',@Salary int=20000
INSERT dbo.Employee(Name,Salary) SELECT @Name,@Salary;

SQL Server Always Encrypted_第8张图片

消息 33299,级别 16,状态 6,第 73 行

加密方案不匹配列/变量 '@Name'。列/变量的加密方案为 (encryption_type = 'PLAINTEXT'),行“2”附近的表达式预期其为 (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'ColumnEncryptedKey', column_encryption_key_database_name = 'AlwaysEncrypted') (或更弱)。

调用上面创建的增加会员的存储过程,我们得到上面相同的错误:

DECLARE @Name NVARCHAR(15)='Jack',@Salary int=20000
EXEC dbo.AddEmployee @Name,@Salary

创建新增员工信息、员工信息查询过程,以供程序调用:

/*
       新增员工信息
*/
CREATE  PROC dbo.AddEmployee
       @Name NVARCHAR(10)
       ,@Salary INT
AS
BEGIN
       INSERT INTO dbo.Employee(Name,Salary)
       SELECT @Name,@Salary
END
GO
/*
       根据员工姓名获得员工信息
*/
CREATE PROC dbo.GetEmployeeByName
       @Name NVARCHAR(10)
AS
BEGIN
       SELECT
              ID,Name,Salary
       FROM DBO.Employee
       WHERE Name=@Name COLLATE Chinese_PRC_BIN2
END
GO

VS开发测试

在Visual Studio的上,我将创建一个非常简单的Windows窗体应用程序,它将允许我填充和查询这个表。首先需要包括列加密设置属性的连接字符串,因此我的App.Config 有如下内容:


   

在我的窗体上添加两个文本框和两个按钮,允许我输入姓名和薪资,并向数据库表中插入一行数据;或者输入姓名,检索展示其工资。

SQL Server Always Encrypted_第9张图片

新增员工信息按钮的脚本:

private void button1_Click(object sender, EventArgs e)
        {
            using (SqlConnection con = new SqlConnection())
            {
                con.ConnectionString = ConfigurationManager.ConnectionStrings["AEDB"].ToString();
                con.Open();
                using (SqlCommand cmd = new SqlCommand("dbo.AddEmployee", con))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter Nm = new SqlParameter("@Name", SqlDbType.NVarChar, 10);
                    Nm.Value = textBox1.Text;
                    SqlParameter Sal = new SqlParameter("@Salary", SqlDbType.Int);
                    Sal.Value = Convert.ToInt32(textBox2.Text);
                    cmd.Parameters.Add(Nm);
                    cmd.Parameters.Add(Sal);
                    cmd.ExecuteNonQuery();
                    MessageBox.Show("新增员工成功。");
                    textBox1.Clear();textBox2.Clear();
                }
            }
        }

查询员工薪资的脚本:

private void button2_Click(object sender, EventArgs e)
        {
            using(SqlConnection con=new SqlConnection())
            {
                con.ConnectionString = ConfigurationManager.ConnectionStrings["AEDB"].ToString();
                con.Open();
                using(SqlCommand cmd =new SqlCommand("dbo.GetEmployeeByName", con))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter Nm = new SqlParameter("@Name", SqlDbType.NVarChar, 10);
                    Nm.Value = textBox1.Text;
                    cmd.Parameters.Add(Nm);
                    SqlDataReader rdr = cmd.ExecuteReader();
                    while (rdr.Read())
                    {
                        textBox2.Text = rdr["Salary"].ToString();
                    }
                }
            }
        }

它非常粗糙和简陋,但它完成了工作:当您输入姓名和薪水并按下“新增员工信息”按钮时,它将其添加到数据库中,然后清除表单。如果只输入姓名按下另一个按钮,它将用该人员的薪水填充salary字段。

如果我们开启Profiler,我们可以看到下面的读写过程,参数在到达SQL Server之前已经加密了。


exec sp_describe_parameter_encryption N'EXEC [dbo].[AddEmployee] @Name=@Name, @Salary=@Salary',N'@Name nvarchar(10),@Salary int'

SQL Server Always Encrypted_第10张图片

exec dbo.AddEmployee @Name=0x01BE14A713B21E6B357A0787010B45118E02D809C43CF75BA7E89EF00E08204709EA11B25936C51B97B6674B7846B9EFF0DF8EE3B66C97B798C3E2B6777D186ED4,@Salary=0x0118A0628238B637DCE8C041CCE05028743F5ADE119EED521AB75DF304CBEB5E43703A484FC3CA8E3DFC1D755A0160C32E9228C60671E9F912AF3981FDAAFBE52E

SQL Server Always Encrypted_第11张图片

查询员工薪资

exec dbo.GetEmployeeByName @Name=0x01BE14A713B21E6B357A0787010B45118E02D809C43CF75BA7E89EF00E08204709EA11B25936C51B97B6674B7846B9EFF0DF8EE3B66C97B798C3E2B6777D186ED4

我们再回过头来查看数据库中的数据存储情况:

SQL Server Always Encrypted_第12张图片

我们可以看到姓名列和薪资列都已经加密了。

 

你可能感兴趣的:(SQL,Server,安全管理,SQL,Server,可用性,SQL,Server,数据库,AlwaysEncrypted,列主秘钥,列加密秘钥,C#)