摘要

在SQL Server安全系列专题月报分享中,往期我们已经陆续分享了:如何使用对称密钥实现SQL Server列加密技术、使用非对称密钥实现SQL Server列加密、使用混合密钥实现SQL Server列加密技术、列加密技术带来的查询性能问题以及相应解决方案、行级别安全解决方案、SQL Server 2016 dynamic data masking实现隐私数据列打码技术和使用证书做数据库备份加密这七篇文章,直接点击以上文章前往查看详情。本期月报我们分享SQL Server 2016新特性Always Encrypted技术。

问题引入

在云计算大行其道的如今,有没有一种方法保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库中数据的绝对安全呢?答案是肯定的,就是我们今天将要谈到的SQL Server 2016引入的始终加密技术(Always Encrypted)。
使用SQL Server Always Encrypted,始终保持数据处于加密状态,只有调用SQL Server的应用才能读写和操作加密数据,如此您可以避免数据库或者操作系统管理员接触到客户应用程序敏感数据。SQL Server 2016 Always Encrypted通过验证加密密钥来实现了对客户端应用的控制,该加密密钥永远不会通过网络传递给远程的SQL Server服务端。因此,最大限度保证了云数据库客户数据安全,即使是云服务提供商也无法准确获知用户数据明文。

具体实现

SQL Server 2016引入的新特性Always Encrypted让用户数据在应用端加密、解密,因此在云端始终处于加密状态存储和读写,最大限制保证用户数据安全,彻底解决客户对云服务提供商的信任问题。以下是SQL Server 2016 Always Encrypted技术的详细实现步骤。

创建测试数据库

为了测试方便,我们首先创建了测试数据库AlwaysEncrypted。

  1. --Step 1 - Create MSSQL sample database
  2. USE master
  3. GO
  4. IF DB_ID('AlwaysEncrypted') IS NULL
  5. CREATE DATABASE [AlwaysEncrypted];
  6. GO
  7. -- Not 100% require, but option adviced.
  8. ALTER DATABASE [AlwaysEncrypted] COLLATE Latin1_General_BIN2;

创建列主密钥

其次,在AlwaysEncrypted数据库中,我们创建列主密钥(Column Master Key,简写为CMK)。

  1. -- Step 2 - Create a column master key
  2. USE [AlwaysEncrypted]
  3. GO
  4. CREATE COLUMN MASTER KEY [AE_ColumnMasterKey]
  5. WITH
  6. (
  7. KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
  8. KEY_PATH = N'CurrentUser/My/C3C1AFCDA7F2486A9BBB16232A052A6A1431ACB0'
  9. )
  10. GO

创建列加密密钥

然后,我们创建列加密密钥(Column Encryption Key,简写为CEK)。

