下载帮

您现在的位置是:首页 > 数据库 > MongoDB

MongoDB

MongoDB 敏感数据加密、解密方案

2022-06-12 18:44MongoDB

mongodb敏感数据加解密方案

一、问题背景

公司项目涉及敏感数据存MongoDB库,基于风险要求,敏感数据做SM4国密加密后密文存库,读取时SM4解密为明文。

二、解决方案

项目采用SpringBoot+MongoDB技术框架,使用Spring Data MongoDB的持久化方案。

解决思路:

1、在数据写到mongodb之前,做一层拦截,对明文做SM4加密处理。

2、在mongodb读取数据之后,做一层拦截,对密文做SM4解密处理。

这里介绍下
AbstractMongoEventListener类,该类监听Mongodb的增删改查操作,并提供增删改查操作前后的事件,如下图所示:

其中:

  • onBeforeConvert:在 object 被MongoConverter转换为Document之前,在MongoTemplate insert,insertList和save操作中调用。
  • onBeforeSave:在将Document插入或保存在数据库中之前,请在MongoTemplate insert,insertList和save操作中调用。
  • onAfterSave:在将Document插入或保存在数据库中之后,在MongoTemplate insert,insertList和save操作中调用。
  • onAfterLoad:从数据库中检索Document后,在MongoTemplate find,findAndRemove,findOne和getCollection方法中调用。
  • onAfterConvert:从数据库中检索到Document后,在MongoTemplate find,findAndRemove,findOne和getCollection方法中调用被转换为 POJO。
  • onAfterDelete : 在数据库删除Document之后调用。
  • onBeforeDelete: 在数据库删除Document之前调用。

这里我们只需继承该上述基类,并覆写其中onBeforeConvert和onAfterConvert方法,即可实现在对象写到mongodb之前做SM4加密,在mongodb读取document转换到对象之后做SM4解密。

三、具体实现

1、首先实现一个SM4Enc字段注解。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SM4Enc {
    SM4EncType encType() default SM4EncType.NAME;
}

其中加密类型不同,对应的SM4加解密索引也不同,这里定义一个SM4加密索引枚举值:

public enum SM4EncType {
    NAME("A001"),
    PHONE("B001"),
    NONE("NONE");

    private String keyIdx;

    SM4EncType(String keyIdx){
        this.keyIdx = keyIdx;
    }

    public String keyIdx(){
        return keyIdx;
    }
}

2、实现自定义的mongodb读写时间监听器
MongoReadWriteEventListener,监听onBeforeConvert和onAfterConvert事件,并对标有@SM4Enc注解的字段进行写前SM4加密,读后SM4解密处理。

@Component
@Slf4j
public class MongoReadWriteEventListener extends AbstractMongoEventListener {

    @Override
    public void onAfterConvert(AfterConvertEvent event) {
        if (event.getSource() != null) {
            ReflectionUtils.doWithFields(event.getSource().getClass(), field -> {

                // @SM4Enc注解字段,读库后做SM4解密
                if (field.isAnnotationPresent(SM4Enc.class)) {
                    ReflectionUtils.makeAccessible(field);
                    Object value = field.get(event.getSource());
                    if (value != null) {
                        String ciphertext = (String) value;
                        if (StringUtils.isNotBlank(ciphertext)) {
                            String plainText = SM4Util.convertToEntityAttribute(ciphertext);
                            log.info("解密前:{},解密后:{}", ciphertext, plainText);
                            field.set(event.getSource(), plainText);
                        }
                    }
                }
            });
        }
    }

    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        if (event.getSource() != null) {
            ReflectionUtils.doWithFields(event.getSource().getClass(), field -> {
                // @SM4Enc注解字段,写库前做SM4加密
                if (field.isAnnotationPresent(SM4Enc.class)) {
                    ReflectionUtils.makeAccessible(field);

                    // 获取加密类型
                    SM4Enc sm4Enc = field.getAnnotation(SM4Enc.class);
                    SM4EncType encType = sm4Enc.encType();

                    Object value = field.get(event.getSource());
                    if (value != null) {
                        String plainText = (String) value;
                        String cipherText = SM4Util.convertToDatabaseColumn(encType.keyIdx(), plainText);
                        log.info("加密前:{},加密后:{}", plainText, cipherText);
                        field.set(event.getSource(), cipherText);
                    }
                }
            });
        }
    }

}

3、应用范例

    /**
     * 手机号
     */
    @SM4Enc(encType = SM4EncType.PHONE)
    private String phone;

文章评论