MONI后台管理系统-数据敏感字段存储加密

前言:
    在我们数据库中,存在很多的敏感数据,如用户表中,存在用户电话、身份证号、邮箱等属于用户的敏感信息,我们通常在存入数据库后,将其进行加密存储,以此来保证数据安全性。
    加密的方式有很多,如摘要加密、对称加密、非对称加密等,这里不做描述。因为我们的数据加密存储后,查询出来还需解密进行显示,我们使用对称加密的方式来实现加密。
    常用的对称加密有DES、AES、3DES以及国密算法SM4等。前者我们比较推荐使用AES,那么我们这次的对称加密实现将使用Aes以及SM4。
    而对于代码来说,我们在存储时需要加密,查询时又需要解密,如果每个方法我们都去这样操作,冗余代码会非常的多,是非常不合适的。因为我们集成的是Mybatis-plus框架,我们可以自定义TypeHandler字段类型处理器来实现我们这一需求。类型处理器(TypeHandler)扮演着 JavaType 与 JdbcType 之间转换的桥梁角色。它们用于在执行 SQL 语句时,将 Java 对象的值设置到 PreparedStatement 中,或者从 ResultSet 或 CallableStatement 中取出值。在类型处理器中,我们针对特定字段集成加解密的逻辑即可。
    本项目代码已上传至gitee Omni-Admin

1. 定义EncryptHandler字段类型处理器。

package com.omni.admin.mybatis;

import com.omni.admin.context.SpringContext;
import com.omni.admin.encryption.EncryptionTypeEnum;
import com.omni.admin.encryption.symmetricalencryption.SymmetricalEncryption;
import com.omni.admin.encryption.symmetricalencryption.SymmetricalEncryptionFactory;
import com.omni.admin.properties.EncryptionProperties;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 数据加解密拦截
 */
public class EncryptHandler extends BaseTypeHandler<String> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, encrypt(parameter));
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return decrypt(rs.getString(columnName));
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return decrypt(rs.getString(columnIndex));
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return decrypt(cs.getString(columnIndex));
    }

    /**
     * 加密
     *  */
    private String encrypt(String params){
        EncryptionProperties encryptionProperties = SpringContext.getBean(EncryptionProperties.class);
        Boolean enableStorage = encryptionProperties.getEnableStorage();
        if (Boolean.FALSE.equals(enableStorage)) return params;
        SymmetricalEncryption symmetricalEncryption = getSymmetricalEncryption(encryptionProperties.getStorageEncryption());
        return symmetricalEncryption.encrypt(params);
    }

    /**
     * 解密
     */
    private String decrypt(String params){
        EncryptionProperties encryptionProperties = SpringContext.getBean(EncryptionProperties.class);
        Boolean enableStorage = encryptionProperties.getEnableStorage();
        if (Boolean.FALSE.equals(enableStorage)) return params;
        SymmetricalEncryption symmetricalEncryption = getSymmetricalEncryption(encryptionProperties.getStorageEncryption());
        return symmetricalEncryption.decrypt(params);
    }

    private SymmetricalEncryption getSymmetricalEncryption(EncryptionTypeEnum encryptionTypeEnum){
        return SymmetricalEncryptionFactory.getSymmetricalEncryption(encryptionTypeEnum);
    }
}

2. 使用该处理器

我们以用户实体为例,当中有电话、身份证号和你邮箱。在@TableField注解中,加入EncryptHandler类型处理器即可。另外还需在@TableName注解中,添加autoResultMap = true属性,这样在查询时才能进行解密。

@EqualsAndHashCode(callSuper = true)
@Data
@TableName(value = "omni_user_info",autoResultMap = true)
public class UserInfo extends BaseEntity {
    /**
     * 用户电话
     */
    @Sensitize(rule = SensitizeRuleEnums.MOBILE_PHONE)
    @TableField(value = "F_PHONE",typeHandler = EncryptHandler.class)
    private String phone;

    /**
     * 用户邮箱
     */
    @TableField(value = "F_EMAIL",typeHandler = EncryptHandler.class)
    private String email;


    /**
     * 用户身份证
     */
    @TableField(value = "F_ID_CARD",typeHandler = EncryptHandler.class)
    private String idCard;
 }

3. 查看效果

我们可在看到数据库中目前存储的就是密文了。
在这里插入图片描述

4. 加密的实现

这里我们考虑到设计以及后续的拓展性,我们对称加密的实现将会有多个,使用工厂模式来设计编码。

4.1 创建对称机密接口类SymmetricalEncryption

该接口空中提供了加密接口和解密接口,由子类去实现。

package com.omni.admin.encryption.symmetricalencryption;

public interface SymmetricalEncryption {
    /**
     * 加密
     * @param plaintext 明文
     * @param key 密钥
     * @return 加密后的值
     */
    String encrypt(String  plaintext, String key);


    /**
     * 加密
     * @param plaintext 明文
     * @return 加密后的值
     */
    String encrypt(String  plaintext);


    /**
     * 解密
     * @param ciphertext 密文
     * @param key 密钥
     * @return 解密后的明文
     */
    String decrypt(String ciphertext, String key);

    /**
     * 解密
     * @param ciphertext 密文
     * @return 解密后的明文
     */
    String decrypt(String ciphertext);
}

4.2 AES加解密实现SymmetricalEncryptionAes类

maven依赖

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.70</version>
        </dependency>
4.3 加解密工具类EncryptUtils
package com.omni.admin.utils;

