9.6.3 密态等值查询
除了传统的数据存储加密和数据脱敏等数据保护技术外,openGauss从1.1.0版本开始支持了一种全新的数据全生命周期保护方案:全密态数据库机制。在这种机制下数据在客户端就被加密,从客户端传输到数据库内核,到在内核中完成查询运算,到返回结果给客户端,数据始终处于加密状态,而数据加解密所需的密钥则由用户持有;从而实现了数据拥有者和数据处理者的数据权属分离,有效规避由内鬼和不可信第三方等威胁造成的数据泄漏风险。
本小节重点介绍全密态数据库的第一阶段能力——密态等值查询。与非加密数据库相比,密态等值查询主要提供以下能力。
(1) 数据加密:openGauss通过客户端驱动加密敏感数据,保证敏感数据明文不在除客户端驱动外的地方存在。遵循密钥分级原则将密钥分为数据加密密钥和密钥加密密钥,客户端驱动仅需要妥善保管密钥加密密钥即可保证只有自己才拥有解密数据密文的能力。
(2) 数据检索:openGauss支持在用户无感知的情况下,为其提供对数据库密文进行等值检索的能力。在数据加密阶段,openGauss会将与加密相关的元数据存储在系统表中,当处理敏感数据时,客户端会自动检索加密相关元数据并对数据进行加解密。
openGauss新增数据加解密表语法,通过采用驱动层过滤技术,在客户端的加密驱动中集成了SQL语法解析、密钥管理和敏感数据加解密等模块来处理相关语法。加密驱动源码流程如图9-37所示。
图9-37 客户端加密驱动源码流程
用户执行SQL查询语句时,通过Pqexec函数执行SQL语句,SQL语句在发送之前首先进入run_pre_query函数函数,通过前端解析器解析涉及密态的语法。然后在run_pre_statement函数中通过分类器对语法标签进行识别,进入对应语法的处理逻辑。在不同的处理逻辑函数中,查找出要替换的数据参数,并存储在结构体StatementData中,数据结构如图9-38所示。最后通过replace_raw_values函数重构SQL语句,将其发送给服务端。在PqgetResult函数接收到从服务端返回的数据时,若是加密数据类型,则用deprocess_value函数对加密数据进行解密。接收完数据后还需要在run_post_query函数中刷新相应的缓存。
图9-38 客户端加密驱动数据结构
openGauss密态数据库采用列级加密,用户在创建加密表的时候需要指定加密列的列加密密钥(Column Encryption Key,CEK)和加密类型,以确定该数据列以何种方式进行加密。同时,在创建表前,应该先创建客户端主密钥(client master key,CMK)。
整个加密步骤和语法可简化为如下3个阶段:创建客户端密钥CMK、创建列加密密钥CEK和创建加密表。下面将结合一个具体示例对密态等值查询特性进行详细介绍。
密态等值查询示例如下。
(1) 创建CMK客户端主密钥。
CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048);
(2) 创建CEK列加密密钥。
CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256);
(3) 创建加密表。
CREATE TABLE creditcard_info (id_number int, name text encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), gender varchar(10) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), salary float4 encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC),credit_card varchar(19) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC));
如示例所示,首先使用“CREATE CLIENT MASTER KEY”语法创建客户端主密钥,其所涉及的语法结构定义如下:
/* 保存创建客户端主密钥的语法信息 */
typedef struct CreateClientLogicGlobal {
NodeTag type;
List *global_key_name; /* 全密态数据库主密钥名称 */
List *global_setting_params; /* 全密态数据库主密钥参数,每一个元素是一个ClientLogicGlobalparam */
} CreateClientLogicGlobal;
/* 保存客户端主密钥参数信息 */
typedef struct ClientLogicGlobalParam {
NodeTag type;
ClientLogicGlobalProperty key; /* 键 */
char *value; /* 值 */
unsigned int len; /* 值长度 */
int location; /* 位置标记 */
} ClientLogicGlobalParam;
/* 保存客户端主密钥参数的key的枚举类型 */
typedef enum class ClientLogicGlobalProperty {
CLIENT_GLOBAL_FUNCTION, /* 默认为encryption */
CMK_KEY_STORE, /* 目前仅支持localkms */
CMK_KEY_PATH, /* 密钥存储路径 */
CMK_ALGORITHM /* 指定加密CEK的算法 */
} ClientLogicGlobalProperty;
CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048);
上面命令的参数说明为:
(1) KEY_STORE:指定管理CMK的组件或工具;目前仅支持localkms模式。
(2) KEY_PATH:一个KEY_STORE中存储了多个CMK,而KEY_PATH用于唯一标识CMK。
(3) ALGORITHM:CMK被用于加密CEK,该参数指定加密CEK的算法,即指定CMK的密钥类型。
客户端主密钥创建语法本质上是将CMK的元信息解析并保存在CreateClientLogicGlobal结构体中。其中global_key_name是密钥名称,global_setting_params是一个List结构,每个节点是一个ClientLogicGlobalParam结构,以键值的形式保存着密钥的信息。客户端先通过解析器“fe_raw_parser()”解析为CreateClientLogicGlobal结构体,对其参数进行校验并发送查询语句到服务端;服务端解析为CreateClientLogicGlobal结构体并检查用户namespace等权限,CMK元信息保存在系统表中。创建CMK的总体流程如图9-39所示。
图9-39 客户端主密钥CMK创建流程
有了主密钥CMK,可以基于此创建CEK,下面将对CREATE COLUMN ENCRYPTION KEY语句所涉及的语法结构定义进行逐一介绍。