检查CMK和CEK

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

  1. -- Step 4 - CMK & CEK Checking
  2. select * from sys.column_master_keys
  3. select * from sys.column_encryption_keys
  4. 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测试表,代码如下:

  1. -- Step 5 - Create a table with an encrypted column
  2. USE [AlwaysEncrypted]
  3. GO
  4. IF OBJECT_ID('dbo.CustomerInfo', 'U') IS NOT NULL
  5. DROP TABLE dbo.CustomerInfo
  6. GO
  7. CREATE TABLE dbo.CustomerInfo
  8. (
  9. CustomerId INT IDENTITY(10000,1) NOT NULL PRIMARY KEY,
  10. CustomerName NVARCHAR(100) COLLATE Latin1_General_BIN2
  11. ENCRYPTED WITH (
  12. ENCRYPTION_TYPE = DETERMINISTIC,
  13. ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
  14. COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey
  15. ) NOT NULL,
  16. CustomerPhone NVARCHAR(www.baihuiyulep.cn) COLLATE Latin1_General_BIN2
  17. ENCRYPTED WITH (
  18. ENCRYPTION_TYPE www.tianscpt.com= RANDOMIZED,
  19. ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
  20. COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey
  21. ) NOT NULL
  22. )
  23. ;
  24. 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,修改连接字符串即可。整个测试应用程序代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Dapper;
  7. using System.Data;
  8. using System.Data.SqlClient;
  9. namespace AlwaysEncryptedExample
  10. {
  11. public class AlwaysEncrypted
  12. {
  13. public static readonly string CONN_STRING = "Column Encryption Setting = Enabled;Server=.,1433;Initial Catalog=AlwaysEncrypted;Trusted_Connection=Yes;MultipleActiveResultSets=True;";
  14. public static void Main(string[www.tscdeLu.cn] args)
  15. {
  16. List<Customer> Customers = QueryCustomerList<Customer>(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)");
  17. // there is no record
  18. if(Customers.Count == 0)
  19. {
  20. Console.WriteLine("************There is no record.************");
  21. string execSql = @"INSERT INTO dbo.CustomerInfo VALUES (@customerName, @cellPhone);";
  22. Console.WriteLine("************Insert some records.************");
  23. DynamicParameters dp = new DynamicParameters();
  24. dp.Add("@customerName",www.yunsengyule.com  "CustomerA", dbType: DbType.String, direction: ParameterDirection.Input, size: 100);
  25. dp.Add("@cellPhone", www.mmingyLgw.com"13402871524", dbType: DbType.String, direction: ParameterDirection.Input, size: 11);
  26. DoExecuteSql(execSql, dp);
  27. Console.WriteLine("************re-generate records.************");
  28. Customers = QueryCustomerList<www.yscylept.com Customer>(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)");
  29. }
  30. else
  31. {
  32. Console.WriteLine("************There are a couple of records.************");
  33. }
  34. foreach(Customer cus in Customers)
  35. {
  36. Console.WriteLine(string.Format("Customer name is {0} and cell phone is {1}.", cus.CustomerName, cus.CustomerPhone));
  37. }
  38. Console.ReadKey();
  39. }
  40. public static List<T> QueryCustomerList<T>(string queryText)
  41. {
  42. // input variable checking
  43. if (queryText == null || queryText == "")
  44. {
  45. return new List<T>();
  46. }
  47. try
  48. {
  49. using (IDbConnection dbConn = new SqlConnection(CONN_STRING))
  50. {
  51. // if connection is closed, open it
  52. if (dbConn.State == ConnectionState.Closed)
  53. {
  54. dbConn.Open();
  55. }
  56. // return the query result data set to list.
  57. return dbConn.Query<T>(queryText, commandTimeout: 120).ToList();
  58. }
  59. }
  60. catch (Exception ex)
  61. {
  62. Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", queryText, ex.Message, ex.StackTrace);
  63. // return empty list
  64. return new List<T>();
  65. }
  66. }
  67. public static bool DoExecuteSql(String execSql, object parms)
  68. {
  69. bool rt = false;
  70. // input parameters checking
  71. if (string.IsNullOrEmpty(execSql))
  72. {
  73. return rt;
  74. }
  75. if (!string.IsNullOrEmpty(CONN_STRING))
  76. {
  77. // try to add event file target
  78. try
  79. {
  80. using (IDbConnection dbConn = new SqlConnection(CONN_STRING))
  81. {
  82. // if connection is closed, open it
  83. if (dbConn.State == ConnectionState.Closed)
  84. {
  85. dbConn.Open();
  86. }
  87. var affectedRows = dbConn.Execute(execSql, parms);
  88. rt = (affectedRows > 0);
  89. }
  90. }
  91. catch (Exception ex)
  92. {
  93. Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", execSql, ex.Message, ex.StackTrace);
  94. }
  95. }
  96. return rt;
  97. }
  98. public class Customer
  99. {
  100. private int customerId;
  101. private string customerName;
  102. private string customerPhone;
  103. public Customer(int customerId, string customerName, string customerPhone)
  104. {
  105. this.customerId = customerId;
  106. this.customerName = customerName;
  107. this.customerPhone = customerPhone;
  108. }
  109. public int CustomerId
  110. {
  111. get
  112. {
  113. return customerId;
  114. }
  115. set
  116. {
  117. customerId = value;
  118. }
  119. }
  120. public string CustomerName
  121. {
  122. get
  123. {
  124. return customerName;
  125. }
  126. set
  127. {
  128. customerName = value;
  129. }
  130. }
  131. public string CustomerPhone
  132. {
  133. get
  134. {
  135. return customerPhone;
  136. }
  137. set
  138. {
  139. customerPhone = value;
  140. }
  141. }
  142. }
  143. }
  144. }

我们在应用程序代码中,仅需要在连接字符串中添加Column Encryption Setting = Enabled;属性配置,即可支持SQL Server 2016新特性Always Encrypted,非常简单。为了方便大家观察,我把这个属性配置放到了连接字符串的第一个位置,如下图所示:

运行我们的测试应用程序,展示结果如下图所示:

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

测试SSMS

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

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

展示结果如下截图:

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

  1. -- try to insert records to encrypted table, will be fail.
  2. USE [AlwaysEncrypted]
  3. GO
  4. INSERT INTO dbo.CustomerInfo
  5. VALUES ('CustomerA','13402872514'),('CustomerB','13880674722')
  6. GO

会报告如下错误:

  1. Msg 206, Level 16, State 2, Line 74
  2. 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表的明文数据。

测试结果分析

从应用程序读写测试和使用SSMS直接读写Always Encrypted表的测试结果来看,用户可以使用前者正常读写测试表,工作良好;而后者无法读取测试表明文,仅可查看测试表的加密后的密文数据,加之写入操作直接报错。

测试应用源代码

如果您需要本文的测试应用程序源代码,请点击下载。

最后总结

本期月报,我们分享了SQL Server 2016新特性Always Encrypted的原理及实现方法,以此来保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库的数据绝对安全,解决了云数据库场景中最重要的用户对云服务提供商信任问题。

