【 OpenGauss源码学习 —— 列存储(CU)(三)】

列存储(CU)(三)

  • 概述
  • CUDataEncrypt 函数
    • isEncryptedCluster 函数
    • encryptBlockOrCUData 函数
  • CUDataDecrypt 函数
    • decryptBlockOrCUData 函数

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些学习资料

概述

  在【OpenGauss源码学习 —— 列存储(CU)(一)】中我们初步认识了 CU 的结构和作用,以及在【 OpenGauss源码学习 —— 列存储(CU)(二)】中学习了CU 的压缩和解压方法。本文则来了解一下对 CU 的加密和解密操作。

CUDataEncrypt 函数

  CUDataEncrypt 函数用于CU 数据写入磁盘之前,对数据进行加密。它首先检查 CU 是否已经被加密,然后计算待加密数据的长度,接着调用加密函数执行加密操作,最后更新 CU 的元数据,以标记 CU 已被加密。这是数据安全性的一个关键组成部分,确保数据在磁盘上存储时得到保护。其函数源码如下所示:(路径:src/gausskernel/storage/cstore/cu.cpp

/*
 * CU 数据加密。
 * 由于 CU 没有缓冲区并直接写入磁盘,因此没有并发线程问题。
 */
void CU::CUDataEncrypt(char* buf)
{
    // plainLength 用于存储未加密数据的长度,cipherLength 用于存储加密后数据的长度。
    size_t plainLength = 0;
    size_t cipherLength = 0;

    // 如果当前的 CU 处于加密状态,执行以下操作。
    if (isEncryptedCluster()) {
        // 计算待加密数据的长度。
        plainLength = m_cuSizeExcludePadding - GetCUHeaderSize() - m_bpNullCompressedSize;
        
        // 如果待加密数据的长度大于 0,说明有数据需要加密。
        if (plainLength > 0) {
            // 调用 encryptBlockOrCUData 函数执行实际的加密操作。
            // 参数包括输入缓冲区 buf、待加密数据的长度 plainLength,
            // 输出缓冲区 buf,以及指向 cipherLength 的指针,用于存储加密后数据的长度。
            encryptBlockOrCUData(buf, plainLength, buf, &cipherLength);
            
            // 更新 CU 的信息模式(infoMode)以指示 CU 已被加密。
            m_infoMode |= CU_ENCRYPT;
        }
    }
}

isEncryptedCluster 函数

  isEncryptedCluster 函数主要用于检查透明数据加密(Transparent Data Encryption,TDE是否在集群中启用,如果不启用相关条件不满足,它会返回 false,否则返回 true。如果透明数据加密被禁用或不受支持,函数可能会生成相应的日志或错误信息。总的来说,该函数用于检查是否在集群中启用了透明数据加密。以下是代码的详细注释:(路径:src/gausskernel/cbb/utils/aes/cipherfn.cpp

bool isEncryptedCluster()
{
    // 如果当前处于安全模式(security mode)并且透明加密字符串为空或未设置,那么透明加密被禁用。
    if (isSecurityMode && (g_instance.attr.attr_security.transparent_encrypted_string == NULL ||
                          *g_instance.attr.attr_security.transparent_encrypted_string == '\0')) {
        // 输出一条 DEBUG5 级别的日志,指示在 DWS(可能是数据仓库系统)中已禁用透明加密。
        ereport(DEBUG5, (errmsg("Transparent encryption disabled in DWS.\n")));
        return false;  // 返回 false 表示透明加密被禁用。
    } 
    // 如果不处于安全模式并且透明加密 KMS(Key Management Service)的 URL 为空或未设置,那么透明加密被禁用。
    else if ((!isSecurityMode) && (g_instance.attr.attr_common.transparent_encrypt_kms_url == NULL ||
                                  *g_instance.attr.attr_common.transparent_encrypt_kms_url == '\0')) {
        // 输出一条 DEBUG5 级别的日志,指示在 GaussDB(可能是高斯数据库)中已禁用透明加密。
        ereport(DEBUG5, (errmsg("Transparent encryption disabled in GaussDB.\n")));
        return false;  // 返回 false 表示透明加密被禁用。
    }

    // 检查是否已禁用透明数据加密特性。如果已禁用,抛出一个错误并返回 false。
    if (is_feature_disabled(TRANSPARENT_ENCRYPTION) == true) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("TDE is not supported.")));
        return false;  // 返回 false 表示透明加密被禁用。
    }
    
    // 如果以上条件都未触发,说明透明加密在当前集群中启用。
    return true;  // 返回 true 表示透明加密已启用。
}

