在数据库的日常使用中,用户手机号、身份证号、银行卡号这类敏感数据如果直接明文存储,一旦出现数据库泄露或者越权查询的情况,会导致严重的隐私泄露问题。因此针对SQL层面的敏感数据做列级加密和脱敏是非常必要的防护手段。
什么是SQL列级加密
列级加密是指只对数据库表中指定的敏感字段做加密处理,而非对整个表或者整个数据库加密。这种方式的优势是灵活度高,不会影响非敏感字段的查询性能,同时能精准保护核心数据。常见的列级加密可以在应用层实现,也可以在数据库层通过内置函数实现。
应用层列级加密实践
应用层加密的逻辑是在数据写入数据库前,先对敏感字段做加密处理,读取时再做解密。这种方式不依赖数据库特性,适配所有关系型数据库。以下是一个Java实现的手机号加密示例:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class ColumnEncryptUtil {
// 加密密钥,实际生产环境需要妥善保管,不要硬编码
private static final String SECRET_KEY = "1234567890abcdef";
// 加密算法
private static final String ALGORITHM = "AES";
// 加密方法
public static String encrypt(String content) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(content.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 解密方法
public static String decrypt(String encryptedContent) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedContent);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
return new String(decryptedBytes);
}
public static void main(String[] args) throws Exception {
String phone = "13800138000";
String encryptedPhone = encrypt(phone);
System.out.println("加密后手机号:" + encryptedPhone);
System.out.println("解密后手机号:" + decrypt(encryptedPhone));
}
}
使用这种方式时,写入SQL的语句中敏感字段已经是加密后的字符串,比如插入用户表的手机号字段时,SQL会是如下形式:
INSERT INTO user_info (user_id, user_name, phone) VALUES (1, '张三', '加密后的字符串');
数据库层列级加密实践
很多数据库本身提供了列级加密的内置函数,比如MySQL的AES_ENCRYPT和AES_DECRYPT函数,可以直接在SQL层面完成加密解密。以下是MySQL的使用示例:
-- 创建用户表,phone字段存储加密后的二进制数据
CREATE TABLE user_info (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
phone VARBINARY(255)
);
-- 插入数据时使用AES_ENCRYPT加密手机号,密钥为'my_secret_key'
INSERT INTO user_info (user_id, user_name, phone)
VALUES (1, '张三', AES_ENCRYPT('13800138000', 'my_secret_key'));
-- 查询数据时使用AES_DECRYPT解密手机号
SELECT user_id, user_name, AES_DECRYPT(phone, 'my_secret_key') AS phone
FROM user_info;
需要注意的是,数据库层加密的密钥如果明文写在SQL中会有泄露风险,生产环境可以结合数据库的密钥管理功能来存储密钥。
SQL数据脱敏实践
数据脱敏是指在查询展示敏感数据时,不返回完整的信息,而是隐藏部分内容,比如手机号展示为138****8000。脱敏可以在应用层实现,也可以在SQL查询时直接处理。
SQL查询层脱敏实现
可以在查询SQL中直接对敏感字段做截断和替换处理,以下是一个MySQL的脱敏示例,对手机号做中间四位隐藏:
-- 查询时脱敏手机号,格式为前三位+****+后四位
SELECT
user_id,
user_name,
CONCAT(
LEFT(AES_DECRYPT(phone, 'my_secret_key'), 3),
'****',
RIGHT(AES_DECRYPT(phone, 'my_secret_key'), 4)
) AS masked_phone
FROM user_info;
如果是已经明文存储的手机号字段,也可以直接用字段名替换上面的解密函数结果:
SELECT
user_id,
user_name,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS masked_phone
FROM user_info;
不同场景的脱敏规则参考
不同敏感数据的脱敏规则可以根据业务需求调整,常见的规则如下:
- 身份证号:保留前6位和后4位,中间替换为8个星号,比如110101********1234
- 银行卡号:保留后4位,前面替换为对应数量的星号,比如**** **** **** 1234
- 邮箱:保留邮箱前缀的第一个字符和域名部分,中间替换为星号,比如a***@ipipp.com
列级加密与脱敏的结合实践
在实际业务中,通常会同时采用列级加密和脱敏方案,实现存储和使用的双重防护。整体的流程如下:
- 数据写入时,应用层或者数据库层对敏感字段做加密,存储到数据库对应列中
- 数据查询时,先解密加密字段,再按照脱敏规则处理后再返回给前端或者调用方
- 对于内部运维等需要查看完整数据的场景,可以配置单独的权限,允许返回未脱敏的解密数据
以下是一个完整的查询示例,先解密再脱敏:
-- 先解密phone字段,再做脱敏处理
SELECT
user_id,
user_name,
CASE
-- 判断当前用户是否有查看明文权限,这里用简单的角色判断示例
WHEN @current_user_role = 'admin' THEN AES_DECRYPT(phone, 'my_secret_key')
ELSE CONCAT(LEFT(AES_DECRYPT(phone, 'my_secret_key'), 3), '****', RIGHT(AES_DECRYPT(phone, 'my_secret_key'), 4))
END AS phone
FROM user_info;
注意事项
在实施列级加密和脱敏时,需要注意以下几点:
- 加密密钥需要妥善保管,不要硬编码在代码或者SQL中,建议使用专业的密钥管理服务
- 列级加密会影响对应字段的查询性能,无法对加密字段直接做范围查询或者模糊查询,如果有这类需求可以考虑应用层处理或者额外的索引方案
- 脱敏规则需要根据业务场景调整,避免脱敏后影响正常的业务使用
- 定期对加密和脱敏方案做安全审计,及时发现潜在的风险