import com.omni.admin.exception.ExceptionStatus;
import com.omni.admin.exception.OmniException;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;

public class EncryptUtils {
    static{
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final Logger log = LoggerFactory.getLogger(EncryptUtils.class);

    public static String sm4Encrypt(String plainString, String key) {
        return encrypt(plainString,key,"SM4");
    }

    public static String aesEncrypt(String plainString, String key) {
        return encrypt(plainString,key,"AES");
    }

    public static String sm4Decrypt(String plainString, String key) {
        return decrypt(plainString,key,"SM4");
    }

    public static String aesDecrypt(String plainString, String key) {
        return decrypt(plainString,key,"AES");
    }

    private static String encrypt(String plainString, String key,String algorithm) {
        try {
            // 创建密钥规范
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
            // 获取Cipher对象实例(BC中SM4默认使用ECB模式和PKCS5Padding填充方式,因此下列模式和填充方式无需指定)
            Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding");
            // 初始化Cipher为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            // 获取加密byte数组
            byte[] cipherBytes = cipher.doFinal(plainString.getBytes(StandardCharsets.UTF_8));
            // 输出为Base64编码
            return Base64.encodeBase64String(cipherBytes);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new OmniException(ExceptionStatus.ENCRYPT_ERROR);
        }
    }

    private static String decrypt(String ciphertext, String key,String algorithm) {
        try {
            // 创建密钥规范
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
            // 获取Cipher对象实例(Java8中AES默认使用ECB模式和PKCS5Padding填充方式,因此下列模式和填充方式无需指定)
            Cipher cipher = Cipher.getInstance(String.format("%s/ECB/PKCS5Padding", algorithm));
            // 初始化Cipher为解密模式
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            // 获取加密byte数组
            byte[] cipherBytes = cipher.doFinal(Base64.decodeBase64(ciphertext));
            // 输出为字符串
            return new String(cipherBytes);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new OmniException(ExceptionStatus.ENCRYPT_ERROR);
        }
    }
}

package com.omni.admin.encryption.symmetricalencryption;
import com.omni.admin.utils.EncryptUtils;


public class SymmetricalEncryptionAes implements SymmetricalEncryption {

    public static final String KEY = "cf410532f92a47de";

    @Override
    public String encrypt(String plaintext, String key) {
        return EncryptUtils.aesEncrypt(plaintext,key);
    }

    @Override
    public String encrypt(String plaintext) {
        return encrypt(plaintext, KEY);
    }

    @Override
    public String decrypt(String ciphertext, String key) {
        return EncryptUtils.aesDecrypt(ciphertext,key);
    }

    @Override
    public String decrypt(String ciphertext) {
        return decrypt(ciphertext, KEY);
    }
}
4.4 国密算法SymmetricalEncryptionSM4类
package com.omni.admin.encryption.symmetricalencryption;

import com.omni.admin.utils.EncryptUtils;

public class SymmetricalEncryptionSM4 implements SymmetricalEncryption {

    public static final String KEY = "cf410532f92a47de";

    @Override
    public String encrypt(String plaintext, String key) {
        return EncryptUtils.sm4Encrypt(plaintext,key);
    }

    @Override
    public String encrypt(String plaintext) {
        return encrypt(plaintext, KEY);
    }

    @Override
    public String decrypt(String ciphertext, String key) {
        return EncryptUtils.sm4Decrypt(ciphertext,key);
    }

    @Override
    public String decrypt(String ciphertext) {
        return decrypt(ciphertext, KEY);
    }
}
4.5 加解密工厂类SymmetricalEncryptionFactory
package com.omni.admin.encryption.symmetricalencryption;

import com.omni.admin.encryption.EncryptionTypeEnum;
import com.omni.admin.exception.ExceptionStatus;
import com.omni.admin.exception.OmniException;

public class SymmetricalEncryptionFactory {

    private SymmetricalEncryptionFactory() {
    }

    public static SymmetricalEncryption getSymmetricalEncryption(EncryptionTypeEnum encryptionType) {
        if (EncryptionTypeEnum.AES.equals(encryptionType)){
            return new SymmetricalEncryptionAes();
        }

        if (EncryptionTypeEnum.SM4.equals(encryptionType)){
            return new SymmetricalEncryptionSM4();
        }
        throw new OmniException(ExceptionStatus.NOT_FOUND_SIGNATURE);
    }
}

4.6 枚举类
package com.omni.admin.encryption;

import lombok.Getter;

@Getter
public enum EncryptionTypeEnum {
    MD5("md5"),
    SHA256("sha256"),
    SM3("sm3"),
    AES("AES"),
    SM4("SM4");

    private final String type;

    EncryptionTypeEnum(String type) {
        this.type = type;
    }
}

5. 加解密类型的配置化EncryptionProperties

package com.omni.admin.properties;

import com.omni.admin.encryption.EncryptionTypeEnum;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "omni.encryption")
public class EncryptionProperties {
    /**
     * 是否开启数据签名
     */
    private Boolean enableSign = true;

    /**
     * 是否开启存储加密
     */
    private Boolean enableStorage = true;

    /**
     * 签名加密算法
     */
    private EncryptionTypeEnum signAlgorithm = EncryptionTypeEnum.SM3;

    /**
     * 存储加密算法
     */
    private EncryptionTypeEnum storageEncryption = EncryptionTypeEnum.SM4;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

synda@hzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值