加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > MsSql教程 > 正文

MSSQL-很棒的实践-Always Encrypted

发布时间:2023-03-13 11:16:59 所属栏目:MsSql教程 来源:
导读:我们首先创建了测试数据库AlwaysEncrypted。

--Step 1 - Create MSsql sample database
USE master
GO
IF DB_ID('AlwaysEncrypted') IS NULL
CREATE DATABASE [AlwaysEncrypted];
GO
-- Not 10
我们首先创建了测试数据库AlwaysEncrypted。

--Step 1 - Create MSsql sample database
USE master
GO
IF DB_ID('AlwaysEncrypted') IS NULL
    CREATE DATABASE [AlwaysEncrypted];
GO
-- Not 100% require, but option adviced.
ALTER DATABASE [AlwaysEncrypted] COLLATE latin1_General_BIN2;
创建列主密钥
其次,在AlwaysEncrypted数据库中,我们创建列主密钥(Column Master Key,简写为CMK)。

-- Step 2 - Create a column master key
USE [AlwaysEncrypted]
GO
CREATE COLUMN MASTER KEY [AE_ColumnMasterKey]
WITH
(
    KEY_STORE_PROVIDER_NAME = N'MSsql_CERTIFICATE_STORE',
    KEY_PATH = N'CurrentUser/My/C3C1AFCDA7F2486A9BBB16232A052A6A1431ACB0'
)
GO
创建列加密密钥
然后,我们创建列加密密钥(Column Encryption Key,简写为CEK)。

检查CMK和CEK
接下来,我们检查下刚才创建的列主密钥和列加密密钥,方法如下:

-- Step 4 - CMK & CEK Checking
select * from sys.column_master_keys
select * from sys.column_encryption_keys
select * from sys.column_encryption_key_values

当然,您也可以使用SSMS的IDE来查看Column Master Key和Column Encryption Key,方法是:
展开需要检查的数据库 -> Security -> Always Encrypted Keys -> 展开Column Master Keys和 Column Encryption Keys。如下图所示:

创建Always Encryped测试表
下一步,我们创建Always Encrypted测试表,代码如下:

-- Step 5 -  Create a table with an encrypted column
USE [AlwaysEncrypted]
GO
IF OBJECT_ID('dbo.CustomerInfo', 'U') IS NOT NULL
    DROP TABLE dbo.CustomerInfo
GO
CREATE TABLE dbo.CustomerInfo
(
CustomerId        INT IDENTITY(10000,1)    NOT NULL PRIMARY KEY,
CustomerName    NVARCHAR(100) COLLATE latin1_General_BIN2 
    ENCRYPTED WITH (
        ENCRYPTION_TYPE = DETERMINISTIC, 
        ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', 
        COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey
    ) NOT NULL,
CustomerPhone    NVARCHAR()  COLLATE latin1_General_BIN2
    ENCRYPTED WITH (
    ENCRYPTION_TYPE= RANDOMIZED, 
    ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', 
    COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey
    ) NOT NULL
 )
;
GO
在创建Always Encrypted测试表过程中,对于加密字段,我们指定了:
 加密类型:DETERMINISTIC和RANDOMIZED。
 算法:AEAD_AES_256_CBC_HMAC_SHA_256是Always Encrypted专有算法。
 加密密钥:创建的加密密钥名字。

导出服务器端证书
最后,我们将服务端的证书导出成文件,方法如下:
Control Panel –> Internet Options -> Content -> Certificates -> Export。

导出向导中输入私钥保护密码。

选择存放路径。

最后导出成功。

应用程序端测试
sql Server服务端配置完毕后,我们需要在测试应用程序端导入证书,然后测试应用程序。

客户端导入证书
客户端导入证书方法与服务端证书导出方法入口是一致的,方法是:Control Panel –> Internet Options -> Content -> Certificates -> Import。如下截图所示:

然后输入私钥文件加密密码,导入成功。

测试应用程序
我们使用VS创建一个C#的Console Application做为测试应用程序,使用NuGet Package功能安装Dapper,做为我们sql Server数据库操作的工具。
注意:仅.NET 4.6及以上版本支持Always Encrypted特性的sql Server driver,因此,请确保您的项目Target framework至少是.NET 4.6版本,方法如下:右键点击您的项目 -> Properties -> 在Application中,切换你的Target framework为.NET Framework 4.6。

