openGauss database source code analysis series articles -- data security technology

Keywords: OpenSSL

Described in the previous article "9.5 audit and tracking" In this chapter, we introduce the wonderful contents related to "9.6 data security technology" in Chapter 9 security management source code analysis.
openGauss uses a variety of encryption and decryption technologies to improve the security of data in all links.

9.6.1 data encryption and decryption interface

When using the database, users not only need basic database security, but also encrypt and decrypt the imported data. openGauss provides a functional interface for encrypting and decrypting the data imported by users. Users can use this interface to encrypt and decrypt the data they think contains sensitive information.

  1. Data encryption interface
    The encryption function provided by openGauss is implemented based on the standard AES128 encryption algorithm. The encryption interface functions provided are:
gs_encrypt_aes128 (encryptstr, keystr)

keystr is the plaintext of the key provided by the user. The encryption function encrypts the encryptstr string through the standard AES128 encryption algorithm and returns the encrypted string. The length of keystr ranges from 1 to 16 bytes. The encryption data types supported by the encryption function include numeric type, character type, RAW in binary type, DATE in DATE / time type, TIMESTAMP, SMALLDATETIME, etc.
Length of ciphertext value returned by encryption function: at least 92 bytes, no more than 4*[(Len+68)/3] bytes, where Len is the length of data before encryption (unit: bytes). Use examples are as follows:

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)

The encryption interface function is through the function gs_encrypt_aes128 is implemented, and its code source files are: "builtins.h" and "cipherfn.cpp".
This function is a stored procedure function of openGauss, which encrypts the data through the plaintext and key entered by the user. The main process is shown in figure 9-29.

                               Figure 9-29  Data encryption process

The code of data encryption is introduced part by part below.
The relevant codes for converting plaintext into ciphertext are as follows:

bool gs_encrypt_aes_speed (GS_UCHAR* plaintext, GS_UCHAR* key, GS_UCHAR* ciphertext, GS_UINT32* cipherlen)
......

Obtain the random salt value and the derived key. The relevant codes are as follows:

/* bool gs_encrypt_aes_speed Function: */
/* Use random salt values that exist */
    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;

    /* Limit the number of times a random salt value is used */
    const GS_UINT64 random_salt_count_max = 24000000;

    if (random_salt_tag == false || random_salt_count > random_salt_count_max) {
   
        /* Encrypt to get random salt value */
        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);

Store user and derived keys and salt values. Relevant codes are as follows:

bool aes128EncryptSpeed(GS_UCHAR* PlainText, GS_UINT32 PlainLen, GS_UCHAR* Key, GS_UCHAR* RandSalt,
GS_UCHAR* CipherText, GS_UINT32* CipherLen)
{
   
......
/* If the random salt value and key are not updated, the existing derived key will be used. Otherwise, a new derived key 'will be generated */

    if (0 == memcmp(RandSalt, random_salt_saved, RANDOM_LEN)) {
   
        retval = 1;
        /* The mask holds user keys and derived keys */
        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;
        }

        /* Generate mac key for hmac */
        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;
        }

        /* Store random salt */
        errorno = memcpy_s(random_salt_saved, RANDOM_LEN, RandSalt, RANDOM_LEN);
        securec_check_c(errorno, "\0", "\0");

        /* Use random salt to mask the stored user key, derived key and 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]);
        }
}
}

Use a derived key to encrypt plaintext. Relevant codes are as follows:

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);

    /* Turn on fill mode */
    EVP_CIPHER_CTX_set_padding(ctx, 1);
    /* Process last 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;
    }
    /* The first byte is filled with "0x80" and the others with "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);

Add the encrypted information into the ciphertext header to facilitate decryption, and convert the encrypted information into a visible desensitization mode encode. Relevant codes are as follows:

/*Add init rand to the head of the ciphertext for decryption */
    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");
......
/*The ciphertext is encoded to achieve good display and decryption operation*/
    encodetext = SEC_encodeBase64((char*)ciphertext, ciphertextlen);

This completes the encryption process.

2 data decryption interface

The decryption interface functions provided by openGauss are:

gs_decrypt_aes128 (decryptstr, keystr)

Decrypt the decryptstr encrypted string with keystr as the user encryption key and return the decrypted string. The keystr used for decryption must be consistent with the keystr used for encryption before normal decryption. keystr must not be empty. Use examples are as follows.

opengauss=# SELECT gs_decrypt_aes128(name,'gaussDB123') FROM student005;
 gs_decrypt_aes128
-------------------
 zhangsan
(1 row)

The decryption interface function is through the function gs_decrypt_aes128 is implemented, and its code source files are: "builtins.h" and "cipherfn.cpp".
This function is a stored procedure function of openGauss, which decrypts data through the ciphertext (ciphertext generated by ciphertext encryption) and key entered by the user. The main process is shown in Figure 9-30.

                                       Figure 9-30  Data decryption process

The ciphertext and key to be decrypted are parsed and desensitized. Relevant codes are as follows:

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 decoding for decryption operation */
    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");

Convert the ciphertext into plaintext, and the relevant codes are as follows:

bool gs_decrypt_aes_speed(
GS_UCHAR* ciphertext, GS_UINT32 cipherlen, GS_UCHAR* key, GS_UCHAR* plaintext, GS_UINT32* plainlen)
......

Decode the ciphertext and separate the ciphertext and information. Relevant codes are as follows:

bool aes128DecryptSpeed(GS_UCHAR* CipherText, GS_UINT32 CipherLen, GS_UCHAR* Key, GS_UCHAR* RandSalt,
GS_UCHAR* PlainText, GS_UINT32* PlainLen)
......
    /*The ciphertext is divided into ciphertext part, AES vector part and mac vector part for decryption*/
    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 Is the usage used to separate two different areas_ frequency */  
 static THR_LOCAL GS_UINT32 insert_position = NUMBER_OF_SAVED_DERIVEKEYS / 2;

    /* Initialize 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");
    }

/*Find the corresponding derived vector in the order of use frequency*/
    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 {
   
                ;
            }
        }
    }

    /* If no derived vector exists, a new derived key is generated */
    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;
    }

Decrypt the ciphertext using a derived key. Relevant codes are as follows:

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);

This completes the decryption process.

For more database related highlights, please pay attention to:

Gauss squirrel Club (WeChat official account)

Gauss squirrel Club (wechat video Number)

Gauss squirrel club bar (Baidu Post Bar)

Posted by asmon on Mon, 06 Dec 2021 18:31:37 -0800