GVKun编程网logo

MSSQL - 最佳实践 - Always Encrypted

3

在本文中,我们将为您详细介绍MSSQL-最佳实践-AlwaysEncrypted的相关知识,此外,我们还会提供一些关于ajax–jQuery.always()似乎在$.get()请求实际完成之前触发、

在本文中,我们将为您详细介绍MSSQL - 最佳实践 - Always Encrypted的相关知识,此外,我们还会提供一些关于ajax – jQuery .always()似乎在$.get()请求实际完成之前触发、alter user xx with encrypted password ''123'' 与 alter user xx with password ''123'' 区别如何看得出来、android – 当我们使用app时更改选项菜单的文字颜色:showAsAction =“always”、Android菜单项showAsAction =“always”不起作用的有用信息。

本文目录一览:

MSSQL - 最佳实践 - Always Encrypted

MSSQL - 最佳实践 - Always Encrypted

author: 风移

摘要

在 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。

--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(11)  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 表的明文数据。

测试结果分析

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

测试应用源代码

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

最后总结

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

原文链接​

本文为云栖社区原创内容,未经允许不得转载。

ajax – jQuery .always()似乎在$.get()请求实际完成之前触发

ajax – jQuery .always()似乎在$.get()请求实际完成之前触发

这可能是我对 AJAX如何运行的误解,但我有一种情况,即在GET请求实际完成之前调用$.get回调(.always()).至少,这就是它的样子.

代码

var apiRequest  = 'some/url/';

// fetch list of video embed codes
$.get( apiRequest,function( embed_codes ) {

    // added this per Explosion Pills' answer
    var jqxhrs = [];

    // for each embed code,get video details (name,duration,etc.)
    for ( var i = 0; i < embed_codes.length; i++ ) {
        var videoDetailsRequest = '/v2/assets/' + embed_codes[i];
        //..declare vars..

        // fetch video details
        var jqxhr = $.get( videoDetailsRequest,function( video ) {
            // build playlist entry for each video
            playlistItem = '<li><h3>' + video.name + '</h3><p>' + video.description + '</p></li>';

            // create object of video HTML,with key = "video name" for sorting later
            videoArray[video.name] = playlistItem;
        },'jsonp' )

        // added this per Explosion Pills' answer
        jqxhrs.push( jqxhr );
    }

    // updated from jqxhr.always( function() {
    $.when( jqxhrs ).always( function() {
        // create array of keys
        for ( k in videoArray ) {
            if ( videoArray.hasOwnProperty( k ) ) { keys.push( k ); }
        }

        // append alphabetized list of videos to the page...
    });
},'jsonp' );

代码的作用

本质上,代码执行此操作:循环浏览视频嵌入代码列表.对于for循环的每次迭代,获取有关每个视频的详细信息并将这些细节推送到多维数组中.完成获取所有视频后,请调用.always()回调,该回调将视频分类为按字母顺序排列的播放列表并附加到页面.

问题

在获取所有视频之前,有时会调用.always()回调.播放列表中有9个视频,但有时在回拨被触发之前只返回6或7个视频,这意味着我的播放列表有点短.我已通过alert()键数和使用控制台对此进行了测试.我看到的是6-7个视频将返回,然后回调中的alert()会触发,然后返回剩余的视频.

我的问题:

为什么会这样?我认为在AJAX请求完成后触发了.always()回调.这是不是意味着在回调被触发之前应该返回所有视频?我怀疑它与AJAX中的“A”有关,但我认为always()回调的目的是解释这一点.非常感谢任何帮助我理解的东西.谢谢!

解决方法

你设置它的方式,一旦循环中的最后一个ajax请求完成,.always回调就会触发.这可能与其他人无关.当循环中的所有请求都已完成或外部请求已完成时,我无法判断是否要触发它.

对于外部请求,这很容易.只需将一个呼叫链接到.always(或.done,我相信它们是相同的,后者是首选).

如果您希望在完成所有其他ajax请求后完成,则可以使用$.when,它会检查所有延迟对象何时完成:

var jqxhrs = [];
   ...for loop...
   var jqxhr = $.get( videoDetailsRequest
   ...
   },'jsonp');
   jqxhrs.push(jqxhr);