为了简单方便,我们直接在sql Server服务端测试应用程序,因此您看到的连接字符串是连接本地sql Server服务。如果您需要测试远程sql Server,修改连接字符串即可。整个测试应用程序代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using System.Data;
using System.Data.sqlClient;
namespace AlwaysEncryptedExample
{
    public class AlwaysEncrypted
    {
        public static readonly string CONN_STRING = "Column Encryption Setting = Enabled;Server=.,1433;Initial Catalog=AlwaysEncrypted;Trusted_Connection=Yes;MultipleActiveResultSets=True;";
        public static void Main(string[] args)
        {
            List<Customer> Customers = QueryCustomerList<Customer>(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)");
            // there is no record
            if(Customers.Count == 0)
            {
                Console.WriteLine("************There is no record.************");
                string execsql = @"INSERT INTO dbo.CustomerInfo VALUES (@customerName, @cellPhone);";
                Console.WriteLine("************Insert some records.************");
                DynamicParameters dp = new DynamicParameters();
                dp.Add("@customerName", "CustomerA", dbType: DbType.String, direction: ParameterDirection.Input, size: 100);
                dp.Add("@cellPhone", "13402871524", dbType: DbType.String, direction: ParameterDirection.Input, size: 11);
                DoExecutesql(execsql, dp);
                Console.WriteLine("************re-generate records.************");
                Customers = QueryCustomerList< Customer>(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)");
            }
            else
            {
                Console.WriteLine("************There are a couple of records.************");
            }
            foreach(Customer cus in Customers)
            {
                Console.WriteLine(string.Format("Customer name is {0} and cell phone is {1}.", cus.CustomerName, cus.CustomerPhone));
            }
            Console.ReadKey();
        }
        public static List<T> QueryCustomerList<T>(string queryText)
        {
            // input variable checking
            if (queryText == null || queryText == "")
            {
                return new List<T>();
            }
            try
            {
                using (IDbConnection dbConn = new sqlConnection(CONN_STRING))
                {
                    // if connection is closed, open it
                    if (dbConn.State == ConnectionState.Closed)
                    {
                        dbConn.open();
                    }
                    // return the query result data set to list.
                    return dbConn.Query<T>(queryText, commandTimeout: 120).ToList();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", queryText, ex.Message, ex.StackTrace);
                // return empty list
                return new List<T>();
            }
        }
        public static bool DoExecutesql(String execsql, object parms)
        {
            bool rt = false;
            // input parameters checking
            if (string.IsNullOrEmpty(execsql))
            {
                return rt;
            }
            if (!string.IsNullOrEmpty(CONN_STRING))
            {
                // try to add event file target
                try
                {
                    using (IDbConnection dbConn = new sqlConnection(CONN_STRING))
                    {
                        // if connection is closed, open it
                        if (dbConn.State == ConnectionState.Closed)
                        {
                            dbConn.open();
                        }
                        var affectedRows = dbConn.Execute(execsql, parms);
                        rt = (affectedRows > 0);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", execsql, ex.Message, ex.StackTrace);
                }
            }
            return rt;
        }
        public class Customer
        {
            private int customerId;
            private string customerName;
            private string customerPhone;
            public Customer(int customerId, string customerName, string customerPhone)
            {
                this.customerId = customerId;
                this.customerName = customerName;
                this.customerPhone = customerPhone;
            }
            public int CustomerId
            {
                get
                {
                    return customerId;
                }
                set
                {
                    customerId = value;
                }
            }
            public string CustomerName
            {
                get
                {
                    return customerName;
                }
                set
                {
                    customerName = value;
                }
            }
            public string CustomerPhone
            {
                get
                {
                    return customerPhone;
                }
                set
                {
                    customerPhone = value;
                }
            }
        }
    }
}
我们在应用程序代码中,仅需要在连接字符串中添加Column Encryption Setting = Enabled;属性配置,即可支持sql Server 2016新特性Always Encrypted,非常简单。为了方便大家观察,我把这个属性配置放到了连接字符串的第一个位置,如下图所示:

从应用程序的测试结果来看,我们可以正常读、写Always Encrypted测试表,应用程序工作良好。那么,假如我们抛开应用程序使用其它方式能否读写该测试表,看到又是什么样的数据结果呢?

测试SSMS
假设,我们使用SSMS做为测试工具。首先读取Always Encrypted测试表中的数据:

-- try to read Always Encrypted table and it'll show us encrypted data instead of the plaintext.
USE [AlwaysEncrypted]
GO
SELECT * FROM dbo.CustomerInfo WITH(NOLOCK)

然后,使用SSMS直接往测试表中插入数据:

-- try to insert records to encrypted table, will be fail.
USE [AlwaysEncrypted]
GO 
INSERT INTO dbo.CustomerInfo 
VALUES ('CustomerA','13402872514'),('CustomerB','13880674722')
GO
会报告如下错误:

Msg 206, Level 16, State 2, Line 74
Operand type clash: varchar is incompatible with varchar(8000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'AE_ColumnEncryptionKey', column_encryption_key_database_name = 'AlwaysEncrypted') collation_name = 'Chinese_PRC_CI_AS'

由此可见,我们无法使用测试应用程序以外的方法读取和操作Always Encrypted表的明文数据。

 

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章