MSSQL-最佳实践-Always Encrypted的更多相关文章

  1. MSSQL - 最佳实践 - 使用SSL加密连接

    MSSQL - 最佳实践 - 使用SSL加密连接 author: 风移 摘要 在SQL Server安全系列专题月报分享中,往期我们已经陆续分享了:如何使用对称密钥实现SQL Server列加密技术. ...

  2. MSSQL · 最佳实践 · 利用文件组实现冷热数据隔离备份方案

    文件组的基本知识点介绍完毕后,根据场景引入中的内容,我们将利用SQL Server文件组技术来实现冷热数据隔离备份的方案设计介绍如下. 设计分析 由于payment数据库过大,超过10TB,单次全量备 ...

  3. SQL Server系统数据库备份最佳实践

    原文:SQL Server系统数据库备份最佳实践 首先了解主要的系统数据库: 系统数据库 master 包含登录信息和其他数据库的核心信息 msdb 存储作业.操作员.警报.备份还原历史.数据库邮件信 ...

  4. MongoDB最佳实践中文手册

    背景:查阅了一下MongoDB的相关文档,发现中文文档还是比较少的,工作中需要用到MongoDB,而这本<MongoDB最佳实践>是很好的选择,所以就把这本手册翻译了一下,其中生涩的专业用 ...

  5. SQL Server - 最佳实践 - 参数嗅探问题 转。

    文章来自:https://yq.aliyun.com/articles/61767 先说我的问题,最近某个存储过程,暂定名字:sp_a 总是执行超时,sp_a带有一个参数,暂定名为 para1 var ...

  6. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  7. ASP.NET跨平台最佳实践

    前言 八年的坚持敌不过领导的固执,最终还是不得不阔别已经成为我第二语言的C#,转战Java阵营.有过短暂的失落和迷茫,但技术转型真的没有想象中那么难.回头审视,其实单从语言本身来看,C#确实比Java ...

  8. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  9. ASP.NET MVC防范CSRF最佳实践

    XSS与CSRF 哈哈,有点标题党,但我保证这篇文章跟别的不太一样. 我认为,网站安全的基础有三块: 防范中间人攻击 防范XSS 防范CSRF 注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷 ...

  10. 快速web开发中的前后端框架选型最佳实践

    这个最佳实践是我目前人在做的一个站点,主要功能: oauth登录 发布文章(我称为"片段"),片段可以自定义一些和内容有关的指标,如“文中人物:12”.支持自定义排版.插图.建立相 ...

随机推荐

  1. 【Oracle学习笔记】序列

    Oracle提供了sequence对象,由系统提供自增长的序列号,通常用于生成数据库数据记录的自增长主键或序号的地方,一般结合触发器使用. Sequence是数据库系统的特性,有的数据库有Sequen ...

  2. JFreeChart画图+jsp页面显示实现统计图

    1 开发环境: 1.eclipse(可替换) 2.jfreechart-1.0.19 2 说明: (1) source目录:为 jfreechart的源码目录:不会的主要看这里.因为他的文档是收费的. ...

  3. 华途软件受控XML转EXCEL

    公司加密系统用的是华途的产品.最近公司高层想要重新梳理公司信息安全管理情况,华途加密系统的梳理和优化是重中之重. 今天公司领导要求IT导出目前系统中所有软件.后缀的受控情况,然后IT吭哧吭哧地把华途软 ...

  4. 【已采纳】最快获取package和activity的方式

    意外找到一个本人自认为是最快获取package和activity的方法,欢迎来辩! 用adb命令快速查看某应用appPackage及appActivity的方法(前提是需要用数据线连接真机\模拟器也可 ...

  5. Git-初始化配置及SSH_key配置

    step1.安装完Git,执行检查是否安装成功:git --version step2.配置全局变量 配置完执行检查:git config --list step3.生成SSH_KEY 如果报ssh- ...

  6. FPGA设计千兆以太网MAC(3)——数据缓存及位宽转换模块设计与验证

    本文设计思想采用明德扬至简设计法.上一篇博文中定制了自定义MAC IP的结构,在用户侧需要位宽转换及数据缓存.本文以TX方向为例,设计并验证发送缓存模块.这里定义该模块可缓存4个最大长度数据包,用户根 ...

  7. Fix: Unable to terminate process ‘Access is denied’ 杀进程,关服务

    https://appuals.com/fix-unable-to-terminate-process-access-is-denied/ 我 Process Hacker (方法3),成功杀掉: 阿 ...

  8. R语言学习——图形初阶之折线图与图形参数控制

    plot()是R中为对象作图的一个泛型函数(它的输出将根据所绘制对象类型的不同而变化):plot(x,y,type="b")表示将x置于横轴,y置于纵轴,绘制点集(x,y),然后使 ...

  9. pip "Cannot uninstall 'six'. It is a distutils installed project..." 解决方法

    安装 mysql-connector-python 时,由于依赖包 six 之前已经安装过,但是不能自动更新到所需版本.有如下错误提示: pip "Cannot uninstall 'six ...

  10. 使用 canvas 画图时图像文字模糊的解决办法

    最近在使用 canvas 画图的时候,遇到了图像文字模糊的问题,解决思路就是根据分辨率创建不同尺寸的画布.以下是创建高分辨率画布的代码: /** * 创建高分辨率画布 * @param w 画布宽 * ...