MySQL 触发器实现的敏感字段加密脱敏套装

在MySQL中对敏感字段进行加密、解密及脱敏处理

在处理数据库中的敏感信息时,如身份证号码、电话号码等,确保这些数据的安全性至关重要。本文将介绍如何使用MySQL触发器和内置函数(如AES加密/解密和Base64编码/解码)来实现对特定字段的加密、解密以及脱敏处理,同时保持表结构不变。

背景

直接存储明文敏感信息存在安全风险。为了解决这个问题,我们可以采用加密技术保护这些数据。然而,AES加密后的结果是二进制数据,不适合存储在VARCHAR类型的列中。因此,我们将使用Base64编码将加密后的二进制数据转换成文本格式,以便存储。

表结构

我们以一个名为ucam_person_account_info的表为例,该表包含多个可能需要加密或脱敏处理的字段:

CREATE TABLE `ucam_person_account_info` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `depositor_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '存款人姓名',
  `depositor_masked_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '脱敏存款人姓名',
  `depositor_id_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '存款人身份证件种类',
  `depositor_id_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '存款人身份证件号码',
  `depositor_id_masked_number` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '脱敏身份证件号码',
  `id_expiry_date` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '身份证件到期日',
  `issuing_authority_region_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发证机关所在地的地区代码',
  `depositor_nationality_region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '存款人国籍/地区',
  `depositor_gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '存款人性别',
  `birth_year` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '出生年份',
  `depositor_phone_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '存款人电话',
  `depositor_masked_phone_number` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '脱敏存款人电话',
  `is_opened_agent` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '是否代理开户',
  `account_bank_institution_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '开户银行金融机构编码',
  `account_number` varchar(35) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号唯一',
  `account_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '账户类型',
  `linked_type_i_account_number` varchar(35) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '绑定I类账户账号',
  `linked_type_i_account_bank_institution_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '绑定I类账户开户银行金融机构编码',
  `account_opening_date` datetime DEFAULT NULL COMMENT '开户日期',
  `account_closing_date` datetime DEFAULT NULL COMMENT '销户日期',
  `account_status` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '账户状态',
  `currency_type` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '币种类型',
  `currency` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '币种',
  `special_category_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '特殊种类账户',
  `account_information_type` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '信息类型',
  `account_opening_channel` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '开户渠道',
  `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
  `is_joint_account` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '是否为“联名账户”',
  `account_opening_region_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '开户地地区代码',
  `reserved_field1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '预留字段1',
  `reserved_field2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '预留字段2',
  `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='个人账户信息表';
准备工作

首先,我们需要设置一个加密密钥,并使用SHA-256哈希算法生成它:

SET @encryption_key = SHA2('your_secret_key', 256); -- 替换为你的密钥
创建触发器
加密触发器

在插入或更新记录时,我们将使用AES加密结合Base64编码来加密敏感字段:

DELIMITER $$

CREATE TRIGGER before_insert_encrypt_sensitive_data
BEFORE INSERT ON ucam_person_account_info FOR EACH ROW
BEGIN
    IF NEW.depositor_id_number IS NOT NULL THEN
        SET NEW.depositor_id_number = TO_BASE64(AES_ENCRYPT(NEW.depositor_id_number, @encryption_key));
    END IF;
    
    IF NEW.depositor_phone_number IS NOT NULL THEN
        SET NEW.depositor_phone_number = TO_BASE64(AES_ENCRYPT(NEW.depositor_phone_number, @encryption_key));
    END IF;
END$$

CREATE TRIGGER before_update_encrypt_sensitive_data
BEFORE UPDATE ON ucam_person_account_info FOR EACH ROW
BEGIN
    IF NEW.depositor_id_number IS NOT NULL THEN
        SET NEW.depositor_id_number = TO_BASE64(AES_ENCRYPT(NEW.depositor_id_number, @encryption_key));
    END IF;
    
    IF NEW.depositor_phone_number IS NOT NULL THEN
        SET NEW.depositor_phone_number = TO_BASE64(AES_ENCRYPT(NEW.depositor_phone_number, @encryption_key));
    END IF;
END$$

DELIMITER ;
脱敏触发器

对于需要脱敏显示的字段,在插入或更新时生成相应的脱敏数据:

DELIMITER $$

CREATE TRIGGER before_insert_mask_sensitive_data
BEFORE INSERT ON ucam_person_account_info FOR EACH ROW
BEGIN
    IF NEW.depositor_name IS NOT NULL THEN
        SET NEW.depositor_masked_name = CONCAT(SUBSTRING(NEW.depositor_name, 1, 1), REPEAT('*', LENGTH(NEW.depositor_name) - 1));
    END IF;
    
    IF NEW.depositor_id_number IS NOT NULL THEN
        SET NEW.depositor_id_masked_number = CONCAT(SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_id_number), @encryption_key), 1, 6), '****', SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_id_number), @encryption_key), 14));
    END IF;
    
    IF NEW.depositor_phone_number IS NOT NULL THEN
        SET NEW.depositor_masked_phone_number = CONCAT(SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_phone_number), @encryption_key), 1, 3), '****', SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_phone_number), @encryption_key), 8));
    END IF;
END$$

CREATE TRIGGER before_update_mask_sensitive_data
BEFORE UPDATE ON ucam_person_account_info FOR EACH ROW
BEGIN
    IF NEW.depositor_name IS NOT NULL THEN
        SET NEW.depositor_masked_name = CONCAT(SUBSTRING(NEW.depositor_name, 1, 1), REPEAT('*', LENGTH(NEW.depositor_name) - 1));
    END IF;
    
    IF NEW.depositor_id_number IS NOT NULL THEN
        SET NEW.depositor_id_masked_number = CONCAT(SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_id_number), @encryption_key), 1, 6), '****', SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_id_number), @encryption_key), 14));
    END IF;
    
    IF NEW.depositor_phone_number IS NOT NULL THEN
        SET NEW.depositor_masked_phone_number = CONCAT(SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_phone_number), @encryption_key), 1, 3), '****', SUBSTRING(AES_DECRYPT(FROM_BASE64(NEW.depositor_phone_number), @encryption_key), 8));
    END IF;
END$$

DELIMITER ;
查询时解密

查询加密的数据时,需要先用FROM_BASE64()函数将Base64编码的数据转换回二进制形式,然后使用AES_DECRYPT()解密:

SELECT 
    id,
    CAST(AES_DECRYPT(FROM_BASE64(depositor_id_number), @encryption_key) AS CHAR) AS depositor_id_number,
    CAST(AES_DECRYPT(FROM_BASE64(depositor_phone_number), @encryption_key) AS CHAR) AS depositor_phone_number,
    depositor_masked_name,
    depositor_id_masked_number,
    depositor_masked_phone_number,
    depositor_name,
    account_number,
    create_time,
    update_time
FROM 
    ucam_person_account_info;
示例操作
插入新记录
INSERT INTO ucam_person_account_info (
    depositor_name, 
    depositor_id_type, 
    depositor_id_number, 
    id_expiry_date, 
    issuing_authority_region_code, 
    depositor_nationality_region, 
    depositor_gender, 
    birth_year, 
    depositor_phone_number, 
    is_opened_agent, 
    account_bank_institution_code, 
    account_number, 
    account_type, 
    linked_type_i_account_number, 
    linked_type_i_account_bank_institution_code, 
    account_opening_date, 
    account_status, 
    currency_type, 
    currency, 
    special_category_account, 
    account_information_type, 
    account_opening_channel, 
    remarks, 
    is_joint_account, 
    account_opening_region_code
) VALUES (
    '张三',
    '身份证',
    '123456789012345678', -- 将被加密并Base64编码
    '2025-12-31',
    '110000',
    '中国',
    '男',
    '1990',
    '13800001234', -- 将被加密并Base64编码
    '否',
    'BANK001',
    'ACC123456789',
    '储蓄账户',
    'LINKACC123456789',
    'BANK002',
    NOW(),
    '正常',
    'CNY',
    '人民币',
    NULL,
    '个人信息',
    '线上',
    '备注信息',
    '否',
    '110101'
);
更新记录
UPDATE ucam_person_account_info 
SET 
    depositor_phone_number = '13900005678', -- 新的电话号码(将被加密并Base64编码)
    update_time = NOW()
WHERE 
    id = 1; -- 假设要更新id为1的记录

通过上述步骤,你可以在不修改现有表结构的情况下,利用MySQL的触发器机制对敏感字段进行加密、解密以及脱敏处理,从而增强数据的安全性和隐私保护。