encryptBlockOrCUData 函数

  encryptBlockOrCUData 函数用于对数据进行加密,并处理了加密失败时的重试机制,以确保数据安全性。它首先检查加密 DEK 和 IV 是否已初始化,然后根据指定的加密算法执行加密操作。如果加密失败,会尝试最多三次,然后根据不同情况,抛出 PANIC 级别的错误以避免进一步的处理。其函数源码如下所示:(路径:src/gausskernel/cbb/utils/aes/cipherfn.cpp

/*
 * encryptBlockOrCUData:
 * 加密数据块或列单元(CU),不包括头部和填充部分,用户数据必须加密。
 * 如果加密失败,重试三次,如果仍然失败,则退出进程,应使用 panic 而不是 fatal,
 * 以避免在检查点或后台写入进程(bgwriter)中挂起。
 * 
 * @IN plainText:明文数据及其长度
 * @OUT cipherText:密文数据及其长度
 * @RETURN: 无
 */
void encryptBlockOrCUData(const char* plainText, const size_t plainLength, char* cipherText, size_t* cipherLength)
{
    int retryCnt = 0;
    GS_UINT32 ret = 0;

    /*
     * 解密数据块或列单元(CU),不包括头部和填充部分,用户数据必须解密。
     * 如果解密失败,重试三次,如果仍然失败,则退出进程,应使用 panic 而不是 fatal,
     * 以避免在自动清理(autovacuum)过程中挂起。
     * 
     * @IN cipherText:密文数据及其长度
     * @OUT plainText:明文数据及其长度
     * @RETURN: 无
     */
    if ((trans_encrypt_dek == NULL || *trans_encrypt_dek == '\0') ||
        (trans_encrypt_iv == NULL || *trans_encrypt_iv == '\0')) {
        // 如果加密 DEK(Data Encryption Key)或 IV(Initialization Vector)未初始化,
        // 则抛出 PANIC 级别的错误,指示参数未初始化或 DEK 和 IV 的长度不等于 16 字节。
        ereport(PANIC,
            (errmsg("这是一个加密集群,但参数未初始化!"),
                errdetail("解密 DEK 或 IV 为空或两者的长度不等于16字节")));
    }

    do {
        // 根据加密算法执行加密操作,使用加密 DEK 和 IV。
        // 加密操作的结果存储在 cipherText 中,其长度存储在 cipherLength 中。
        if (g_tde_algo == TDE_ALGO_SM4_CTR_128) {
            ret = sm4_ctr_enc_partial_mode(
                plainText, plainLength, cipherText, cipherLength, trans_encrypt_dek, trans_encrypt_iv);
        } else {
            ret = aes_ctr_enc_partial_mode(
                plainText, plainLength, cipherText, cipherLength, trans_encrypt_dek, trans_encrypt_iv);
        }
        
        if (ret != 0) {
            retryCnt++;
        } else {
            // 如果加密成功,检查明文和密文的长度是否一致。
            if (plainLength != *cipherLength) {
                ereport(PANIC,
                    (errmsg("加密失败,返回码为 %u!", ret),
                        errdetail("密文长度不正确,明文长度为 %lu,密文长度为 %lu",
                            plainLength, *cipherLength)));
            }
            return;
        }
        
        if (retryCnt == 2) {
            // 如果重试三次后仍然加密失败,抛出 PANIC 级别的错误,指示返回码为无效参数或内部错误。
            ereport(PANIC,
                (errmsg("加密在重试三次后失败,错误码为 %u!", ret),
                    errdetail("返回码为无效参数或内部错误")));
        }
    } while (retryCnt < 3);
}

CUDataDecrypt 函数

  CUDataDecrypt 函数主要用于对已加密的 CU 数据进行解密。首先,它检查 CU 的信息模式,如果包含 CU_ENCRYPT 标志位,表示数据已被加密。然后,它计算密文数据的长度,以便调用解密函数 decryptBlockOrCUData 执行解密操作解密后的数据将替代原始数据存储在 buf 中,同时清除了 CU_ENCRYPT 标志,以表示数据已解密。这有助于在读取 CU 数据时对其进行解密,以便访问明文数据。其函数源码如下所示:(路径:src/gausskernel/storage/cstore/cu.cpp

/*
 * CU 数据解密。
 * 由于 CU 没有缓冲区并直接写入磁盘,所以没有并发线程问题。
 */
void CU::CUDataDecrypt(char* buf)
{
    // 检查 CU 的信息模式(infoMode),如果包含 CU_ENCRYPT 标志位,表示数据已被加密。
    if ((m_infoMode & CU_ENCRYPT) != 0) {
        size_t cipherLength = 0;
        size_t plainLength = 0;

        // 计算密文数据的长度,以便进行解密操作。
        cipherLength = m_cuSizeExcludePadding - GetCUHeaderSize() - m_bpNullCompressedSize;

        // 调用解密函数 decryptBlockOrCUData,对数据进行解密。
        // 解密后的数据存储在原始的 buf 中,解密后的数据长度存储在 plainLength 中。
        decryptBlockOrCUData(buf, cipherLength, buf, &plainLength);

        // 清除 CU_ENCRYPT 标志位,表示数据已解密。
        m_infoMode &= ~CU_ENCRYPT;
    }
}

decryptBlockOrCUData 函数

  decryptBlockOrCUData 函数用于对已加密的数据进行解密,它会检查解密 DEKIV 是否已初始化,并根据指定的解密算法执行解密操作。如果解密失败,它会尝试最多三次,然后根据不同情况,抛出错误以避免进一步的处理。这有助于确保在自动清理(autovacuum)等关键进程中不会挂起。函数源码如下所示:(路径:src/gausskernel/cbb/utils/aes/cipherfn.cpp

/*
 * 解密数据块或列单元,不包括头部和填充部分,用户数据必须解密。
 * 如果解密失败,重试三次,如果仍然失败,则退出进程,应使用 panic 而不是 fatal,
 * 以避免在自动清理(autovacuum)过程中挂起。
 * 
 * @IN cipherText:密文数据及其长度
 * @OUT plainText:明文数据及其长度
 * @RETURN: 无
 */
void decryptBlockOrCUData(const char* cipherText, const size_t cipherLength, char* plainText, size_t* plainLength)
{
    int retryCnt = 0;
    GS_UINT32 ret = 0;
    
    // 检查解密 DEK(Data Encryption Key)和 IV(Initialization Vector)是否已初始化,如果没有则抛出错误。
    if ((trans_encrypt_dek == NULL || *trans_encrypt_dek == '\0') ||
        (trans_encrypt_iv == NULL || *trans_encrypt_iv == '\0')) {
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("这是一个加密集群,但参数未初始化!"),
                errdetail("解密 DEK 或 IV 为空或两者的长度不等于16字节")));
    }

    do {
        // 根据解密算法执行解密操作,使用解密 DEK 和 IV。
        // 解密后的数据存储在 plainText 中,其长度存储在 plainLength 中。
        if (g_tde_algo == TDE_ALGO_SM4_CTR_128) {
            ret = sm4_ctr_dec_partial_mode(
                cipherText, cipherLength, plainText, plainLength, trans_encrypt_dek, trans_encrypt_iv);
        } else {
            ret = aes_ctr_dec_partial_mode(
                cipherText, cipherLength, plainText, plainLength, trans_encrypt_dek, trans_encrypt_iv);
        }
        
        if (ret != 0) {
            retryCnt++;
        } else {
            // 如果解密成功,检查密文和明文的长度是否一致。
            if (cipherLength != *plainLength) {
                ereport(ERROR,
                    (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
                        errmsg("解密失败,返回码为 %u!", ret),
                        errdetail("plainLength 长度不正确,cipherLength 为 %lu,plainLength 为 %lu",
                            cipherLength, *plainLength)));
            }
            return;
        }
        
        if (retryCnt == 2) {
            // 如果重试三次后仍然解密失败,抛出错误,指示返回码为无效参数或内部错误。
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("解密在重试三次后失败,错误码为 %u!", ret),
                    errdetail("返回码为无效参数或内部错误")));
        }
    } while (retryCnt < 3);
}

你可能感兴趣的:(OpenGauss,gaussdb,postgresql,数据库)