...
$.when.apply(undefined,jqxhrs).always(function () { /* your intended callback */

或者,您可以使用.pipe:

//Create initial deferred object
var jqxhr = $.Deferred();
   ...for loop...
   jqxhr = jqxhr.pipe($.get(...
jqxhr.always(function () { /* callback */

alter user xx with encrypted password ''123'' 与 alter user xx with password ''123'' 区别如何看得出来

alter user xx with encrypted password ''123'' 与 alter user xx with password ''123'' 区别如何看得出来

我有一个困惑,我在修改用户密码时,
我使用下面两句都没有问题,不知道他们有什么区别?
alter user xx with password ‘123’
alter user xx with encrypted password ‘123’

android – 当我们使用app时更改选项菜单的文字颜色:showAsAction =“always”

android – 当我们使用app时更改选项菜单的文字颜色:showAsAction =“always”

我正在使用工具栏和使用选项进行相应的操作.我的问题是我想在白色工具栏上显示“SAVE”文本,我应用了很多样式,但它总是显示为黑色.但是当选项显示为弹出窗口时,文本颜色始终为白色.我谷歌这很多,但没有解决方案.

这是我的代码:

工具栏

<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/tools"
    android:id="@+id/my_awesome_toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:theme="@android:style/ThemeOverlay.Material.Light"
    app:popupTheme="@style/Theme.AppCompat.NoActionBar"
    android:background="@drawable/tb_header"
    android:minHeight="?attr/actionBarSize"/>

款式

<style name="MStyle" parent="Theme.AppCompat.Light.NoActionBar">

 <item name="android:windowBackground">@android:color/white</item>
        <item name="android:colorBackground">@android:color/black</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="android:textAllCaps">false</item>
        <item name="android:popupMenuStyle">@style/MyPopupMenu</item>
        <item name="android:panelBackground">@android:color/holo_green_light</item>
        <item name="android:actionBarWidgetTheme">@style/AppTheme</item>

    </style>

<style name ="MyPopupMenu" parent="android:Theme.Holo.Light">
        <item name="android:popupBackground">@android:color/holo_green_light</item>
    </style>


****Option menu**** 

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.doubtfire.userprofiles.ParentProfileActivity">
    <item
        android:id="@+id/action_skip"
        android:title="@string/menu_str_skip"

        android:orderInCategory="100"
        app:showAsAction="always" />
</menu>


****I even apply code at onPrepareOptionMenu**** 



   @Override
        public boolean onPrepareOptionsMenu(Menu menu) {
            for (int i=0; i<menu.size(); i++) {
                MenuItem mi = menu.getItem(i);
                String title = mi.getTitle().toString();
                Spannable newTitle = new SpannableString(title);
                newTitle.setSpan(new ForegroundColorSpan(Color.WHITE),newTitle.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                mi.setTitle(newTitle);
            }
            return true;
        }

解决方法

使用
<item name="android:actionMenuTextColor">#FFFFFF</item>

在MStyle中,如下所示:

<style name="MStyle" parent="Theme.AppCompat.Light.NoActionBar">

  ...

  <item name="android:actionMenuTextColor">#FFFFFF</item>
  <!-- for appcompat-->
  <item name="actionMenuTextColor">#FFFFFF</item>
</style>

当SAVE项目在ActionBar上可见时,此菜单项的更改颜色

Android菜单项showAsAction =“always”不起作用

Android菜单项showAsAction =“always”不起作用

我试过设置:

android:showAsAction=".."

对于以下每一个:

ifRoom,never,withText,always,collapseActionView

但我总是得到相同的结果,操作栏上没有任何按钮,所以我必须按“菜单”按钮.

这是现在菜单的图片:

<item android:id="@+id/smth1"
    android:title="@string/smth1"
    android:showAsAction="always"
    android:orderInCategory="1" />

我甚至试过添加这个:

android:uiOptions="splitactionBarWhenNarrow"

进入应用程序清单文件,但没有正面结果(没有改变).

我尝试在各种API(14,16,17,19)上运行它,但结果相同.

如果我的问题似乎不清楚,这里有一张菜单图片,我想要:

谢谢你的帮助.

解决方法

您可能只是没有足够的空间,您应该首先从您的活动中删除标题,方法是将其添加到您的onCreate方法:

ActionBar actionBar = getActionBar();
actionBar.setdisplayShowTitleEnabled(false);

然后你可以看到here你可以显示的最大图标数量直接链接到屏幕的宽度,在小型手机上你只能看到ActionBar中的2个图标.

今天的关于MSSQL - 最佳实践 - Always Encrypted的分享已经结束,谢谢您的关注,如果想了解更多关于ajax – jQuery .always()似乎在$.get()请求实际完成之前触发、alter user xx with encrypted password ''123'' 与 alter user xx with password ''123'' 区别如何看得出来、android – 当我们使用app时更改选项菜单的文字颜色:showAsAction =“always”、Android菜单项showAsAction =“always”不起作用的相关知识,请在本站进行查询。

本文标签: