openGauss数据库源码解析系列文章——数据安全技术(上)

Posted Gauss松鼠会

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了openGauss数据库源码解析系列文章——数据安全技术(上)相关的知识,希望对你有一定的参考价值。

在前面文章中介绍过“9.5 审计与追踪”,本篇我们介绍第9章 安全管理源码解析中“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);

存储用户用户密钥和派生密钥以及盐值。相关代码如下:

bool aes128EncryptSpeed(GS_UCHAR* PlainText, GS_UINT32 PlainLen, GS_UCHAR* Key, GS_UCHAR* RandSalt,
GS_UCHAR* CipherText, GS_UINT32* CipherLen)

……
/* 如果随机盐值和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的存储过程函数,通过用户输入的密文(注明文加密生成的密文)和密钥进行数据的解密操作。主要流程如图9-30所示。

                                       图9-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_UINT

以上是关于openGauss数据库源码解析系列文章——数据安全技术(上)的主要内容,如果未能解决你的问题,请参考以下文章

openGauss数据库源码解析系列文章——公共组件源码解析(上)

openGauss数据库源码解析系列文章——存储引擎源码解析

openGauss数据库源码解析系列文章——数据安全技术(上)

openGauss数据库源码解析系列文章——存储引擎源码解析

openGauss数据库源码解析系列文章—— 事务机制源码解析

openGauss数据库源码解析系列文章—— SQL引擎源解析