openGauss数据库源码解析系列文章—安全管理源码解析三(上篇)
五、审计与追踪
审计机制和审计追踪机制能够对用户的日常行为进行记录和分析,实现规避风险、提高安全性。
5.1 审计日志设计
审计内容的记录方式通常有两种:记录到数据库的表中、记录到OS文件中。openGauss采用记录到OS文件中(即审计日志)的方式来保存审计结果,审计日志文件夹受操作系统权限保护,默认只有初始化用户可以读写,从数据库安全角度出发,保证了审计结果的可靠性。日志文件的存储目录由audit_directory参数指定。
openGauss审计日志每条记录包括time、type、result、userid、username、database、client_conninfo、object_name、detail_info、node_name、thread_id、local_port、remote_port共13个字段。图23为审计日志的单条记录示例。
图23 审计记录示例
对审计日志文件进行读写的函数的代码主要位于“pgaudit.cpp”文件中,其中主要包括两类函数:审计文件的读、写、更新函数;审计记录的增、删、查接口。
首先介绍审计文件的数据结构,如图24所示。
openGauss的审计日志采用文件的方式存储在指定目录中。通过查看目录,可以发现日志主要包括两类文件:形如0_adt的审计文件以及名为index_table索引文件。
图24 审计文件结构
以adt结尾的审计文件中,每一条审计记录对应一个AuditData结构体。数据结构AuditData代码如下:
typedef struct AuditData {
AuditMsgHdr header; /* 记录文件头,存储记录的标识、大小等信息 */
AuditType type; /* 审计类型 */
AuditResult result; /* 执行结果 */
char varstr[1]; /* 二进制格式存储的具体审计信息 */
} AuditData;
其中AuditMsgHdr记录着审计记录的标识信息,数据结构AuditMsgHdr的代码如下:
typedef struct AuditMsgHdr {
char signature[2]; /* 审计记录标识,目前固定为AUDIT前两个字符’A’和’U’ */
uint16 version; /* 版本信息,目前固定为0 */
uint16 fields; /* 审计记录字段数,目前为13 */
uint16 flags; /* 记录有效性标识,如果被删除则标记为DEAD */
pg_time_t time; /* 审计记录创建时间 */
uint32 size; /* 审计信息占字节长度 */
} AuditMsgHdr;
AuditData的其他结构存储着审计记录的审计信息,AuditType为审计类型,目前有38种类型。AuditResult为执行的结果,有AUDIT_UNKNOWN、AUDIT_OK、AUDIT_FAILED三种结果。其余的各项信息,均通过二进制的方式写入到varstr中。
审计日志有关的另一个文件为索引文件index_table,其中记录着审计文件的数量、审计日志文件编号、审计文件修改日期等信息。其数据结构AuditIndexTable代码如下:
typedef struct AuditIndexTable {
uint32 maxnum; /* 审计目录下审计文件个数的最大值 */
uint32 begidx; /* 审计文件开始编号 */
uint32 curidx; /* 当前使用的审计文件编号 */
uint32 count; /* 当前审计文件的总数 */
pg_time_t last_audit_time; /* 最后一次写入审计记录的时间 */
AuditIndexItem data[1]; /* 审计文件指针 */
} AuditIndexTable;
索引文件中每一个AuditIndexItem对应一个审计文件,其数据结构AuditIndexItem的代码如下:
typedef struct AuditIndexItem {
pg_time_t ctime; /* 审计文件创建时间 */
uint32 filenum; /* 审计文件编号 */
uint32 filesize; /* 审计文件占空间大小 */
} AuditIndexItem;
审计文件的读、写类函数如auditfile_open、auditfile_rotate等函数实现较简单,读者可以直接阅读源码。
下面主要介绍日志文件的结构和日志记录的增、删、查接口。
审计记录的写入接口为audit_report函数。该函数的原型为:
void audit_report(AuditType type, AuditResult result, const char* object_name, const char* detail_info);
其中入参type、result、object_name、detail_info分别对应审计日志记录中的相应字段,审计日志中的其余9个字段均为函数在执行时从全局变量中获取。
audit_report函数的执行主要分为3个部分,首先会检查审计的各项开关,判断是否需要审计该操作;然后根据传入的参数、全局变量中的参数以及当前时间,生成审计日志所需的信息并拼接成字符串;最后调用审计日志文件读写接口,将审计日志写入文件中。
审计记录查询接口为pg_query_audit函数,该函数为数据库内置函数,可供用户直接调用,调用形式为:
SELECT * FROM pg_query_audit (timestamptz startime,timestamptz endtime, audit_log);
入参为需要查询审计记录的起始时间和终止时间以及审计日志文件所在的物理路径。当不指定audit_log时,默认查看连接当前实例的审计日志信息。
审计记录的删除接口为pg_delete_audit函数,该函数为数据库内置函数,可供用户直接调用,调用形式为:
SELECT * FROM pg_delete_audit (timestamptz startime,timestamptz endtime);
入参为需要被删除审计记录的起始时间和终止时间。该函数通过调用pgaudit_delete_file函数来将审计日志文件中,startime与endtime之间的审计记录标记为AUDIT_TUPLE_DEAD,达到删除审计日志的效果,而不实际删除审计记录的物理数据。带来的效果是执行该函数审计日志文件大小不会减小。
5.2 审计执行
1. 执行原理
审计机制是openGauss的内置安全能力之一,openGauss提供对用户发起的SQL行为审计和追踪能力,支持针对DDL、DML语句和关键行为(登录、退出、系统启动、恢复)的审计。在每个工作线程初始化阶段把审计模块加载至线程中,其审计的执行原理是把审计函数赋给SQL生命周期不同阶段的Hook(钩子),当线程执行至SQL处理流程的特定阶段后会进行审计执行判定逻辑。审计模块加载关键代码如下:
void pgaudit_agent_init(void) {
…
/* DDL、DML语句审计Hook赋值, 赋值结束后标识审计模块已在此线程加载 */
prev_ExecutorEnd = ExecutorEnd_hook;
ExecutorEnd_hook = pgaudit_ExecutorEnd;
prev_ProcessUtility = ProcessUtility_hook;
ProcessUtility_hook = (ProcessUtility_hook_type)pgaudit_ProcessUtility;
u_sess->exec_cxt.g_pgaudit_agent_attached = true;
}
SQL语句在执行到ProcessUtility_hook和ExecutorEnd_hook函数指针时,会分别进入到已预置好的审计流程中。这两个函数指针的位置在SQL进入执行器执行之前,具体关系如图25所示。
图25 审计执行关系图
如图25所示,在线程初始化阶段审计模块已加载完毕。SQL经过优化器得到计划树,此时审计模块的pgaudit_ExecutorEnd函数和pgaudit_ProcessUtility函数分别进行DML和DDL语句的分析,如果和已设置审计策略相匹配,则会调用审计日志接口,生成对应的审计日志。对于系统变更类的审计直接内置于相应行为的内核代码中。
2. 关键执行流程
1) 系统变更类审计执行
pgaudit_system_recovery_ok
pgaudit_system_start_ok
pgaudit_system_stop_ok
pgaudit_user_login
pgaudit_user_logout
pgaudit_system_switchover_ok
pgaudit_user_no_privileges
pgaudit_lock_or_unlock_user
以上为openGauss支持系统变更类的审计执行函数,对于此类审计函数均嵌入内核相应调用流程中,下面以审计用户登入退出pgaudit_user_login函数为例说明其主体流程。
图26 登入审计执行流程
图26为服务端校验客户端登入时的主要流程。以登录失败场景为例,首先根据配置文件和客户端IP和用户信息确认采用的认证方式(包括sha256和SSL认证等);然后根据不同的认证方式采用不同的认证流程和客户端进行交互完成认证身份流程;如果认证失败,则线程进入退出流程上报客户端,此时调用pgaudit_user_login获取当前访问数据库名称和详细信息,并记录登录失败相关的审计日志。关键代码如下:
/* 拼接登录失败时候的详细信息,包括数据库名称和用户名 */
rc = snprintf_s(details,
PGAUDIT_MAXLENGTH,
PGAUDIT_MAXLENGTH - 1,
"login db(%s)failed,authentication for user(%s)failed",
port->database_name,
port->user_name);
securec_check_ss(rc, "\0", "\0");
/* 调用登入审计函数,记录审计日志 */
pgaudit_user_login(FALSE, port->database_name, details);
/* 退出当前线程 */
ereport(FATAL, (errcode(errcode_return), errmsg(errstr, port->user_name)))
登入审计日志接口pgaudit_user_login则主要完成审计日志记录接口需要参数的拼接,相关代码如下:
void pgaudit_user_login(bool login_ok, const char* object_name, const char* detaisinfo)
{
AuditType audit_type;
AuditResult audit_result;
Assert(detaisinfo);
/* 审计类型和审计结果拼装 */
if (login_ok) {
audit_type = AUDIT_LOGIN_SUCCESS;
audit_result = AUDIT_OK;
} else {
audit_type = AUDIT_LOGIN_FAILED;
audit_result = AUDIT_FAILED;
}
/* 直接调用审计日志记录接口 */
audit_report(audit_type, audit_result, object_name, detaisinfo);
}
2) DDL、DML语句审计执行
依据“1. 执行原理”节的描述,DDL、DML语句的执行分别由于pgaudit_ProcessUtility函数、pgaudit_ExecutorEnd函数来承载。此处首先介绍函数pgaudit_ProcessUtility函数,其主体结构代码如下:
static void pgaudit_ProcessUtility(Node* parsetree, const char* queryString, ...)
{
/* 适配不同编译选项 */
...
/* 开始匹配不同的DDL语句 */
switch (nodeTag(parsetree)) {
case T_CreateStmt: {
/* CREATE table语句审计执行 */
CreateStmt* createtablestmt = (CreateStmt*)(parsetree);
pgaudit_ddl_table(createtablestmt->relation->relname, queryString);
} break;
case T_AlterTableStmt: {
AlterTableStmt* altertablestmt = (AlterTableStmt*)(parsetree); /* Audit alter table */
if (altertablestmt->relkind == OBJECT_SEQUENCE) {
pgaudit_ddl_sequence(altertablestmt->relation->relname, queryString);
} else {
pgaudit_ddl_table(altertablestmt->relation->relname, queryString);
}
} break;
/* 匹配其他DDL类型语句逻辑 */
...
}}
DDL审计执行函数关键入参parsetree用于识别审计日志类型(create/drop/alter等操作)。入参queryString保存原始执行SQL语句,用于记录审计日志,略去非关键流程。此函数主要根据判断nodeTag所归属的DDL操作类型,进入不同的审计执行逻辑。以T_CreateStmt为例,识别当前语句CREATE table则进入pgaudit_ddl_table逻辑进行审计日志执行并最终记录审计日志。
图27 DDL审计执行流程
如图27所示,首先从当前SQL语句中获取执行对象类别校验其相应的审计开关是否开启(可以通过GUC参数audit_system_object控制)。当前支持开启的全量对象代码如下:
typedef enum {
DDL_DATABASE = 0,
DDL_SCHEMA,
DDL_USER,
DDL_TABLE,
DDL_INDEX,
DDL_VIEW,
DDL_TRIGGER,
DDL_FUNCTION,
DDL_TABLESPACE,
DDL_RESOURCEPOOL,
DDL_WORKLOAD,
DDL_SERVERFORHADOOP,
DDL_DATASOURCE,
DDL_NODEGROUP,
DDL_ROWLEVELSECURITY,
DDL_TYPE,
DDL_TEXTSEARCH,
DDL_DIRECTORY,
DDL_SYNONYM
} DDLType;
如果DDL操作的对象审计已开启则进行审计日志记录流程,在调用审计日志记录函数audit_report之前需要对包含密码的SQL语句进行脱敏处理。将包含密码的语句中(CREATE role/user)密码替换成‘********’用于隐藏敏感信息,至此针对CREATE DDL语句的审计执行完成。其他类型DDL语句主体流程一致,不做赘述。
下面介绍针对DML语句审计执行逻辑pgaudit_ExecutorEnd函数,整体调用流程如图28所示。
图28 DML审计执行流程
首先判断SQL查询语句所归属的查询类型。以CMD_SELECT类型为例,先获取查询对象的object_name用于审计日志记录中访问对象的记录,然后调用pgaudit_dml_table函数。相关代码如下:
case CMD_SELECT:
object_name = pgaudit_get_relation_name(queryDesc->estate->es_range_table);
pgaudit_dml_table_select(object_name, queryDesc->sourceText);
和DDL的记录一样,同样会对敏感信息进行脱敏后调用审计日志记录接口audit_report,至此对DML语句的审计日志执行完成。
六、数据安全技术
openGauss采用了多种加密解密技术来提升数据在各个环节的安全性。
6.1 数据加解密接口
用户在使用数据库时,除了需要基本的数据库安全之外,还会对导入的数据进行加密和解密的操作。openGauss提供了针对用户导入数据进行加密和解密的功能接口,用户使用该接口可以对其认为包含敏感信息的数据进行加密和解密操作。
1. 数据加密接口
openGauss提供的加密功能是基于标准的AES128加密算法进行实现,提供的加密接口函数为:
gs_encrypt_aes128 (encryptstr, keystr)
其中keystr是用户提供的密钥明文,加密函数通过标准的AES128加密算法对encryptstr字符串进行加密,并返回加密后的字符串。keystr的长度范围为1~16字节。加密函数支持的加密数据类型包括数值类型、字符类型、二进制类型中的RAW、日期/时间类型中的DATE、TIMESTAMP、SMALLDATETIME等。
加密函数返回的的密文值长度:至少为92字节,不超过4*[(Len+68)/3]字节,其中Len为加密前数据长度(单位为字节)。
使用示例如下:
opengauss=# CREATE table student005 (name text);
opengauss=# INSERT into student005 values(gs_encrypt_aes128('zhangsan','gaussDB123'));
INSERT 0 1
opengauss=# SELECT * FROM student005;
name
----------------------------------------------------------------------------------------------
NrGJdx8pDgvUSE2NN7eM5mFDnSSJ41fq31/0SI2+4kABgOnCu9H2vkjpvcAdG/AhJ8OrBn906Xaj6oqyEHsTbcTvjrU=
(1 row)
图29 数据加密流程
数据加密的代码如下逐个部分介绍。
开始将明文转换为密文过程,相关代码如下:
bool gs_encrypt_aes_speed (GS_UCHAR* plaintext, GS_UCHAR* key, GS_UCHAR* ciphertext, GS_UINT32* cipherlen)
……
获取随机salt值,获取派生密钥,相关代码如下:
/* bool gs_encrypt_aes_speed函数: */
/* 使用存在的随机salt值 */
static THR_LOCAL GS_UCHAR random_salt_saved[RANDOM_LEN] = {0};
static THR_LOCAL bool random_salt_tag = false;
static THR_LOCAL GS_UINT64 random_salt_count = 0;
/* 对随机salt值的使用次数限制 */
const GS_UINT64 random_salt_count_max = 24000000;
if (random_salt_tag == false || random_salt_count > random_salt_count_max) {
/* 加密获取随机salt值 */
retval = RAND_bytes(init_rand, RANDOM_LEN);
if (retval != 1) {
(void)fprintf(stderr, _("generate random key failed,errcode:%u\n"), retval);
return false;
}
random_salt_tag = true;
errorno = memcpy_s(random_salt_saved, RANDOM_LEN, init_rand, RANDOM_LEN);
securec_check(errorno, "\0", "\0");
random_salt_count = 0;
} else {
errorno = memcpy_s(init_rand, RANDOM_LEN, random_salt_saved, RANDOM_LEN);
securec_check(errorno, "\0", "\0");
random_salt_count++;
}
plainlen = strlen((const char*)plaintext);
存储用户用户密钥和派生密钥以及salt值。相关代码如下:
bool aes128EncryptSpeed(GS_UCHAR* PlainText, GS_UINT32 PlainLen, GS_UCHAR* Key, GS_UCHAR* RandSalt,
GS_UCHAR* CipherText, GS_UINT32* CipherLen)
{
……
/* 如果随机salt和key没有更新就使用已经存在的派生key,否则就生成新的派生key ’ */
if (0 == memcmp(RandSalt, random_salt_saved, RANDOM_LEN)) {
retval = 1;
/* 掩码保存用户key和派生key */
for (GS_UINT32 i = 0; i < RANDOM_LEN; ++i) {
if (user_key[i] == ((char)input_saved[i] ^ (char)random_salt_saved[i])) {
derive_key[i] = ((char)derive_vector_saved[i] ^ (char)random_salt_saved[i]);
mac_key[i] = ((char)mac_vector_saved[i] ^ (char)random_salt_saved[i]);
} else {
retval = 0;
}
}
}
if (!retval) {
retval = PKCS5_PBKDF2_HMAC(
(char*)Key, keylen, RandSalt, RANDOM_LEN, ITERATE_TIMES, (EVP_MD*)EVP_sha256(), RANDOM_LEN, derive_key);
if (!retval) {
(void)fprintf(stderr, _("generate the derived key failed,errcode:%u\n"), retval);
……
return false;
}
/* 为hmac生成mac key */
retval = PKCS5_PBKDF2_HMAC((char*)user_key,
RANDOM_LEN,
RandSalt,
RANDOM_LEN,
MAC_ITERATE_TIMES,
(EVP_MD*)EVP_sha256(),
RANDOM_LEN,
mac_key);
if (!retval) {
(void)fprintf(stderr, _("generate the mac key failed,errcode:%u\n"), retval);
……
return false;
}
/* 存储随机salt */
errorno = memcpy_s(random_salt_saved, RANDOM_LEN, RandSalt, RANDOM_LEN);
securec_check_c(errorno, "\0", "\0");
/* 使用随机salt为存储的user key、派生key和mac key做掩码处理 */
for (GS_UINT32 i = 0; i < RANDOM_LEN; ++i) {
input_saved[i] = ((char)user_key[i] ^ (char)random_salt_saved[i]);
derive_vector_saved[i] = ((char)derive_key[i] ^ (char)random_salt_saved[i]);
mac_vector_saved[i] = ((char)mac_key[i] ^ (char)random_salt_saved[i]);
}
}
}
使用派生密钥去加密明文。相关代码如下:
GS_UINT32 CRYPT_encrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen, const GS_UCHAR* pucIV,
GS_UINT32 ulIVLen, GS_UCHAR* pucPlainText, GS_UINT32 ulPlainLen, GS_UCHAR* pucCipherText, GS_UINT32* pulCLen)
……
cipher = get_evp_cipher_by_id(ulAlgId);
if (cipher == NULL) {
(void)fprintf(stderr, ("invalid ulAlgType for cipher,please check it!\n"));
return 1;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
(void)fprintf(stderr, ("ERROR in EVP_CIPHER_CTX_new:\n"));
return 1;
}
EVP_CipherInit_ex(ctx, cipher, NULL, pucKey, pucIV, 1);
/* 开启填充模式 */
EVP_CIPHER_CTX_set_padding(ctx, 1);
/* 处理最后一个block */
blocksize = EVP_CIPHER_CTX_block_size(ctx);
if (blocksize == 0) {
(void)fprintf(stderr, ("invalid blocksize, ERROR in EVP_CIPHER_CTX_block_size\n"));
return 1;
}
nInbufferLen = ulPlainLen % blocksize;
padding_size = blocksize - nInbufferLen;
pchInbuffer = (unsigned char*)OPENSSL_malloc(blocksize);
if (pchInbuffer == NULL) {
(void)fprintf(stderr, _("malloc failed\n"));
return 1;
}
/* 第一个字节使用“0x80”去填充,其他的使用“0x00”填充 */
rc = memcpy_s(pchInbuffer, blocksize, pucPlainText + (ulPlainLen - nInbufferLen), nInbufferLen);
securec_check_c(rc, "\0", "\0");
rc = memset_s(pchInbuffer + nInbufferLen, padding_size, 0, padding_size);
securec_check_c(rc, "\0", "\0");
pchInbuffer[nInbufferLen] = 0x80;
EVP_CIPHER_CTX_set_padding(ctx, 0);
将加密信息加入密文头方便解密,并转换加密信息为可见的脱敏模式encode。相关代码如下:
/*将init rand添加到密文的头部进行解密使用 */
GS_UCHAR mac_temp[MAC_LEN] = {0};
errorno = memcpy_s(mac_temp, MAC_LEN, ciphertext + *cipherlen - MAC_LEN, MAC_LEN);
securec_check(errorno, "\0", "\0");
errorno = memcpy_s(ciphertext + *cipherlen - MAC_LEN + RANDOM_LEN, MAC_LEN, mac_temp, MAC_LEN);
securec_check(errorno, "\0", "\0");
GS_UCHAR temp[RANDOM_LEN] = {0};
for (GS_UINT32 i = (*cipherlen - MAC_LEN) / RANDOM_LEN; i >= 1; --i) {
errorno = memcpy_s(temp, RANDOM_LEN, ciphertext + (i - 1) * RANDOM_LEN, RANDOM_LEN);
securec_check(errorno, "\0", "\0");
errorno = memcpy_s(ciphertext + i * RANDOM_LEN, RANDOM_LEN, temp, RANDOM_LEN);
securec_check(errorno, "\0", "\0");
}
errorno = memcpy_s(ciphertext, RANDOM_LEN, init_rand, RANDOM_LEN);
securec_check(errorno, "\0", "\0");
*cipherlen = *cipherlen + RANDOM_LEN;
errorno = memset_s(temp, RANDOM_LEN, '\0', RANDOM_LEN);
securec_check(errorno, "\0", "\0");
……
/*对密文进行编码,以实现良好的显示和解密操作*/
encodetext = SEC_encodeBase64((char*)ciphertext, ciphertextlen);
至此完成加密过程。
2. 数据解密接口
openGauss提供的解密接口函数为:
gs_decrypt_aes128 (decryptstr, keystr)
以keystr为用户加密密钥对decryptstr加密字符串进行解密,返回解密后的字符串。解密使用的keystr必须保证与加密时使用的keystr一致才能正常解密。keystr不得为空。
使用示例如下。
opengauss=# SELECT gs_decrypt_aes128(name,'gaussDB123') FROM student005;
gs_decrypt_aes128
-------------------
zhangsan
(1 row)
解密接口函数是通过函数gs_decrypt_aes128实现的,其代码源文件为:“builtins.h”和“cipherfn.cpp”。
该函数是一个openGauss的存储过程函数,通过用户输入的密文(注明文加密生成的密文)和密钥进行数据的解密操作。
主要流程如图30所示。
图30 数据解密流程
数据解密的代码如下逐个部分介绍。
通过存储过程的入参解析出需要解密的密文和密钥,并进行脱敏的decode操作。相关代码如下:
decodetext = (GS_UCHAR*)(text_to_cstring(PG_GETARG_TEXT_P(0)));
key = (GS_UCHAR*)(text_to_cstring(PG_GETARG_TEXT_P(1)));
keylen = strlen((const char*)key);
/*为解密操作去做密文解码 */
ciphertext = (GS_UCHAR*)(SEC_decodeBase64((char*)decodetext, &decodetextlen));
if ((ciphertext == NULL) || (decodetextlen <= RANDOM_LEN)) {
if (ciphertext != NULL) {
OPENSSL_free(ciphertext);
ciphertext = NULL;
}
errorno = memset_s(decodetext, decodetextlen, '\0', decodetextlen);
securec_check(errorno, "\0", "\0");
pfree_ext(decodetext);
errorno = memset_s(key, keylen, '\0', keylen);
securec_check(errorno, "\0", "\0");
pfree_ext(key);
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
errmsg("Decode the cipher text failed or the ciphertext is too short!")));
}
errorno = memset_s(decodetext, decodetextlen, '\0', decodetextlen);
securec_check(errorno, "\0", "\0");
pfree_ext(decodetext);
plaintext = (GS_UCHAR*)palloc(decodetextlen);
errorno = memset_s(plaintext, decodetextlen, '\0', decodetextlen);
securec_check(errorno, "\0", "\0");
开始将密文转换为明文过程。相关代码如下:
bool gs_decrypt_aes_speed(
GS_UCHAR* ciphertext, GS_UINT32 cipherlen, GS_UCHAR* key, GS_UCHAR* plaintext, GS_UINT32* plainlen)
……
将密文进行解码操作,分离密文和信息两个部分。相关代码如下:
bool aes128DecryptSpeed(GS_UCHAR* CipherText, GS_UINT32 CipherLen, GS_UCHAR* Key, GS_UCHAR* RandSalt,
GS_UCHAR* PlainText, GS_UINT32* PlainLen)
……
/*将密文分成,密文部分、AES向量部分和mac向量部分进行解密操作*/
GS_UINT32 cipherpartlen = CipherLen - RANDOM_LEN - MAC_LEN;
errorno = memcpy_s(aes_vector, RANDOM_LEN, CipherText + cipherpartlen, RANDOM_LEN);
securec_check_c(errorno, "", "");
errorno = memcpy_s(mac_text_saved, MAC_LEN, CipherText + cipherpartlen + RANDOM_LEN, MAC_LEN);
securec_check_c(errorno, "", "");
static THR_LOCAL GS_UINT32 usage_frequency[NUMBER_OF_SAVED_DERIVEKEYS] = {0};
/* insert_position是用来分隔两个不同的区域的usage_frequency */
static THR_LOCAL GS_UINT32 insert_position = NUMBER_OF_SAVED_DERIVEKEYS / 2;
/* 初始化usage_frequency */
if (usage_frequency[0] == 0 && usage_frequency[NUMBER_OF_SAVED_DERIVEKEYS - 1] == 0) {
for (GS_UINT32 i = 0; i < NUMBER_OF_SAVED_DERIVEKEYS; ++i)
usage_frequency[i] = i;
}
errorno = memcpy_s(user_key, RANDOM_LEN, Key, keylen);
securec_check_c(errorno, "\0", "\0");
if (keylen < RANDOM_LEN) {
errorno = memset_s(user_key + keylen, RANDOM_LEN - keylen, '\0', RANDOM_LEN - keylen);
securec_check_c(errorno, "\0", "\0");
}
/*按照使用频率进行顺序查找对应的派生向量*/
for (GS_UINT32 i = 0; i < NUMBER_OF_SAVED_DERIVEKEYS && !DERIVEKEY_FOUND; ++i) {
if (0 == memcmp(random_salt_used[usage_frequency[i]], RandSalt, RANDOM_LEN)) {
DERIVEKEY_FOUND = 1;
for (GS_UINT32 j = 0; j < RANDOM_LEN; ++j) {
GS_UCHAR mask = (char)random_salt_used[usage_frequency[i]][j];
if (user_key[j] == ((char)user_input_used[usage_frequency[i]][j] ^ (char)mask)) {
decrypt_key[j] = ((char)derive_vector_used[usage_frequency[i]][j] ^ (char)mask);
mac_key[j] = ((char)mac_vector_used[usage_frequency[i]][j] ^ (char)mask);
} else {
DERIVEKEY_FOUND = 0;
}
}
if (i > 0 && i < NUMBER_OF_SAVED_DERIVEKEYS / 2 && DERIVEKEY_FOUND) {
GS_UINT32 temp = usage_frequency[i - 1];
usage_frequency[i - 1] = usage_frequency[i];
usage_frequency[i] = temp;
} else if (i >= NUMBER_OF_SAVED_DERIVEKEYS / 2 && DERIVEKEY_FOUND) {
GS_UINT32 temp = usage_frequency[NUMBER_OF_SAVED_DERIVEKEYS / 2 - 1];
usage_frequency[NUMBER_OF_SAVED_DERIVEKEYS / 2 - 1] = usage_frequency[i];
usage_frequency[i] = temp;
} else {
;
}
}
}
/* 如果没有派生向量存在,就生成新的派生key */
if (!DERIVEKEY_FOUND) {
retval = PKCS5_PBKDF2_HMAC(
(char*)Key, keylen, RandSalt, RANDOM_LEN, ITERATE_TIMES, (EVP_MD*)EVP_sha256(), RANDOM_LEN, decrypt_key);
if (!retval) {
……
return false;
}
retval = PKCS5_PBKDF2_HMAC((char*)user_key,
RANDOM_LEN,
RandSalt,
RANDOM_LEN,
MAC_ITERATE_TIMES,
(EVP_MD*)EVP_sha256(),
RANDOM_LEN,
mac_key);
if (!retval) {
……
return false;
}
errorno = memcpy_s(random_salt_used[usage_frequency[insert_position]], RANDOM_LEN, RandSalt, RANDOM_LEN);
securec_check_c(errorno, "\0", "\0");
for (GS_UINT32 j = 0; j < RANDOM_LEN; ++j) {
GS_UCHAR mask = random_salt_used[usage_frequency[insert_position]][j];
user_input_used[usage_frequency[insert_position]][j] = ((char)user_key[j] ^ (char)mask);
derive_vector_used[usage_frequency[insert_position]][j] = ((char)decrypt_key[j] ^ (char)mask);
mac_vector_used[usage_frequency[insert_position]][j] = ((char)mac_key[j] ^ (char)mask);
}
insert_position = (insert_position + 1) % (NUMBER_OF_SAVED_DERIVEKEYS / 2) + NUMBER_OF_SAVED_DERIVEKEYS / 2;
}
使用派生密钥去解密密文。相关代码如下:
GS_UINT32 CRYPT_decrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen, const GS_UCHAR* pucIV,
GS_UINT32 ulIVLen, GS_UCHAR* pucCipherText, GS_UINT32 ulCLen, GS_UCHAR* pucPlainText, GS_UINT32* pulPLen)
……
cipher = get_evp_cipher_by_id(ulAlgId);
if (cipher == NULL) {
(void)fprintf(stderr, ("invalid ulAlgType for cipher,please check it!\n"));
return 1;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
(void)fprintf(stderr, ("ERROR in EVP_CIPHER_CTX_new:\n"));
return 1;
}
EVP_CipherInit_ex(ctx, cipher, NULL, pucKey, pucIV, 0);
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (!EVP_DecryptUpdate(ctx, pucPlainText, &dec_num, pucCipherText, ulCLen)) {
(void)fprintf(stderr, ("ERROR in EVP_DecryptUpdate\n"));
goto err;
}
*pulPLen = dec_num;
if (!EVP_DecryptFinal(ctx, pucPlainText + dec_num, &dec_num)) {
(void)fprintf(stderr, ("ERROR in EVP_DecryptFinal\n"));
goto err;
}
*pulPLen += dec_num;
/* padding bytes of the last block need to be removed */
blocksize = EVP_CIPHER_CTX_block_size(ctx);
至此完成解密过程。
文章转载自公众号:openGauss