9.6 数据安全技术
openGauss采用了多种加密解密技术来提升数据在各个环节的安全性。
9.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)
加密接口函数是通过函数gs_encrypt_aes128实现的,其代码源文件为:“builtins.h”和“cipherfn.cpp”。
该函数是一个openGauss的存储过程函数,通过用户输入的明文和密钥进行数据的加密操作。
主要流程如图9-29所示。
图9-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*
至此完成加密过程。