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


    声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
    本文主要参考了 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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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 表示透明加密已启用。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    我的C#基础
    Java项目:SSM餐厅点餐收银管理系统
    Java项目:农业信息管理系统(java+SSM+JSP+JS+HTML+mysql)
    一种速度引导的哈里斯鹰优化算法
    【Redis】01-如何在Linux下安装Reids保姆级教程
    Speedoffice(word)如何设置文字高亮显示
    C/C++问题:一个指针的大小是多少?怎么理解指针变量的存在
    Go中GUI库fyne
    UEC 利用代理/委托写一个生命组件
    IOI2018 werewolf 狼人 题解
  • 原文地址:https://blog.csdn.net/qq_43899283/article/details/134027683