一、前言#
在企业数据库安全治理中,身份证号、手机号、工资、银行卡号、邮箱、地址等字段通常属于敏感数据。传统做法包括修改真实数据、建立脱敏副本库、应用层代码脱敏、导出后手工处理等,但这些方式通常存在业务侵入大、运维复杂、数据冗余、安全边界不清晰等问题。
动态数据脱敏的核心思想是:真实数据不变,仅在查询结果返回时根据策略动态屏蔽敏感字段。
本文结合 Oracle 与达梦数据库两种实现方式,通过员工表中的身份证号、手机号、工资三个字段进行完整演示,并把实际排错过程中遇到的问题一并整理出来,形成一份可复现、可验证、可发布的技术实践文档。
| 数据库 | 实现方式 | 核心对象 |
|---|---|---|
| Oracle | DBMS_REDACT | Redaction Policy |
| 达梦 DM8 | DBMS_RLS + POLICY + MASK | RLS Policy + Mask Function |
二、Oracle 动态数据脱敏#
2.1 DBMS_REDACT 理论说明#
Oracle DBMS_REDACT 是用于实现 Data Redaction 的 PL/SQL 包。其作用是在 SQL 查询结果返回给低权限用户或应用前,对指定列进行实时掩码处理。该机制不修改表中的真实数据,而是在结果返回阶段进行动态处理。
Oracle Data Redaction 的几个关键点:
| 项目 | 说明 |
|---|---|
| 是否修改原始数据 | 不修改 |
| 生效阶段 | 查询结果返回前 |
| 控制对象 | 表或视图中的敏感列 |
| 条件控制 | 通过 expression 判断是否脱敏 |
| 绕过机制 | 具有 EXEMPT REDACTION POLICY 权限的用户不会被脱敏 |
| 多列脱敏 | 同一对象先 ADD_POLICY,后续列通过 ALTER_POLICY ADD_COLUMN 添加 |
Oracle 的 expression 逻辑与达梦后面示例不同:
Oracle DBMS_REDACT:expression 为 TRUE 时执行脱敏。例如:
EXPRESSION => '1=1'表示对符合条件的普通查询用户执行脱敏。
2.2 创建测试用户#
CREATE USER HR_TEST IDENTIFIED BY "Oracle123";
GRANT CONNECT, RESOURCE TO HR_TEST;
SQL> CREATE USER HR_TEST IDENTIFIED BY "Oracle123";
User created.
SQL> GRANT CONNECT,RESOURCE TO HR_TEST;
Grant succeeded.
SQL> 2.3 创建测试表#
CREATE TABLE HR_TEST.EMP_INFO
(
EMP_ID INT,
EMP_NAME VARCHAR2(50),
ID_CARD VARCHAR2(18),
MOBILE VARCHAR2(11),
SALARY NUMBER(10,2)
);插入测试数据:
INSERT INTO HR_TEST.EMP_INFO VALUES
(1, '张三', '310101199001011234', '13812345678', 25000);
INSERT INTO HR_TEST.EMP_INFO VALUES
(2, '李四', '320102198805056789', '13987654321', 18000);
COMMIT;结果如下:
SQL> CREATE TABLE HR_TEST.EMP_INFO
(
EMP_ID INT,
2 3 4 EMP_NAME VARCHAR2(50),
ID_CARD VARCHAR2(18),
MOBILE VARCHAR2(11),
SALARY NUMBER(10,2)
);
INSERT INTO HR_TEST.EMP_INFO VALUES
(1, '张三', '310101199001011234', '13812345678', 25000);
INSERT INTO HR_TEST.EMP_INFO VALUES
(2, '李四', '320102198805056789', '13987654321', 18000);
COMMIT; 5 6 7 8
Table created.
SQL> SQL> 2
1 row created.
SQL> SQL> 2
1 row created.
SQL> SQL>
Commit complete.
SQL> select * from HR_TEST.EMP_INFO;
EMP_ID EMP_NAME ID_CARD
---------- -------------------------------------------------- ------------------
MOBILE SALARY
----------- ----------
1 张三 310101199001011234
13812345678 25000
2 李四 320102198805056789
13987654321 18000
SQL> 2.4 Oracle 创建身份证脱敏策略#
目标:
310101199001011234
→
310101********1234添加策略:
BEGIN
DBMS_REDACT.ADD_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
COLUMN_NAME => 'ID_CARD',
POLICY_NAME => 'REDACT_EMP_ID_CARD',
FUNCTION_TYPE => DBMS_REDACT.PARTIAL,
FUNCTION_PARAMETERS => 'VVVVVVVVVVVVVVVVVV,VVVVVVVVVVVVVVVVVV,*,7,14',
EXPRESSION => '1=1'
);
END;
/
SQL> BEGIN
DBMS_REDACT.ADD_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
COLUMN_NAME => 'ID_CARD',
POLICY_NAME => 'REDACT_EMP_ID_CARD',
FUNCTION_TYPE => DBMS_REDACT.PARTIAL,
FUNCTION_PARAMETERS => 'VVVVVVVVVVVVVVVVVV,VVVVVVVVVVVVVVVVVV,*,7,14',
EXPRESSION => '1=1'
);
END;
/ 2 3 4 5 6 7 8 9 10 11 12
PL/SQL procedure successfully completed.
SQL> 2.5 Oracle 添加手机号脱敏#
目标:
13812345678
→
138****5678使用 DBMS_REDACT.ALTER_POLICY,在已有策略中增加列::
BEGIN
DBMS_REDACT.ALTER_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
POLICY_NAME => 'REDACT_EMP_ID_CARD',
ACTION => DBMS_REDACT.ADD_COLUMN,
COLUMN_NAME => 'MOBILE',
FUNCTION_TYPE => DBMS_REDACT.PARTIAL,
FUNCTION_PARAMETERS => 'VVVVVVVVVVV,VVVVVVVVVVV,*,4,7',
EXPRESSION => '1=1'
);
END;
/
SQL> BEGIN
2 DBMS_REDACT.ALTER_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
POLICY_NAME => 'REDACT_EMP_ID_CARD',
ACTION => DBMS_REDACT.ADD_COLUMN,
COLUMN_NAME => 'MOBILE',
FUNCTION_TYPE => DBMS_REDACT.PARTIAL,
FUNCTION_PARAMETERS => 'VVVVVVVVVVV,VVVVVVVVVVV,*,4,7',
EXPRESSION => '1=1'
);
END;
/ 3 4 5 6 7 8 9 10 11 12 13
PL/SQL procedure successfully completed.
SQL> 特别注意:同一张表只能 ADD_POLICY 一次。后续字段必须:ALTER_POLICY + ADD_COLUMN
2.6 Oracle 添加工资脱敏#
BEGIN
DBMS_REDACT.ALTER_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
POLICY_NAME => 'REDACT_EMP_ID_CARD',
ACTION => DBMS_REDACT.ADD_COLUMN,
COLUMN_NAME => 'SALARY',
FUNCTION_TYPE => DBMS_REDACT.FULL,
EXPRESSION => '1=1'
);
END;
/
SQL> BEGIN
DBMS_REDACT.ALTER_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
2 3 4 OBJECT_NAME => 'EMP_INFO',
POLICY_NAME => 'REDACT_EMP_ID_CARD',
ACTION => DBMS_REDACT.ADD_COLUMN,
COLUMN_NAME => 'SALARY',
FUNCTION_TYPE => DBMS_REDACT.FULL,
EXPRESSION => '1=1'
);
END;
/ 5 6 7 8 9 10 11 12
PL/SQL procedure successfully completed.
SQL> 2.7 Oracle 验证脱敏效果#
CONN HR_TEST/Oracle123
SELECT EMP_ID,EMP_NAME,ID_CARD,MOBILE,SALARY FROM HR_TEST.EMP_INFO;结果:
SQL> CONN HR_TEST/Oracle123
Connected.
SQL> SET LINES 200
SQL> SELECT EMP_ID,EMP_NAME,ID_CARD,MOBILE,SALARY FROM HR_TEST.EMP_INFO;
EMP_ID EMP_NAME ID_CARD MOBILE SALARY
---------- -------------------------------------------------- ------------------ ----------- ----------
1 张三 310101********1234 138****5678 0
2 李四 320102********6789 139****4321 0
SQL>2.8 Oracle 查看脱敏策略#
查看 Policy#
SELECT OBJECT_OWNER,
OBJECT_NAME,
POLICY_NAME,
EXPRESSION,
ENABLE
FROM REDACTION_POLICIES
WHERE OBJECT_OWNER = 'HR_TEST'
AND OBJECT_NAME = 'EMP_INFO';
SQL> SELECT OBJECT_OWNER,
OBJECT_NAME,
POLICY_NAME,
EXPRESSION,
ENABLE
FROM REDACTION_POLICIES
WHERE OBJECT_OWNER = 'HR_TEST'
AND OBJECT_NAME = 'EMP_INFO'; 2 3 4 5 6 7 8
OBJECT_OWNER OBJECT_NAME POLICY_NAME EXPRESSION ENABLE
-------------------- ------------------------------ ------------------------------ -------------------- -------
HR_TEST EMP_INFO REDACT_EMP_ID_CARD 1=1 YES
SQL>查看脱敏字段#
SELECT OBJECT_OWNER,
OBJECT_NAME,
COLUMN_NAME,
FUNCTION_TYPE,
FUNCTION_PARAMETERS
FROM REDACTION_COLUMNS
WHERE OBJECT_OWNER = 'HR_TEST'
AND OBJECT_NAME = 'EMP_INFO';
SQL> SELECT OBJECT_OWNER,
OBJECT_NAME,
COLUMN_NAME,
FUNCTION_TYPE,
FUNCTION_PARAMETERS
FROM REDACTION_COLUMNS
WHERE OBJECT_OWNER = 'HR_TEST'
AND OBJECT_NAME = 'EMP_INFO'; 2 3 4 5 6 7 8
OBJECT_OWNER OBJECT_NAME COLUMN_NAME FUNCTION_TYPE FUNCTION_PARAMETERS
-------------------- ------------------------------ ------------------------------ -------------------- ------------------------------------------------------------
HR_TEST EMP_INFO ID_CARD PARTIAL REDACTION VVVVVVVVVVVVVVVVVV,VVVVVVVVVVVVVVVVVV,*,7,14
HR_TEST EMP_INFO MOBILE PARTIAL REDACTION VVVVVVVVVVV,VVVVVVVVVVV,*,4,7
HR_TEST EMP_INFO SALARY FULL REDACTION
SQL>2.9 Oracle 管理员默认不脱敏#
数据库管理员保留权限机制。实验验证SYSDBA 默认绕过脱敏策略:
SQL> CONN / AS SYSDBA
Connected.
SQL> SELECT EMP_ID,EMP_NAME,ID_CARD,MOBILE,SALARY FROM HR_TEST.EMP_INFO;
EMP_ID EMP_NAME ID_CARD MOBILE SALARY
---------- -------------------------------------------------- ------------------ ----------- ----------
1 张三 310101199001011234 13812345678 25000
2 李四 320102198805056789 13987654321 18000
SQL>| 用户 | 是否脱敏 |
|---|---|
| APP_USER | 脱敏 |
| SYSDBA | 不脱敏 |
三、达梦数据库动态数据脱敏#
3.1 DBMS_RLS 理论说明#
达梦数据库提供 DBMS_RLS 包,通过策略 POLICY 管理实现数据行级安全访问控制,即 Row Level Security,简称 RLS。RLS 的原始设计目标是控制不同行或不同机构的数据访问范围,避免在大量 SQL、视图或应用代码中反复手工追加 WHERE 条件。
在脱敏场景中,可以结合以下对象实现动态数据脱敏:
| 组件 | 作用 |
|---|---|
ENABLE_RLS | 开启 RLS 能力 |
POLICY_FUNCTION | 返回策略谓词 |
DBMS_RLS.ADD_POLICY | 将策略绑定到表或视图 |
SEC_RELEVANT_COLS | 指定敏感列 |
SEC_RELEVANT_COLS_OPT => DBMS_RLS.ALL_ROWS | 保留所有行,仅处理敏感列显示 |
DBMS_RLS.ADD_MASK | 为敏感列绑定掩码函数 |
MASK FUNCTION | 定义字段最终显示形式 |
达梦官方文档明确说明:DBMS_RLS 依赖 ENABLE_RLS=1,开启后需要初始化 DBMS_RLS 系统包,策略对 SYSDBA 用户不会生效,DM MPP 环境不支持 DBMS_RLS 包。
3.2 开启 ENABLE_RLS#
修改ENABLE_RLS参数,重启数据库实例。
ALTER SYSTEM SET 'ENABLE_RLS' = 1 SPFILE;
EXIT;
DmServiceDMSERVER restart
[dmdba@dameng-srv ~]$ disql SYSDBA
password:
Server[LOCALHOST:5236]:mode is normal, state is open
login used time : 4.068(ms)
disql V8
SQL> ALTER SYSTEM SET 'ENABLE_RLS' = 1 SPFILE;
DMSQL executed successfully
used time: 9.649(ms). Execute id is 6801.
SQL> EXIT
[dmdba@dameng-srv ~]$ DmServiceDMSERVER restart
Stopping DmServiceDMSERVER: [ OK ]
Starting DmServiceDMSERVER: [ OK ]
[dmdba@dameng-srv ~]$验证:
SELECT PARA_NAME, PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = 'ENABLE_RLS';应看到:
[dmdba@dameng-srv ~]$ disql SYSDBA
password:
Server[LOCALHOST:5236]:mode is normal, state is open
login used time : 4.601(ms)
disql V8
SQL> SELECT PARA_NAME, PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = 'ENABLE_RLS';2 3
LINEID PARA_NAME PARA_VALUE
---------- ---------- ----------
1 ENABLE_RLS 1
used time: 7.147(ms). Execute id is 601.
SQL>3.3 初始化 DBMS_RLS(部分环境需要)#
部分版本需要初始化:
SP_INIT_RLS_SYS(1);兼容旧版本的方式:
SP_CREATE_SYSTEM_PACKAGES(1, 'DBMS_RLS');实际执行时,如果当前版本已经内置或已经初始化,则不需要重复执行。
3.4 创建测试用户#
CREATE USER HR_TEST IDENTIFIED BY "Dameng123";
GRANT RESOURCE,PUBLIC TO HR_TEST;
SQL> CREATE USER HR_TEST IDENTIFIED BY "Dameng123";
executed successfully
used time: 4.078(ms). Execute id is 608.
SQL> GRANT RESOURCE,PUBLIC TO HR_TEST;
executed successfully
used time: 3.900(ms). Execute id is 609.
SQL>注意:测试脱敏时不要给业务用户授予
DBA,避免高权限导致判断混乱。
3.5 创建测试表#
CREATE TABLE HR_TEST.EMP_INFO
(
EMP_ID INT,
EMP_NAME VARCHAR2(50),
ID_CARD VARCHAR2(18),
MOBILE VARCHAR2(11),
SALARY NUMBER(10,2)
);插入数据:
INSERT INTO HR_TEST.EMP_INFO VALUES
(1, '张三', '310101199001011234', '13812345678', 25000);
INSERT INTO HR_TEST.EMP_INFO VALUES
(2, '李四', '320102198805056789', '13987654321', 18000);
COMMIT;执行结果:
SQL> CREATE TABLE HR_TEST.EMP_INFO
(
EMP_ID INT,
EMP_NAME VARCHAR2(50),
ID_CARD VARCHAR2(18),
MOBILE VARCHAR2(11),
SALARY NUMBER(10,2)
);2 3 4 5 6 7 8
executed successfully
used time: 4.899(ms). Execute id is 610.
SQL> INSERT INTO HR_TEST.EMP_INFO VALUES
(1, '', '310101199001011234', '13812345678', 25000);
INSERT INTO HR_TEST.EMP_INFO VALUES
(2, '', '320102198805056789', '13987654321', 18000);
COMMIT;2 affect rows 1
used time: 3.683(ms). Execute id is 611.
SQL> SQL> 2 affect rows 1
used time: 1.163(ms). Execute id is 612.
SQL> SQL>
executed successfully
used time: 1.035(ms). Execute id is 613.3.6 创建策略函数#
达梦策略函数必须符合固定模板:两个输入参数,返回字符串。返回值会作为策略谓词参与访问控制。
CREATE OR REPLACE FUNCTION HR_TEST.RLS_EMP_MASK_FUN
(
P_OWNER VARCHAR2,
P_OBJ VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN '1=2';
END;
/
SQL> CREATE OR REPLACE FUNCTION HR_TEST.RLS_EMP_MASK_FUN
(
P_OWNER VARCHAR2,
P_OBJ VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN '1=2';
END;
/2 3 4 5 6 7 8 9 10 11
executed successfully
used time: 8.267(ms). Execute id is 1202.
SQL>注意:
RETURN '1=2';条件不成立,策略生效,触发 MASK
3.7 创建 MASK 函数#
身份证号脱敏函数#
CREATE OR REPLACE FUNCTION HR_TEST.MASK_ID_CARD
(
P_ID_CARD VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN SUBSTR(P_ID_CARD, 1, 6)
|| '********'
|| SUBSTR(P_ID_CARD, -4);
END;
/
SQL> CREATE OR REPLACE FUNCTION HR_TEST.MASK_ID_CARD
(
P_ID_CARD VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN SUBSTR(P_ID_CARD, 1, 6)
|| '********'
|| SUBSTR(P_ID_CARD, -4);
END;
/2 3 4 5 6 7 8 9 10 11 12
executed successfully
used time: 7.145(ms). Execute id is 1203.
SQL>手机号脱敏函数#
CREATE OR REPLACE FUNCTION HR_TEST.MASK_MOBILE
(
P_MOBILE VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN SUBSTR(P_MOBILE, 1, 3)
|| '****'
|| SUBSTR(P_MOBILE, -4);
END;
/
SQL> CREATE OR REPLACE FUNCTION HR_TEST.MASK_MOBILE
(
P_MOBILE VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN SUBSTR(P_MOBILE, 1, 3)
|| '****'
|| SUBSTR(P_MOBILE, -4);
END;
/2 3 4 5 6 7 8 9 10 11 12
executed successfully
used time: 7.131(ms). Execute id is 1204.
SQL>工资脱敏函数#
CREATE OR REPLACE FUNCTION HR_TEST.MASK_SALARY
(
P_SALARY NUMBER
)
RETURN NUMBER
AS
BEGIN
RETURN 0;
END;
/
SQL> CREATE OR REPLACE FUNCTION HR_TEST.MASK_SALARY
(
P_SALARY NUMBER
)
RETURN NUMBER
AS
BEGIN
RETURN 0;
END;
/2 3 4 5 6 7 8 9 10
executed successfully
used time: 7.257(ms). Execute id is 1205.
SQL>3.8 创建 RLS Policy#
BEGIN
DBMS_RLS.ADD_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
POLICY_NAME => 'POL_EMP_INFO_MASK',
FUNCTION_SCHEMA => 'HR_TEST',
POLICY_FUNCTION => 'RLS_EMP_MASK_FUN',
STATEMENT_TYPES => 'SELECT',
SEC_RELEVANT_COLS => 'ID_CARD,MOBILE,SALARY',
SEC_RELEVANT_COLS_OPT => DBMS_RLS.ALL_ROWS
);
END;
/
SQL> BEGIN
DBMS_RLS.ADD_POLICY(
OBJECT_SCHEMA => 'HR_TEST',
OBJECT_NAME => 'EMP_INFO',
POLICY_NAME => 'POL_EMP_INFO_MASK',
2 3 4 5 6 FUNCTION_SCHEMA => 'HR_TEST',
POLICY_FUNCTION => 'RLS_EMP_MASK_FUN',
STATEMENT_TYPES => 'SELECT',
SEC_RELEVANT_COLS => 'ID_CARD,MOBILE,SALARY',
SEC_RELEVANT_COLS_OPT => DBMS_RLS.ALL_ROWS
);
7 8 9 10 11 12 END;
/13
DMSQL executed successfully
used time: 5.585(ms). Execute id is 1206.
SQL>这里的关键参数说明:
| 参数 | 说明 |
|---|---|
OBJECT_SCHEMA | 被保护对象所在模式 |
OBJECT_NAME | 被保护表或视图 |
POLICY_NAME | 策略名称 |
FUNCTION_SCHEMA | 策略函数所在模式 |
POLICY_FUNCTION | 返回策略谓词的函数 |
STATEMENT_TYPES | 生效 SQL 类型,本例为 SELECT |
SEC_RELEVANT_COLS | 敏感列列表 |
SEC_RELEVANT_COLS_OPT | 指定 ALL_ROWS 后保留所有行,仅处理敏感列显示 |
3.9 绑定 MASK 函数#
身份证号:
DBMS_RLS.ADD_MASK(
'HR_TEST',
'EMP_INFO',
'ID_CARD',
'HR_TEST',
NULL,
'MASK_ID_CARD'
);
SQL> DBMS_RLS.ADD_MASK(
'HR_TEST',
'EMP_INFO',
'ID_CARD',
'HR_TEST',
NULL,
'MASK_ID_CARD'
);2 3 4 5 6 7 8
DMSQL executed successfully
used time: 5.621(ms). Execute id is 1207.
SQL>手机号:
DBMS_RLS.ADD_MASK(
'HR_TEST',
'EMP_INFO',
'MOBILE',
'HR_TEST',
NULL,
'MASK_MOBILE'
);
SQL> DBMS_RLS.ADD_MASK(
'HR_TEST',
'EMP_INFO',
'MOBILE',
'HR_TEST',
NULL,
'MASK_MOBILE'
);2 3 4 5 6 7 8
DMSQL executed successfully
used time: 3.423(ms). Execute id is 1208.
SQL>工资:
DBMS_RLS.ADD_MASK(
'HR_TEST',
'EMP_INFO',
'SALARY',
'HR_TEST',
NULL,
'MASK_SALARY'
);
SQL> DBMS_RLS.ADD_MASK(
'HR_TEST',
'EMP_INFO',
'SALARY',
'HR_TEST',
NULL,
'MASK_SALARY'
);2 3 4 5 6 7 8
DMSQL executed successfully
used time: 3.313(ms). Execute id is 1209.
SQL>3.10 达梦验证脱敏效果#
普通用户或业务用户查询:
CONN HR_TEST/Dameng123
SELECT EMP_ID,EMP_NAME,ID_CARD,MOBILE,SALARY FROM HR_TEST.EMP_INFO;验证结果:
SQL> CONN HR_TEST/Dameng123
Server[LOCALHOST:5236]:mode is normal, state is open
login used time : 3.265(ms)
SQL> SELECT EMP_ID,EMP_NAME,ID_CARD,MOBILE,SALARY FROM HR_TEST.EMP_INFO;
LINEID EMP_ID EMP_NAME ID_CARD MOBILE SALARY
---------- ----------- -------- ------------------ ----------- ------
1 1 310101********1234 138****5678 0
2 2 320102********6789 139****4321 0
used time: 4.411(ms). Execute id is 1502.
SQL>3.11 达梦查看脱敏策略#
查看 Policy#
SQL> SELECT * FROM SYS.POLICIES;
LINEID OBJID POLICY_GROUP POLICY_NAME PF_SCHNAME PKG_NAME FUN_NAME SEL DEL INS UPD IND CHK_OPTION ENABLE STATIC_POLICY POLICY_TYPE LONG_PREDICATE TRIGID
---------- ----------- ------------ ----------------- ---------- -------- ---------------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ------------- ----------- -------------- -----------
1 1035 SYS_DEFAULT POL_EMP_INFO_MASK HR_TEST NULL RLS_EMP_MASK_FUN 3 0 0 0 0 0 1 0 5 0 -1
used time: 3.384(ms). Execute id is 1503.
SQL>查看 MASK#
SQL> SELECT * FROM SYS.DBA_POLICY_MASKS;
LINEID OBJECT_OWNER OBJECT_NAME COLUMN_NAME FUNCTION_OWNER PACKAGE_NAME FUNCTION_NAME
---------- ------------ ----------- ----------- -------------- ------------ -------------
1 HR_TEST EMP_INFO ID_CARD HR_TEST NULL MASK_ID_CARD
2 HR_TEST EMP_INFO MOBILE HR_TEST NULL MASK_MOBILE
3 HR_TEST EMP_INFO SALARY HR_TEST NULL MASK_SALARY
used time: 4.071(ms). Execute id is 1504.
SQL>3.12 达梦管理员默认不脱敏#
SYSDBA 默认绕过 RLS 脱敏策略。
[dmdba@dameng-srv ~]$ disql SYSDBA
password:
Server[LOCALHOST:5236]:mode is normal, state is open
login used time : 4.042(ms)
disql V8
SQL> SELECT EMP_ID,EMP_NAME,ID_CARD,MOBILE,SALARY FROM HR_TEST.EMP_INFO;
LINEID EMP_ID EMP_NAME ID_CARD MOBILE SALARY
---------- ----------- -------- ------------------ ----------- ------
1 1 310101199001011234 13812345678 25000
2 2 320102198805056789 13987654321 18000
used time: 3.265(ms). Execute id is 1201.
SQL>实验验证结果:
| 用户 | 是否脱敏 |
|---|---|
| HR_TEST | 脱敏 |
| SYSDBA | 不脱敏 |
四、Oracle 与达梦动态脱敏对比#
| 对比项 | Oracle DBMS_REDACT | 达梦 DBMS_RLS |
|---|---|---|
| 功能定位 | 动态数据脱敏 | 行级安全 + 动态脱敏 |
| 原始数据 | 不修改 | 不修改 |
| 生效阶段 | 查询结果返回前 | 查询策略改写与列掩码阶段 |
| 控制方式 | Redaction Policy | Policy Function + Mask Function |
| 多列处理 | 首列 ADD_POLICY,后续列 ALTER_POLICY ADD_COLUMN | SEC_RELEVANT_COLS 指定列,ADD_MASK 绑定函数 |
| 条件逻辑 | expression 为 TRUE 时脱敏 | 本例中 RETURN '1=2' 触发掩码显示 |
| 管理员绕过 | SYSDBA / EXEMPT 权限用户不脱敏 | SYSDBA 不脱敏 |
| 复杂度 | 较低,原生脱敏能力 | 较高,但灵活性更强 |
五、总结#
动态数据脱敏不是简单地把字段替换成星号,而是一种基于数据库策略的敏感数据访问控制能力。它的核心价值在于:真实数据仍然保存在数据库中,普通用户或应用查询时只看到脱敏结果,管理员或授权用户在受控场景下仍可访问原始数据,应用 SQL 通常无需大规模改造。
从本次实践看,Oracle 与达梦的实现路径不同,但目标一致:都希望在不改变原始数据的前提下,降低敏感信息在查询、运维、报表、测试和排障过程中的暴露风险。
无论 Oracle 还是达梦,动态脱敏都不是万能的数据安全方案。它解决的是“查询展示层面的敏感数据暴露”问题,不能替代权限最小化、账号治理、数据库审计、备份加密、网络访问控制和运维流程管控。尤其要注意,SYSDBA 等高权限管理员默认可以绕过脱敏策略,因此生产环境必须结合审计和权限管理共同落地。
参考资料#
- Oracle Database PL/SQL Packages and Types Reference:DBMS_REDACT
- Oracle Advanced Security Guide:Configuring Oracle Data Redaction Policies
- Oracle Data Redaction Guide:Redacting Multiple Columns
- Oracle Error Help:ORA-28069 / ORA-28074
- 达梦官方文档:DBMS_RLS 包
- 达梦官方 FAQ:如何使图形化工具查询出来的数据不全是明文
- dbi-services 技术文章:Oracle Data Redaction or how to hide critical information
