共计 5309 个字符,预计需要花费 14 分钟才能阅读完成。
导语 | 本文推选自腾讯云开发者社区 -【技思广益 · 腾讯技术人原创集】专栏。该专栏是腾讯云开发者社区为腾讯技术人与宽泛开发者打造的分享交换窗口。栏目邀约腾讯技术人分享原创的技术积淀,与宽泛开发者互启迪共成长。本文作者是腾讯高级开发工程师杨波。
背景
对互联网公司来说,数据安全始终是极为器重和敏感的话题。波及客户平安数据或者一些商业性敏感数据,如身份证号、手机号、卡号、客户号等个人信息如果被泄露进来,就会引发重大的数据安全危险。
在实在业务场景中,相干业务开发团队往往须要针对公司安全部门需要,自行履行并保护一套加解密零碎,自行保护的加解密零碎往往又面临着重构或批改危险。因而心愿实现一个通用的敏感数据处理框架,如何在不批改业务逻辑、业务 SQL 的状况下,透明化、平安低危险地实现无缝进行数据加解密革新。
加密框架实现计划比拟
先通过上面列表来探讨几种加密实现计划的优缺点:
通过下面几种计划比拟,得出实现一种加密框架至多有如下几个需要:
- 代码侵入性少。
- 接入成本低。
- 笼罩更多框架。
- 高性能,高可用。
- 反对存量数据加密。
能够发现对数据库驱动层革新,绝对其余几种计划毛病更少。那么是不是有一种计划,能够在不革新数据库驱动状况下,又能达到通明加解密数据的需要?
数据库拜访架构
计算机领域的任何问题都能够通过减少一个间接的中间层来解决,这自身就体现了分层的重要性。比方,Unix 零碎也是基于分层开发的,它能够大抵上分为三层,别离是内核、零碎调用、应用层。每一层都对下层封装实现细节,裸露形象的接口来调用。
对于数据库拜访也能够基于这样的软件思维来实现。因为各个厂商的数据库服务器差别比拟大,因而须要通过定义一种用于执行 SQL 语句的 API,为多种数据库提供对立拜访。比方 Java 的 JDBC,go 的 database,它们提供了一种基准和标准,据此能够构建更高级的工具和接口。数据库开发人员听从这种基准和标准,编写的应用程序称之为数据库驱动。
(一)面向切面编程
面向切面编程(AOP),是软件开发中的一个热点。通过预编译形式和运行期间动静代理实现程序性能的对立保护的一种技术。利用 AOP 能够将与业务非关联的性能剥离开来,比方权限认证,日志记录,性能监控,错误处理等。通过对指定办法执行前后进行拦挡形式,实现雷同性能的复用,防止对业务代码的侵入。因而 AOP 能够升高代码逻辑之间的耦合度,进步程序的可重用性,同时进步了开发的效率。
(二)通过 AOP 对业务 SQL 拦挡重写
假如实现一种数据库驱动 XDriver,它是对 XDBC 的规范 API 具体实现,上面通过伪代码来实现通过 AOP 对 XDriver 拦挡,从而对业务 SQL 重写。
public interface Database {
/**
* 执行 SQL
*/
ResultSet executeQuery(String sql);
}
public class XDriver implements Database {
@Override
public ResultSet executeQuery(String sql) {
ResultSet resultSet = null;
//TODO
return resultSet;
}
}
public class XDriverIntercepter implements Database {
private Database database;
public void setDatabase(Database database) {this.Database = database;}
@Override
public ResultSet executeQuery(String sql) {String newSql = rewriteSql(sql);
return database.executeQuery(newSql);
}
/**
* 重写 SQL
*/
private String rewriteSql(String sql) {
String newSql = "";
//TODO
return newSql;
}
}
如上代码所示假如有一张表叫 account,须要对表字段 mobile,address 加密,能够做如下解决:
- 在 account 表中新增 mobile_encryted,address_encrypted 字段。\
- 通过 XDriverIntercepter 对 XDriver 的 executeQuery 办法拦挡进行重写 SQL。
重写 SQL
因为 SQL 是一门欠缺的编程语言,因而对 SQL 的语法进行解析,与解析其余编程语言(如:Java 语言、C 语言、Go 语言等)并无本质区别。
(一)形象语法树
SQL 解析过程分为词法解析和语法解析。
词法剖析将 SQL 拆解为不可再分的原子符号,称为 Token。其中 Token 中蕴含关键字(也称符号)和非关键字。
语法分析就是生成形象语法树的过程。
例如,以下 SQL:
SELECT address FROM account WHERE mobile=?
解析之后的为形象语法树见下图:
将形象语法树转换成如下图:
将形象语法树反解析成以下 SQL:
–
SELECT address_encrypted AS address FROM account WHERE mobile_encrypted=?
并发管制
在高并发场景下,不心愿对 SQL 反复解析,这样会影响性能。但如果只是简略对 SQL 解析进行互斥,那么在高并发场景下,会造成大量申请处于阻塞期待状态。因而须要对并发管制做优化:
- 通过分段锁的形式,缩小锁的粒度,提高效率。
- SQL 解析后果放入缓存,防止反复解析。
(二)缓存淘汰策略
业务 SQL 复杂多变,如果对每种 SQL 解析后果都缓存,会影响到内存占用。因而须要对不同的 SQL 有相应的缓存淘汰策略:
- 参数化查问 SQL 解析后果永恒缓存。
- 字符串拼接 SQL 解析后果默认缓存 1 秒,如果 1 秒内再次被拜访,将会刷新淘汰工夫。缓存 1 秒防止高并发场景下大量反复解析 SQL 造成的内存压力。
因而倡议应用参数化查问 SQL 进步性能。
配置管理
通过 AOP 拦挡,解析,重写业务 SQL,实现透明化对数据加密。通过并发和缓存管制保障框架的高性能。这样根本实现一个加密框架基本功能。然而对于业务应用,还有很多个性化需要。其中比拟重要的有如下几点:
- 加密算法
- 密钥获取
- 定义须要加密的表和字段
以上 1 和 2 除了默认实现形式,还须要反对自定义算法扩大性能。
因而须要定义一种形式,将上述配置集中于一起,能够更加无效进行治理。
(一)SPI 机制
Service Provider Interface (SPI) 是一种为了被第三方实现或扩大的 API。它能够用于实现框架扩大或组件替换。用户通过实现框架提供的相应接口,动静将用户自定义的实现类加载其中,从而在放弃框架架构完整性与性能稳定性的状况下,满足用户不同场景的理论需要。
框架提供了内置的加密和密钥获取实现类,用户只需进行配置即可应用;另一方面,为了满足用户不同场景的需要,还凋谢了相干加密和密钥获取接口,用户可根据接口提供具体实现类。再进行简略配置,即可让框架调用用户自定义的加解密计划:
- EncryptAlgorithm 用于实现自定义加密算法:该接口提供 encrypt(),decrypt() 两种办法。在用户进行 INSERT, DELETE, UPDATE 时,框架依据配置规定,调用 encrypt() 将数据加密后存储到数据库, 而在 SELECT 时,则调用 decrypt() 办法将从数据库中取出的加密数据进行逆向解密,最终将原始数据返回给用户。
- KeyGenerate 用于实现自定义密钥获取:该接口提供 generate() 办法。因为平安思考,并不举荐加密密钥简略放在本地,一旦密钥透露将有可能造成数据透露的危险。因而倡议将密钥中心化托管解决,而后在具体实现类通过 generate() 近程获取密钥。
除了以上接口,后续也能够退出数据脱敏等接口。
(二)配置形式定义
只管通过 SPI 机制能够满足用户个性化需要,然而用户对于如何将本人的实现类以及其它规定通过编码方式配置到框架中,仍然须要学习的老本。因而须要定义一种配置形式,使用户只须要参考应用文档,简略配置就能够应用框架。因为 yaml 是目前比拟通用的配置格局,框架的配置也是基于 yaml 去定义。具体的配置内容如下:
encrypt_rule:
query_with_cipher_column: true # 是否应用密文列查问
encrypt_algorithms: # 定义加密算法
- name: AES-CBC # 加密算法名称
props: # 自定义参数,
key1: xx #
encrypt_key_generates: # 定义密钥生成
- name: LOCAL # 密钥生成名
props: # 自定义参数,
key1: xx #
encryptors: # 加密模块设置
encryptor-a:
algorithm: # 加密算法
name: AES-CBC # 指定应用的加密算法
symmetric_key: # 密钥生成
name: LOCAL # 指定应用的密钥生成
tables: # 加密表设置
account: # 表名
columns:
mobile: #逻辑列
plain_column: mobile # 明文列
cipher_column: mobile_encrypted # 密文列
encryptor: encryptor-a # 指定应用的加密模块
key_type: mobile # 列类型,每个类型对应一个密钥
(三)SQL 解决流程
因而在不思考并发场景,在减少配置管理状况下,框架对一条 SQL 解决流程将转换成如下图:
存量数据加密
对于已上线且存在历史明文数据的业务,须要实现业务零碎较为平安、平滑地在明文与密文数据间的迁徙。
在配置中有定义以下三个参数:
- query_with_cipher_column 是否应用密文列查问。
- plain_column 明文列。
- cipher_column 密文列。
假如有一张历史旧表叫 account,须要对字段 mobile,address 加密,能够通过以下几个步骤来实现对加密数据平滑迁徙
(一)已上线业务革新 - 迁徙前
对 account 表新增 mobile_encrypted,address_encrypted 用于寄存密文数据,并做如下配置:
- plain_column 设为 mobile,address。
- cipher_column 设为 mobile_encrypted,address_encrypted。
- query_with_cipher_column 设为 false。
此时数据处理流程将如下图:
(二)已上线业务革新 - 迁徙中
通过上图能够看到,当 query_with_cipher_column 设为 false 时,明文列和密文列双写,通过明文列查问。用户此时能够通过脚本,将存量数据荡涤加密。而后将 query_with_cipher_column 设为 true。
此时数据处理流程将如下图:
(三)已上线业务革新 - 迁徙后
通过上图能够看到,当 query_with_cipher_column 设为 true 时,明文列和密文列双写,通过密文列查问。当零碎运行一段时间稳固当前,此时能够将 account 表中明文列删除,并将配置中的 plain_column 删除。最终数据只会被加密存储在密文列。
此时数据处理流程将如下图:
(四)已上线业务革新流程
因而对已上线业务数据加密革新流程如下图:
总结
当初再总结文章结尾提到几点需要,看看是如何解决的:
- 代码侵入性少:通过 AOP 形式将 SQL 重写,加解密逻辑与业务代码和数据库框架独立解耦。
- 接入成本低:用户无需批改原有业务逻辑,只须要进行大量批改和配置,就能够将框架集成进来。
- 笼罩更多框架:基于数据库驱动层的拦挡,因而不影响下层 ORM 框架的选型。
- 高性能,高可用:通过分段锁,缓存等性能优化伎俩保障框架对业务性能简直无影响。去中心化形式,将极大升高异样造成对业务线影响。
- 反对存量数据加密:通过明文列和密文列灵便的配置,实现业务零碎较为平安、平滑地在明文与密文数据间的迁徙。
如果你是腾讯技术内容创作者,腾讯云开发者社区诚邀您退出【腾讯云原创分享打算】,支付礼品,助力职级降职。