# 设置动态数据脱敏
SQL Server提供了一种称为动态数据遮蔽（Dynamic Data Masking，DDM）的安全功能，该功能可以防止非授权用户在查询时直接查看敏感数据。动态数据脱敏通过在查询结果中展示部分或全部脱敏的值，而不是实际的数据值，来达到保护数据的目的。
本文档提供通过云数据库RDS界面和原生SQL Server设置动态数据脱敏的操作。
#### 约束限制
- 云数据库RDS for SQL Server如需设置动态数据脱敏，请[提交工单](https://console.huaweicloud.com/ticket/?locale=zh-cn#/ticketindex/createIndex)申请。
- 仅2016及以上版本支持动态数据脱敏。
- 数据库级别具有CONTROL SERVER或CONTROL权限的用户可以查看原始数据，包括管理员用户或角色，例如sysadmin、db_owner等。
- 不能针对以下列类型定义掩码规则：
  - 加密列（始终加密）。
  
  - FILESTREAM。
  
  - COLUMN_SET或属于列集一部分的稀疏列。
  
  - 不能在计算列上配置掩码，但如果计算列依赖于带有掩码的列，则计算列将返回已掩码数据。
  
  - 具有数据掩码的列不能作为FULLTEXT索引的密钥。
  
  - PolyBase[外部表](https://learn.microsoft.com/zh-cn/sql/t-sql/statements/create-external-table-transact-sql?view=sql-server-ver17)中的列。
   
- 如果用户没有UNMASK权限，则无法在配置了动态数据掩码的列上执行弃用的READTEXT、UPDATETEXT和WRITETEXT语句。
- 添加动态数据掩码是作为对基础表的架构更改实现，因此无法对具有依赖关系的列（例如计算列引用的列）执行。 如果针对具有依赖关系的列添加动态数据掩码，会报错：ALTER TABLE ALTER COLUMN _columnname_ failed because one or more objects access this column
  如果要解决该限制，可以先删除依赖项，然后添加动态数据屏蔽，再重新创建依赖项。例如：如果依赖项是由于索引依赖于该列，则可以删除索引，然后添加掩码，再重新创建依赖索引。
  
- 每当投影一个表达式并且该表达式引用了为其定义了数据掩码函数的列时，该表达式也将被屏蔽。无论使用何种函数（默认值、电子邮件、随机、自定义字符串）屏蔽引用的列，都将始终使用默认函数屏蔽生成的表达式。
 
#### 安全说明
虽然动态数据脱敏在直接访问生产数据库时也能有效防止敏感数据的意外泄露，但需要注意的是，拥有临时查询权限的非特权用户仍可能采用技术手段获取实际数据。
例如查询语句**SELECT Salary FROM Employees WHERE Salary = 10000**，即使"Salary"字段设置了动态脱敏，查询的结果为"xxx"或其它内容，依旧可以推断出"Salary"的值为"10000"。
#### 操作步骤
- [通过RDS界面设置动态脱敏]
  1. [登录RDS管理控制台](https://console.huaweicloud.com/rds/#/rds/management/list)。
  
  2. 单击管理控制台左上角的![](https://support.huaweicloud.com/usermanual-rds-mssql/zh-cn_image_0000002572991262.png)，选择区域。
  
  3. 在"实例管理"页面，选择目标实例，单击实例名称，进入实例的"概览"页面。
  
  4. 在左侧导航栏选择"数据库与账号管理"。
  
  5. 在"数据库管理"页签下，单击"更多 \> 动态脱敏"。 
     图1数据库管理   
     ![](https://support.huaweicloud.com/usermanual-rds-mssql/zh-cn_image_0000002456992350.png "点击放大")
     
     
  
  6. 在弹出页面中，单击"添加规则"，输入schema、表名称、列字段后选择脱敏格式，单击"保存"。 
     图2动态脱敏   
     ![](https://support.huaweicloud.com/usermanual-rds-mssql/zh-cn_image_0000002456832746.png "点击放大")
     
     
  
  7. 设置完需要配置动态脱敏的字段后，单击"确定"。
   
- [原生SQL Server设置动态脱敏]
  1. 准备数据。
     1. 创建一个数据库，用来存放将要使用的表和数据。
        ```
        -- 创建一个示例数据库
        CREATE DATABASE DemoDDM;
        GO
        -- 使用新创建的数据库
        USE DemoDDM;
        GO
        ```
        
     
     2. 创建一个表，并指定列的脱敏规则。
        ```
        -- 创建一个包含敏感信息的表，并直接应用脱敏规则
        CREATE TABLE Employees (
            Id INT IDENTITY PRIMARY KEY,
            Name VARCHAR(50),
            SSN VARCHAR(11) MASKED WITH (FUNCTION = 'partial(1, "XXX-XX-", 4)'),
            CreditCard VARCHAR(19) MASKED WITH (FUNCTION = 'partial(4, "XXXX-XXXX-XXXX-", 4)')
        );
        GO
        -- 插入一些示例数据
        INSERT INTO Employees (Name, SSN, CreditCard) VALUES
        ('Alice', '123-45-6789', '1234-5678-9012-3456'),
        ('Bob', '234-56-7890', '9876-5432-1098-7654');
        GO
        ```
        
     
     3. 验证脱敏效果。
        ```
        -- 拥有DemoDDM库的db_datareader角色权限，没有数据库db_owner角色权限
        SELECT * FROM Employees;
        ```
        查询结果应如下所示：
        ```
        | Id | Name  | SSN            | CreditCard                  |
        |---|---|---|---|
        | 1  | Alice | 1XXX-XX-678    | 1234XXXX-XXXX-XXXX-         |
        | 2  | Bob   | 2XXX-XX-789    | 9876XXXX-XXXX-XXXX-         |
        ```
        
      
  
  2. 动态数据脱敏的基础使用。
     1. 准备表数据以应⽤动态数据脱敏。 如果表已经存在，执行[2.b]使用**ALTER TABLE**命令为已存在的列添加脱敏规则。
        ```
        -- 创建一个新的表
        CREATE TABLE Customers (
            Id INT IDENTITY PRIMARY KEY,
            Name VARCHAR(50),
            Email VARCHAR(100),
            PhoneNumber VARCHAR(15)
        );
        GO
        -- 插入一些示例数据
        INSERT INTO Customers (Name, Email, PhoneNumber) VALUES
        ('Charlie', 'charlie@example.com', '123-456-7890'),
        ('Diana', 'diana@example.com', '987-654-3210');
        GO
        ```
        
     
     2. 为已有表中的列添加脱敏规则。
        ```
        -- 为 Email 列添加脱敏规则
        ALTER TABLE Customers
        ALTER COLUMN Email ADD MASKED WITH (FUNCTION = 'email()');
        GO
        -- 为 PhoneNumber 列添加脱敏规则
        ALTER TABLE Customers
        ALTER COLUMN PhoneNumber ADD MASKED WITH (FUNCTION = 'partial(5, "XXX-", 0)');
        GO
        ```
        
     
     3. 执行一个查询，验证脱敏效果。
        ```
        -- 没有适当权限的用户看到的数据
        SELECT * FROM Customers;
        ```
        查询结果应如下所示：
        ```
        | Id | Name   | Email                        | PhoneNumber     |
        |---|---|---|---|
        | 1  | Charlie| cXXX@XXXX.com          | 123-4XXX-    |
        | 2  | Diana  | dXXX@XXXX.com           | 987-6XXX-    |
        ```
        
      
  
  3. 关于SQL Server动态数据脱敏函数以及权限的使用方法，请参见[函数及权限相关操作]。
   
 #### 函数及权限相关操作
#### 动态数据脱敏函数创建及使用
SQL Server提供了多种脱敏函数，包括：
- default()：完全遮盖列值，用标准字符替代。
- email()：为电子邮件地址设计的脱敏函数。
- random('min', 'max')：用指定范围内的随机数替换数值。
- partial('prefix', 'mask', 'suffix')：部分遮罩，其中 \`prefix\` 是前缀，\`mask\` 是遮罩字符串，\`suffix\` 是后缀。
动态数据脱敏函数的使用示例如下：
- 使用default()函数
  ```
  -- 为某个列使用 default() 函数
  CREATE TABLE SensitiveData (
      Id INT IDENTITY PRIMARY KEY,
      Value VARCHAR(50) MASKED WITH (FUNCTION = 'default()')
  );
  GO
  -- 插入一些示例数据
  INSERT INTO SensitiveData (Value) VALUES
  ('Confidential Information 1'),
  ('Confidential Information 2');
  GO
  -- 查询表，查看脱敏效果
  SELECT * FROM SensitiveData;
  ```
  查询结果应如下所示：
  ```
  | Id | Value                     |
  |---|---|
  | 1  | xxxx |
  | 2  | xxxx |
  ```
  
- 使用random()函数
  ```
  -- 为某个列使用 random() 函数
  CREATE TABLE Accounts (
      Id INT IDENTITY PRIMARY KEY,
      Balance DECIMAL(10, 2) MASKED WITH (FUNCTION = 'random(1000, 5000)')
  );
  GO
  -- 插入一些示例数据
  INSERT INTO Accounts (Balance) VALUES
  (1234.56),
  (4567.89);
  GO
  -- 查询表，查看脱敏效果
  SELECT * FROM Accounts;
  ```
  查询结果应如下所示：
  ```
  | Id | Balance |
  |---|---|
  | 1  | 3456.78 |
  | 2  | 1234.56 |
  ```
  
 
#### 动态数据脱敏权限的使用
为了允许某些用户查看实际数据而不仅是脱敏版本，可以使用**UNMASK**权限。
1. 授予权限。
   ```
   -- 创建一个示例用户并添加db_datareader角色权限（创建用户推荐使用控制台创建用户，不推荐使用sql，此处仅用作测试）
   CREATE USER DataViewer WITHOUT LOGIN;
   ALTER ROLE db_datareader ADD MEMBER DataViewer;
   -- 授予权限
   GRANT UNMASK TO DataViewer;
   GO
   ```
   
2. 测试权限。 以DataViewer用户身份登录并执行相同的查询：
   ```
   -- 使用 DataViewer 身份登录
   EXECUTE AS USER = 'DataViewer';
   -- 查询 Employees 表
   SELECT * FROM Employees;
   -- 查询 Customers 表
   SELECT * FROM Customers;
   -- 恢复原始用户身份
   REVERT;
   ```
   
3. 查看查询结果。 对于DataViewer，查询结果应显示完整的列内容。
   Employees表的查询结果：
   ```
   | Id | Name  | SSN            | CreditCard                  |
   |---|---|---|---|
   | 1  | Alice | 123-45-6789    | 1234-5678-9012-3456         |
   | 2  | Bob   | 234-56-7890    | 9876-5432-1098-7654         |
   ```
   Customers表的查询结果：
   ```
   | Id | Name   | Email                        | PhoneNumber     |
   |---|---|---|---|
   | 1  | Charlie| charlie@example.com          | 123-456-7890    |
   | 2  | Diana  | diana@example.com            | 987-654-3210    |
   ```
   
 
