目录
前置加密方式
中置加密方式
数据列加密方式
表空间加密
后置加密方式
总结归纳
最近几年,国家不断加强数据安全以及个人信息的保护力度,陆续颁布《网络安全法》、《密码法》、《数据安全法》、《个人信息保护法》、《商用密码管理条例》……国家相关部门依法维护网络空间主权和国家安全、社会公共利益,保护公民、法人和其他组织的合法权益,促进经济社会信息化健康发展。
表 部分法律法规列表
法规名称 | 施行时间 |
中华人民共和国网络安全法 | 2017年6月1日 |
中华人民共和国密码法 | 2020年1月1日 |
关键信息基础设施安全保护条例 | 2021年9月1日 |
中华人民共和国数据安全法 | 2021年9月1日 |
中华人民共和国个人信息保护法 | 2021年11月1日 |
数据出境安全评估办法 | 2022年9月1日 |
商用密码管理条例 | 2023年7月1日 |
在近期公布的某网络安全审查相关行政处罚事件中,其违法违规行为之一即为:“以明文形式存储身份证号信息5780.26万条”。要实现数据的安全存储,密码技术则是最直接、最有效的防护措施,尤其是采用符合相关部门认证的国密密码技术,对数据进行加密,确保数据以密文形式进行存储,在密钥不被泄露的情况下,结合有效的数据访问控制技术,实现数据安全性要求。
密码技术当今已被广泛应用,本文仅针对数据库的透明加密技术进行解析探讨,希望能帮助读者了解相关技术原理,应用场景,以达到在实际应用选型,风险评估中起到参考和帮助的作用。
对数据库中的数据实现密文存储的目的,当前已有多种实现方式,但部分方式会涉及到应用系统的大量改造,比如引入SDK方式,在本文中,仅考虑无需应用改造或极少量改造的透明加解密方式,通过对齐进行分类汇总,根据其部署位置,我们大致可以分为三类加密方式,即"前置"、"中置"、"后置"。
前置加密方式
前置加密方式通常为数据库网关方式,其需要在数据库前串联部署一个密码网关设备,使得所有的数据库访问流量都先经过密码网关设备,该设备应用SQL语法解析技术对所有SQL请求进行语法解析,将需要加密的信息进行SQL改写,然后再转发给数据库执行,最后网关设备把数据库返回的结果集中的相应密文字段进行解密成明文返回给客户端。
此种方式涉及到的核心技术主要包括SQL解析和SQL改写技术:
SQL 解析
分为词法解析和语法解析。词法解析器⽤于将 SQL 拆解为不可再分的原⼦符号,称为 Token。并根据不同数据库⽅⾔所提供的字典,将其归类为关键字,表达式,字⾯量和操作符。再使⽤语法解析器对 SQL 进⾏理解,并最终提炼出解析上下⽂。解析上下⽂包括表对象、列对象、排序项、分组项、聚合函数、分⻚信息、查询条件信息等,最终转换为抽象语法树。如下图所示:
图:SQL抽象语法树
SQL 改写
将 SQL 改写为在真实数据库中可以正确执⾏的语句。在改写时需要保证正确性改写,包括schema名称、表名称、列名称等标识对象名称的正确性。除此之外,可能还包括补列和分⻚信息修正等内容。
从一个简单的示例开始
SELECT * FROM personinfo WHERE pname='张三';
假设 pname 是需要加密的信息,则正确的改写应该是
SELECT * FROM personinfo WHERE pname=func_enc('张三');
或者
SELECT * FROM personinfo WHERE pname='grpfeGWAOjQ=';
在这种最简单的 SQL 场景中,只要通过字符串查找和替换就可以达到 SQL 改写的效果。但是下⾯的场景,就⽆法仅仅通过字符串的查找替换来正确的改写 SQL 了:
SELECT * FROM personinfo WHERE pname='张三' AND remarks='pname:姓名';
假设 pname 是需要加密的信息,则正确的改写应该是
SELECT * FROM personinfo WHERE pname=func_enc('张三') AND remarks='pname:姓名';
或者
SELECT * FROM personinfo WHERE pname='grpfeGWAOjQ=' AND remarks='pname:姓名';
而不是
SELECT * FROM personinfo WHERE pname=func_enc('张三') AND remarks='pname:func_enc(''姓名'')';
或者
SELECT * FROM personinfo WHERE pname='grpfeGWAOjQ=' AND remarks='pname:WEGGomklig=';
此种方式数据在密码网关设备处已加密成密文,然后数据内以密文形式存储在数据库内,而密钥通常保存在密码网关设备中或密码管理平台,数据与密钥独立存储,保证了数据的安全存储。该方式可与应用系统一定程度的解耦,灵活性较高,可避免应用系统的大幅改造,可快速实施的数据加密方式。但其部署在访问链路中不可绕过,一旦加密设备出现问题容易导致数据无法加解密的情况,所以如可支持HA等高可用部署,有效降低单点故障风险,可用性也可大大提高。在具备不同数据库协议解析和改写能力条件下可以支持的数据库类型较多,且较为容易的支持国产加密算法以及第三方KMS平台的对接。
另外由于其部署在数据库服务器前,所有数据库访问都流经该密码网关设备,所以可以对敏感数据的访问进行细粒度控制,提供『密文存储,访问可控,按需解密』的效果,从而保证数据的安全存储,访问可控。
但该方案也并非完美,也可能会有以下问题:
一是对部分数据库私有通信协议的解析和改写有可能面临破坏软件完整性的法律风险;
二是对SQL协议的解析技术的要求很高,尤其对复杂语句的解析及改写,存在解析不正确或不能解析的可能较大,而加密数据无法读取可能导致应用系统运行异常或数据一致性被破坏;
三是此类方式加密数据以后通常只能对密文字段的等值查询,不支持大于、小于、LIKE等范围或模糊查询,限制较大;
四是如果数据库访问压力很大,密码网关设备性能很容易造成瓶颈;
五是数据膨胀率大,通常至少会达到原大小的数倍甚至数十倍,同时对存量数据的加密时长与加密的数据量成正比,容易造成较长的业务停机时间;
六是通过存储过程/函数访问密文数据时,需要对存储过程/函数进行改造,否则程序或数据会出错;
七是对于批量返回解密数据的,解密耗时与待解密数据量成正比,即待解密量越大返回耗时越久,甚至超时。
中置加密方式
数据库中置加密主要有数据列加密和表空间加密两种方式,通常是通过数据库本身的可编程性或配置其安全组件来实现,所以在每种数据库上的具体实现可能存在着比较大的差异。
数据列加密方式
此类方式多为利用数据库UDF函数(User Define Function:用户自定义函数,简称UDF)辅以 视图+触发器 方式实现,可保证数据访问完全透明,无须应用系统改造同时保证部分数据使用场景的性能损耗较低。UDF自定义函数透明加密示意如下图所示。
图 UDF自定义函数加密示意图
在加密逻辑实现上可以参考如下Oracle数据库上的一个数据列加密示例
假设有一个数据表TestA如下
CREATE TABLE tableA (ci INTEGER,CV VARCHAR2(100));
INSERT INTO tableA VALUES(1,'china');
INSERT INTO tableA VALUES(2,'tianjin');
INSERT INTO tableA VALUES(3,'amare');
COMMIT;
然后第一步创建加密解密函数(仅做演示,未真正实现加解密)
--加密函数
CREATE FUNCTION func_encrypt(p_in IN VARCHAR2) RETURN VARCHAR2
AS
BEGIN
RETURN TRANSLATE(p_in,'acejhijmntr','xyzopquvw12');
END;
--解密函数
CREATE FUNCTION func_decrypt(p_in IN VARCHAR2) RETURN VARCHAR2
AS
BEGIN
RETURN TRANSLATE(p_in,'xyzopquvw12','acejhijmntr');
END;
第二步给tableA表添加一个新列,用来保存加密后的数据(这里也可以不添加新的数据列而是通过修改原数据列来实现,但稍显复杂,此处为了方便演示采用添加新列方式):
ALTER TABLE tableA ADD cv_enc VARCHAR2(100);
第三步对cv列数据加密更新到cv_enc列中,原cv列清空处理
UPDATE tableA SET cv_enc=func_encrypt(CV),CV=NULL;
查询一下数据表,确认数据已完成加密操作,可以看到原CV列已清空,密文列CV_ENC存储了"加密"后的数据。
SQL> SELECT * FROM tableA;
CI CV CV_ENC
--- ---------- ----------
1 ypqwx
2 1qxwoqw
3 xvx2z
SQL>
第四步 将存储密文数据的数据表『tableA』修改名称『tableA_enc』
ALTER TABLE tableA RENAME TO tableA_enc;
第五步以『tableA_enc』创建与原数据表同名的视图对象『tableA』以达到数据访问的透明性,视图中添加对密文列的解密
CREATE VIEW tableA AS
SELECT ci,func_decrypt(cv_enc) AS CV
FROM tableA_enc;
第六步 在视图上创建INSERT、UPDATE、DELETE触发器(此处仅做INSERT触发器演示)来保证数据变更的透明性:
CREATE OR REPLACE TRIGGER TRII#TABLEA
INSTEAD OF INSERT ON AMARE.TABLEA FOR EACH ROW
DECLARE
l_sql VARCHAR2(128):='';
BEGIN
l_sql :='INSERT INTO AMARE.TABLEA_ENC(CI,CV,CV_ENC)
VALUES(:1,NULL,:2)';
EXECUTE IMMEDIATE l_sql USING
:NEW.CI,'',
func_encrypt(:NEW.CV);
END;
至此整个配置过程即完成,可进行如下简单验证
当数据写入时通过触发器调用UDF加密函数将数据加密后写入数据库,当读取数据时通过在视图内嵌UDF解密函数实现数据的解密返回。同时如有需要可在UDF加解密函数内可添加权限校验实现敏感数据访问的细粒度访问控制。UDF自定义函数加密数据访问流程示意如图所示。
通过SQL操作确认效果,首先查询原始数据
SQL> select * from tableA;
CI CV
--- ----------
1 china
2 tianjin
3 amare
SQL>
插入数据,数据会自动经过触发器将数据加密后写入密文数据表『tableA_enc』:
SQL> insert into tableA values(88,'hehe');
已创建 1 行。
查看密文表数据,确保新写入的数据以密文形式存储:
SQL> select * from tableA_enc;
CI CV CV_ENC
--- ---------- ----------
1 ypqwx
2 1qxwoqw
3 xvx2z
88 pzpz
SQL>
查询时通过视图内的解密函数解密数据,确认数据能正常解密。
SQL> select * from tableA;
CI CV
--- ----------
1 china
2 tianjin
3 amare
88 hehe
SQL>
此类产品通常应用在列级加密,且加密列数较少的场景下,即保证基本的敏感信息进行密文存储,减少了加密量,加密膨胀率较代理方式相当,取决于加密列数的多少,且较为容易支持国产加密算法以及第三方KMS的对接。此方式对于存量数据加密时长,同样与待加密的数据量成正比,加密时应避免数据的操作,所以当数B据量较大时容易造成较长的业务系统停机时间(可通过先对备数据库加密,然后切换后再对主数据库做加密),支持下作为查询条件、基于非密文字段为查询条件的精确查找、密文列不作为关联条件列的情况,使用时有较多限制情况。
表空间加密
表空间数据加密(Tablespace Data Encryption,简称为TDE)是基于数据库内置高级安全组件实现的数据文件的透明数据加解密技术,适用于Oracle、SQL Server、MySQL等默认内置此高级功能的数据库。数据在落盘时加密,在数据库被读取到内存中是明文,当攻击者通过非法拷贝等方式窃取数据文件时,由于未获得密钥而无法读取明文数据,从而起到保护数据库中数据的效果。除对MySQL一类开源数据库进行开发改造外,通常情况下此类方式不支持国产加密算法,但可通过HSM方式支持主密钥独立存储,保证密钥与数据分开存储,从而达到防止明文存储导致的数据泄露情况。表空间加密示意如图所示。
图 表空间加密示意图
下面以MySQL为例进行配置介绍
为实现MySQL表空间加密,需要先为安装keyring_file插件,该插件仅支持5.7.11以上版本,对于主备,读写分离等高可用环境,需要每个节点进行单独部署,当不需要加密功能时可选择卸载插件,但卸载前需将所有加密表进行解密。
安装插件前需要先在数据库服务器创建保存keyring的目录,并授予mysql用户相应权限,参考语句:
[root# mysql]# pwd
/usr/local/mysql
root#mysql]#mkdir keyring
root#mysql]#chmod -R 750 keyring/
root#mysql]#chown -R mysql.mysql keyring
安装keyring插件。
mysql> INSTALL PLUGIN keyring_file SONAME 'keyring_file.so';
为keyring插件指定目录
mysql> set global keyring_file_data='/usr/local/mysql/keyring/keyring';
* 此目录为默认目录,前一个为keyring需要手动创建的目录,后一个keyring为安装插件后自动生成的密钥文件,生成时文件为空,加密数据表后,将生成密钥
修改my.cnf,防止重启失效。在my.cnf文件的[mysqld]下添加
[mysqld]
early-plugin-load=keyring_file.so
keyring_file_data= /usr/local/mysql/keyring/
配置完成以后建议重启数据库,然后查看插件状态
SELECT PLUGIN_NAME, PLUGIN_STATUS
FROM INFORMATION_SCHEMA.PLUGINS
WHERE PLUGIN_NAME LIKE 'keyring%';
插件卸载
先检查是否还有未解密的密文表,如果存在密文表则建议先解密再卸载插件
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE CREATE_OPTIONS LIKE '%ENCRYPTION=\'Y\'%';
卸载keyring_file插件可使用以下语句:
mysql> UNINSTALL PLUGIN keyring_file;
数据表开启加密配置如下
mysql> alter table testenc encryption='Y';
数据表关闭加密配置如下
mysql> alter table testenc encryption='N';
可参考如下语句获取数据库的所有开启加密配置的数据表列表
SELECT TABLE_NAME,ENGINE,CASE CREATE_OPTIONS WHEN 'ENCRYPTION="Y"' THEN 1 ELSE 0 END AS ENCRYPT
FROM INFORMATION_SCHEMA.TABLES
WHERE CREATE_OPTIONS = 'ENCRYPTION="Y"'
数据表开启加密后,可使用UE等工具直接查看数据文件内容,加密前能查看明文数据,如图所示
加密后数据显示乱码,不能查看原始明文数据,如图所示
MySQL的keyring_file加密会有以下限制,使用时需要注意:
1. AES是唯一支持的加密算法。在InnodB表空间中,tablespace key使用Electronic Codebook (ECB)块加密模式,data使用Cipher Block Chaining (CBC)块加密模式.
2.改变一个有的ENCRYPTION属性使用的是ALGORITHM=COPY,不支持ALGORITHM=INPLACE
3.只支持加密存储在file-per-table的表,general tablespaces, system tablespace, undo log tablespaces,temporary tablespace不支持加密。
4.不能把一个加密的表空间移到一个不支持的表空间类型。
5.表空间加密只加密表空间中的数据,在redo log,undo log,binary log中的数据是不加密的。
6.数据表为innodb引擎的file-per-table型,且已经被加密的表不允许修改成其它存储引擎。
TDE表空间方式可实现数据加密的完全透明化,无须应用改造,对于模糊查询,范围查询支持好,且性能损坏很低,通常OLTP场景下损耗小于10%。但此类方式适用的数据库较少,且需要较高版本。敏感数据的访问控制通常由数据库本身控制,且多为数据表级,粒度较粗,且无法防控超级管理员账号,需额外控制类产品来实现独立的细粒度访问控制,如数据库防火墙。
后置加密方式
此类方式多为在操作系统的文件管理子系统上集成扩展加密插件,通过加密文件来实现数据加密,由操作系统或服务进程完成数据的读取、加密/解密工作,主要应对的是硬盘遗失一类的数据泄露威胁。当下Ext3等常用文件系统不支持加密功能的情况下,可以引入一种称为堆叠式加密文件系统(如eCryptfs、Waycryptic)来做一个加解密的转换层。它运行在普通文件系统之上,读加密文件时先通过下层普通文件系统将文件的密文读入内存,解密后再将明文返回到上层的用户进程;写加密文件时先将内存中的明文加密,然后传给下层普通文件系统,由它们真正地写入物理介质。堆叠式加密文件系统在实现相对容易且用户可以任意选择下层的普通文件系统来存放加密文件。在正常使用时,如果没有合法的使用身份、访问权限以及正确的安全通道,加密文件都将以密文状态被保护。文件层加密示意如图所示。
eCryptfs是Linux平台下的企业文件加密系统。它起源于Erez Zadok's Cryptfs,通过FiST框架实现层叠式文件系统的产生。其应用环境为ubuntu系统或类似依赖DEB的linux系统。eCryptfs文件加密配置过程可简单参考如下:
首先是安装该系统组件
amare@amare-ubuntu:~$ sudo apt-get install ecryptfs-utils
[sudo] password for amare:
Reading package lists... Done
此处省略安装过程
安装成功后,需创建一个目录作为加密目录
root@amare-ubuntu:/home/amare# mkdir testenc
然后挂载该目录
root@amare-ubuntu:/home/amare# mount -t ecryptfs testenc testenc
Passphrase:
Select cipher:
1) aes: blocksize = 16; min keysize = 16; max keysize = 32
2) blowfish: blocksize = 8; min keysize = 16; max keysize = 56
3) des3_ede: blocksize = 8; min keysize = 24; max keysize = 24
4) twofish: blocksize = 16; min keysize = 16; max keysize = 32
5) cast6: blocksize = 16; min keysize = 16; max keysize = 32
6) cast5: blocksize = 8; min keysize = 5; max keysize = 16
Selection [aes]:
Select key bytes:
1) 16
2) 32
3) 24
Selection [16]:
Enable plaintext passthrough (y/n) [n]:
Enable filename encryption (y/n) [n]:
Attempting to mount with the following options:
ecryptfs_unlink_sigs
ecryptfs_key_bytes=16
ecryptfs_cipher=aes
ecryptfs_sig=689c2383fc6d4a81
Would you like to proceed with the mount (yes/no)? : yes
Would you like to append sig [689c2383fc6d4a81] to
[/root/.ecryptfs/sig-cache.txt]
in order to avoid this warning in the future (yes/no)? : yes
Successfully appended new sig to user sig cache file
Mounted eCryptfs
确认已成功挂载
root@amare-ubuntu:/home/amare# df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 391M 1.8M 389M 1% /run
/dev/sda3 39G 7.1G 30G 20% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 4.0M 0 4.0M 0% /sys/fs/cgroup
/dev/sda2 512M 7.8M 505M 2% /boot/efi
tmpfs 391M 100K 391M 1% /run/user/1000
/home/amare/testenc 39G 7.1G 30G 20% /home/amare/testenc
root@amare-ubuntu:/home/amare#
在加密分区内创建一个a.txt文件并写入abcdefg内容(此处以一个txt文件作为演示,如果要对数据库加密,只需将数据文件创建到此目录即可)
root@amare-ubuntu:/home/amare# cd testenc/
root@amare-ubuntu:/home/amare/testenc# touch a.txt
root@amare-ubuntu:/home/amare/testenc# vi a.txt 写入abcdefg
root@amare-ubuntu:/home/amare/testenc# ls
b.txt
root@amare-ubuntu:/home/amare/testenc# cat a.txt
abcdefg
此时如果卸载改加密目录,再查看a.txt文件内容,将不可查看明文
root@amare-ubuntu:/home/amare/testenc# cd ..
root@amare-ubuntu:/home/amare# umount -t ecryptfs /home/amare/testenc
root@amare-ubuntu:/home/amare#
root@amare-ubuntu:/home/amare/testenc# cat b.txt 9OTϣu"3DUfw`¸{°ݙZ#<
C3i³Y7㿏߆¿ª矰ELFRµ{筐SOLEh#0(µ )
J½¤C$z-²·#FSʏ/2弛· i¸^Ibc϶?%c¿!µyDa·HQ,´Фi㞱%ܹa欓F9¿³ψѷ?xznho^|HgrKhDꃡ¨RB®i§(~ͺ-©|pӷ佒¥瘾
_ҡ_x=8d4곖3±ﶹ%c¶þLK6C¤Q3·¨ωª¡
ݻx¡ͻQ6ܶ¦\Dâ½
文件层加密方式与TDE方式类似,性能损耗低,数据无膨胀,无须应用系统改造,可支持国密及第三方KMS,只是加密移到了文件系统层,从而可以支持更多的数据库类型,甚至可支持Hadoop等大数据类组件。但此方式通常缺乏对密文的独立权限控制,当用户被授予表访问权限后即可访问全部敏感数据。加密时需要对整个数据文件加密,加密数据量大,且加密后加密效果不易确认。
总结归纳
至此我们可以对几种加密方式进行总结对比如下,希望此文能帮助读者更好的了解数据库透明加密技术,能在实际选型中起到帮助作用
加密方式的综合对比 | ||||
产 品 | 原 理 | 缺 点 | 优 点 | 适用场景 |
网关 加密 | 以前置代理模式部署在客户端和数据库服务器之间,所有数据访问流经网关处理,处理过程中将语句中敏感信息加密改写,将结果集进行解密返回给客户端/应用,例如:insert 'aaa' 改写成 insert '#¥%' | 1.所有数据流量都经网关处理,影响大 2.密文列不支持范围查询,只支持等值比较 3.密文列不支持关联字段, 如where A表.name=B表.name 4.密文列不支持运算,如sum(密文列) 5.数据空间膨胀率大 6.不支持存储过程/函数直接操作密文数据 | 1.直接改写SQL语句,不依赖具体数据库 2.业务连接代理IP和端口 3.加密效果显而易见 4.可支持{国密}等扩展算法 5. 可支持第三方KMS | 1.对敏感数据的使用很明确 2.敏感字段只有等值操作 3.密文列无运算操作 4.密文列不作为关联条件 5.数据量较少,建议千万以下 |
表空间加密 | 利用数据本身表空间/库加密的特性,在操作上进行产品化,使其原有的命令行操作转变为图像界面操作,降低使用者技术门槛 | 1.确认加密效果不直观,一般直接查看数据文件 2.不支持针对密文的独立权限控制 3.不支持国产加密算法 4.不支持第三方KMS 5.Oracle需要将表进行转存到密文表空间 | 1.加解密速度快,性能损失10%以内 2.加密机宕机,业务不影响 3.无须业务改造 4.数据无膨胀 | 1.数据量大,对性能要求高 2.敏感信息的具体应用方式不明确 3.无国密要求 4.无权控要求或通过额外产品控制 |
UDF加密 | 利用触发器+视图的后置插件代理模式,设置加密后,存量数据使用插件代理程序进行初始加密,增量写入数据由触发器将明文写入密文表,读取数据时通过与原表同名视图【内嵌解密函数】将命中数据解密返回 | 1.当密文列做where条件关联时,性能差 2.当对密文列进行统计或命中大批量数据时性能较差 3.密文数据占用空间膨胀较大 4.通用性差,每种数据库需单独实现 | 1.加密效果显而易见 2. 无须业务改造 3.密钥与数据独立存储 | 1.密文列不作为关联条件 2.敏感数据列应用方式明确:无统计操作,命中数据量少 3.数据量较少,建议千万以下 |
文件加密 | 基于操作系统文件层的加密,可对指定的文件进行加密 | 1.加密效果不易确认 2.整改文件加密,加密数据量大 3.无法直接对数据库用户进行权控 | 1.透明性好,无须应用改造 2.性能损耗低 3.兼容性高 | 1.对性能要求高 2.模糊查询,统计以及无法评估敏感字段使用方式 3.无权控要求或通过额外产品控制 |