【数据安全】 脱敏

背景

        数据脱敏技术在合规性方面也非常重要,它有助于企业遵守《网络安全法》、《数据安全法》、《个人信息保护法》等相关法律法规的要求,确保数据处理活动的合法性和安全性。例如,《数据安全法》第二十七条提到,开展数据处理活动应当建立健全全流程数据安全管理制度,采取相应的技术措施保障数据安全,其中就包括数据脱敏技术的应用。

        总的来说,数据脱敏是确保数据在各种使用场景下安全、合规的关键技术,它通过多种算法和策略来实现对敏感信息的有效保护。随着数据安全和隐私保护意识的增强,数据脱敏技术的应用将越来越广泛。

        数据脱敏的实施流程通常包括敏感数据发现、敏感数据梳理、脱敏方案制定和脱敏任务执行等步骤。在敏感数据发现阶段,可以通过人工或自动的方式识别敏感数据。敏感数据梳理阶段则涉及到对数据列和数据关系的调整,以保持数据的关联性。脱敏方案制定阶段需要根据业务需求配置脱敏规则和策略,而脱敏任务执行阶段则是实际进行数据脱敏操作的过程。

考虑方案

如需要对数据进行脱敏处理,可能考虑的解决方案有:

  • 手动编程实现:通过Java的字符串处理和正则表达式功能,手动编写代码来替换敏感信息。
  • 使用第三方脱敏库:使用Hutool可以一行代码实现脱敏。
  • 使用数据处理框架:对于大数据量的脱敏处理,可以使用Apache Spark等数据处理框架进行批量处理。

以上方法都能解决当前问题实现数据脱敏处理,但可能考虑存在以下问题:

  • 手动编程,第三方脱敏库这两种方式虽然灵活,但需要较多的代码编写和维护工作。
  • 数据处理框架需要引入外部依赖,对于小规模数据有点小题大做,浪费过多的资源不说,对研发的要求也相对提高。

        选择哪种方案取决于具体的需求、数据规模和安全要求。对于小规模数据,手动编程或使用第三方库可能更为简单快捷;而对于大规模数据处理,则可能需要借助数据处理框架来实现高效的脱敏操作。在实际应用中,还需要考虑数据的合法性和合规性,确保脱敏后的数据符合相关法规和监管要求。

利用Spring boot自定义脱敏注解实现

可以结合Spring Boot 自定义注解的功能结合第三方库(Hutool)脱敏库 的方式,实现数据脱敏处理,通过这种方式可以使脱敏逻辑与业务逻辑进行分离,对代码的编写和维护工作有较大的提升,对不同业务处理提供良好的后续扩展。

定义脱敏标识注解

自定义一个脱敏注解,该注解在程序运行时进行,应用于方法上:

package liuyuxiang.service.utils.convert.intlpermission.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Lyx
 * 用于标记处理脱敏的方法	
 * */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveResponse {
}

编写脱敏逻辑,对有脱敏注解的方法做脱敏操作

1.实现数据操作实现数据脱敏的可控性: isOpen()
2.通过约定参数 "eyeCode" 灵活实现数据的脱敏与可见性

”eyeCode=open“ 数据可见

”eyeCode=close / 为空“ 数据脱敏

package liuyuxiang.service.utils.convert.intlpermission;

import com.github.pagehelper.PageInfo;
import liuyuxiang.service.utils.convert.intlpermission.util.ConvertEncryptIntl;
import liuyuxiang.service.utils.convert.intlpermission.util.SensitiveResponse;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.List;
import java.util.Objects;

/**
 * 敏感信息响应处理类
 * */
@Component
@ControllerAdvice
public class SensitiveResponseAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private IOpenManager openManager;
    private static String ENCRYPT_FIELD_CODE="ENCRYPT_FIELD_CODE";

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return Objects.requireNonNull(returnType.getMethod()).isAnnotationPresent(SensitiveResponse.class);
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  @NotNull MethodParameter returnType,
                                  @NotNull MediaType selectedContentType,
                                  @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  @NotNull ServerHttpRequest request,
                                  @NotNull ServerHttpResponse response) {

        if (isOpen()) return body;

        try {
            boolean shouldEncrypt = shouldEncrypt(request);
            if (shouldEncrypt) {
                return body;
            }
        } catch (IOException e) {
            logger.error("BCP脱敏接口:'"+request.getURI()+"'异常",e);
        }

        if (body instanceof PageInfo<?>) {
            ConvertEncryptIntl.convertToEncryptPage((PageInfo<?>) body,request);
            return body;
        }
        if (body instanceof List<?>) {
            ConvertEncryptIntl.convertToEncryptList((List<?>) body,request);
            return body;
        }

        if (body != null) {
            ConvertEncryptIntl.encrypt(body,request);
            return body;
        }
        return body;
    }

    private boolean isOpen() {
        String code=iSysConfManager.findByCode(ENCRYPT_FIELD_CODE);
        if(StringUtils.isBlank(code) || StringUtils.equals("close",code.toLowerCase())){
            return false;
        }
        return true;
    }

    public boolean shouldEncrypt(ServerHttpRequest request) throws IOException {
        String requestBody = StreamUtils.copyToString(request.getBody(), StandardCharsets.UTF_8);
        if (StringUtils.isNotBlank(requestBody)) {
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> requestParams = objectMapper.readValue(requestBody, Map.class);
            return shouldEncryptBasedOnRequestParams(requestParams);
        }
        // 如果请求体为空,检查查询字符串中的eyeCode,针对GET请求
        return "open".equals(findEyeCode(request.getURI().getQuery()));
    }


    private boolean shouldEncryptBasedOnRequestParams(Map<String, Object> requestParams) {
        if (requestParams.containsKey("eyeCode") && StringUtils.equals( "open", (CharSequence) requestParams.get("eyeCode"))) {
            return true;
        }
        return false;
    }

    private String findEyeCode(String queryString){
        if(StringUtils.isBlank(queryString)){
            return null;
        }
        Pattern pattern = Pattern.compile("eyeCode=(\\w+)");
        Matcher matcher = pattern.matcher(queryString);

        if (matcher.find()) {
            return matcher.group(1); // 匹配到的参数值
        }
        return null;
    }
}
ResponseBodyAdvice:Spring框架中的一个接口,用于对控制器方法的返回值进行处理。
主要方法:
  1. boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

        这个方法用于判断当前的 ResponseBodyAdvice 实现是否支持处理特定的方法返回值。

你可以根据返回类型和消息转换器类型来决定是否应用该处理逻辑。

  1. T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);

        这个方法在响应体写入之前被调用。你可以在这里对响应体进行修改。例如,添加额外的包装层、修改数据格式等。

编写脱敏工具类

package liuyuxiang.service.utils.convert.intlpermission.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageInfo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

/**
 * @author Lyx
 * desc 方法脱敏工具类
 **/
@Component
public class ConvertEncryptIntl {

    @Autowired
    public void init(List<PermissionValidatedManager> permissionValidatedManagers) {
        ConvertEncryptIntl.permissionValidatedManagers = permissionValidatedManagers;
    }

    private static List<PermissionValidatedManager> permissionValidatedManagers;

    /**
     * 需要脱敏的属性名称
     */
    private static final String FIELD = "mobilePhone,regionalManagerPhone,contactPhone,contractPhone,contactTel," +
            "operationsManagerPhone,accountManagerPhone,regionalManagerPhone,serviceManagerPhone";

    private static final String EMAIL = "loginEmail,customerEmail,email," +
            "regionalManagerEmail,accountManagerEmail,contactEmail,operationsManagerEmail";

    /**
     * name 单独脱敏
     */
    private static final String FIELD_NAME = "customerName," +
            "userName," +
            "loginName," +
            "aliasName," +
            "acctName," +
            "legalPersonName," +
            "agentName," +
            "operationsManagerName," +
            "custName," +
            "certName," +
            "contractName" +
            ",contactName," +
            "regionalManagerName," +
            "partbName,accountManagerName,"+
            "lastName," +
            "firstName," +
            "companyName,";

    private static final String CERT_NUMBER = "certNumber,certNum,legalPersonCertNumber,agentCertNo";

    private static final String CERT_ADDRESS = "certAddress,address,contractAddress,street";


    public static <T> PageInfo<T> convertToEncryptPage(PageInfo<T> pageInfo,ServerHttpRequest request) {
        if (Objects.isNull(pageInfo)) {
            return new PageInfo<>();
        }
        convertToEncryptList(pageInfo.getList(),request);
        return pageInfo;
    }

    public static <T> void convertToEncryptList(List<T> list, ServerHttpRequest request) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        for (T t : list) {
            encrypt(t,request);
        }
    }

    public static <T> T encrypt(T t, ServerHttpRequest request) {
        if(ObjectUtil.isEmpty(request)){
            return t;
        }
        Field[] fields = ReflectUtil.getFields(t.getClass());
        for (Field field : fields) {
            desensitization(t, request, field);

            Object value = ReflectUtil.getFieldValue(t, field);
            if (ObjectUtil.isNotEmpty(value)) {
                if (value instanceof List) {
                    convertToEncryptList((List<? extends Object>) value,request);
                }
                encrypt(value,request);
            }
        }
        return t;
    }

    private static <T> void desensitization(T t, ServerHttpRequest request, Field field) {
        String fieldName = ReflectUtil.getFieldName(field);
        for (PermissionValidatedManager s : permissionValidatedManagers) {
            if (Boolean.TRUE.equals(s.isAction(request.getURI().getPath()))) {
                if (s.validated(fieldName)) {
                    Object value = ReflectUtil.getFieldValue(t, field);
                    Object name = switchEncryptName(value, fieldName);/*字段脱敏处理*/
                    ReflectUtil.setFieldValue(t, field, name);
                }
            }
        }
    }

    private static Object switchEncryptName(Object value,String fieldName) {
        if (existsCertAddress(fieldName)) {
            value=addressDetail(value);
        }
        if (existsName(fieldName)) {
            value= userNameHyposensitization(String.valueOf(value));
        }
        if (existsMobile(fieldName)) {
            value= DesensitizedUtil.mobilePhone(String.valueOf(value));
        }
        if (existsNumber(fieldName)) {
            value= DesensitizedUtil.idCardNum(String.valueOf(value), 6, 4);
        }
        if (existsEmail(fieldName)) {
            value= DesensitizedUtil.email(String.valueOf(value));
        }
        return value;
    }

    private static Object addressDetail(Object value) {
        if (ObjectUtil.isNotEmpty(value)) {
            String address = String.valueOf(value);
            int index = address.indexOf("市");
            String pre = address.substring(0, index + 1);
            if (StringUtils.isNotEmpty(pre)) {
                return pre + generate(address.length() - index);
            }
        }
        return null;
    }

    private static boolean existsName(String str) {
        for (String s : FIELD_NAME.split(StrUtil.COMMA)) {
            if (str.toLowerCase().contains(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static boolean existsCertAddress(String str) {
        for (String s : CERT_ADDRESS.split(StrUtil.COMMA)) {
            if (str.toLowerCase().contains(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static boolean existsEmail(String str) {
        for (String s : EMAIL.split(StrUtil.COMMA)) {
            if (str.toLowerCase().contains(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static boolean existsMobile(String str) {
        for (String s : FIELD.split(StrUtil.COMMA)) {
            if (str.toLowerCase().contains(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static boolean existsNumber(String str) {
        for (String s : CERT_NUMBER.split(StrUtil.COMMA)) {
            if (str.toLowerCase().contains(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }


    public static String userNameHyposensitization(String userName) {
        if (userName.length() == 2) {
            return DesensitizedUtil.chineseName(userName);
        } else if (userName.length() == 3) {
            String[] split = userName.split("");
            return StrUtil.format("{}*{}", split[0], split[2]);
        } else {
            String[] split = userName.split("");
            StringBuilder stringBuilder = new StringBuilder();
            int length = split.length;
            int header = length / 3;

            int encry = length / 3 + 1;

            for (int i = 0; i < split.length; i++) {
                if (i < header) {
                    stringBuilder.append(split[i]);
                }
                if (i == encry) {
                    stringBuilder.append(generate(encry));
                }
                if (i > (header * 2)) {
                    stringBuilder.append(split[i]);
                }
            }
            return stringBuilder.toString();
        }
    }

    private static String generate(Integer length) {
        StringBuilder stringBuilder = new StringBuilder();
        for (Integer integer = 0; integer < length; integer++) {
            stringBuilder.append("*");
        }
        return stringBuilder.toString();
    }
}

编写脱敏接口选择器

package liuyuxiang.service.utils.convert.intlpermission;


/**
 * @author Lyx
 * */
public interface PermissionValidatedManager {


    /**
     * 指定执行bean
     */
    Boolean isAction(String url);


    /**
     * 校验脱敏字段
     *
     * @param attrName
     * @return
     */
    boolean validated(String attrName);

}

具体接口及字段脱敏初始化

package liuyuxiang.service.utils.convert.intlpermission.impl;

import liuyuxiang.service.utils.convert.intlpermission.PermissionValidatedManager;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 脱敏接口:/liuyuxiang/getAccountExtraInfo
 * 脱敏参数:contactPhone
 * */
@Service
public class AccountExtraInfoManagerImpl implements PermissionValidatedManager, InitializingBean {

    private static final String path="/liuyuxiang/getAccountExtraInfo";
    private static final List<String> FIELD_LIST = new ArrayList<>();


    @Override
    public Boolean isAction(String url) {
        int endIndex = url.indexOf("/", path.length());
        if (endIndex != -1) {
            // 如果找到了指定字符 "/", 执行下面的代码
            String result = url.substring(0, endIndex);
            return StringUtils.equals(result,result);
        }
        return StringUtils.equals(path,url);
    }

    @Override
    public boolean validated(String attrName) {
        return FIELD_LIST.stream().anyMatch(value -> StringUtils.equals(value.toLowerCase(), attrName.toLowerCase()));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        FIELD_LIST.add("contactPhone");
    }
}

实现参考类图:

实现逻辑:

根据springMvc 工作原理,在Controller层给需要脱敏的方法添加:@SensitiveResponse 注解

实现响应即脱敏的数据安全防范功能。

<think>好的,我现在需要帮助用户解决关于数据安全脱敏实验的问题。用户想了解方法、工具和最佳实践。首先,我得回忆一下之前提供的引用内容,看看有哪些相关信息可以利用。 引用[1]提到数据脱敏的几种核心技术,比如数据掩码、替换、扰动、加密和令牌化。这些应该是用户需要的方法部分。我需要详细解释每种方法,并给出例子,比如数据掩码中的部分遮蔽电话号码,或者替换中的用虚构数据替代真实信息。同时,引用里还提到这些方法的适用场景和优劣势,应该在回答中对比说明,帮助用户选择合适的方法。 接下来是工具部分。引用[4]提到了数据沙箱的使用,特别是在隔离环境中进行大数据分析和机器学习,这可能是一个很好的实践案例。我需要推荐一些开源工具,比如Apache ShardingSphere的脱敏模块,或者IBM的InfoSphere Optim,同时结合数据沙箱的应用场景,比如在测试环境中使用沙箱进行脱敏处理,确保数据安全。 最佳实践方面,引用[1]和[4]都提到了场景化脱敏策略和物理隔离。比如生产数据脱敏后再用于测试,或者在沙箱环境中操作,引用[4]中的例子可以直接作为案例说明。同时,动态脱敏和静态脱敏的区别也需要解释清楚,用户可能不太了解两者的应用场景。 用户可能还想知道如何验证脱敏后的数据是否有效,这里需要提到数据质量校验,比如统计特征对比或抽样验证。另外,权限控制和审计跟踪也是关键,引用[3]提到数据泄露的问题,说明安全措施的重要性,可以结合这一点强调权限管理。 最后,生成相关问题时,要考虑用户可能的后续需求,比如脱敏数据分析的影响,或者具体工具的操作步骤。需要确保问题覆盖用户可能感兴趣的各个方面,帮助他们深入探索。</think>### 数据安全脱敏实验方法与工具实践指南 #### 一、数据脱敏核心技术方法 1. **数据掩码** - 原理:隐藏部分敏感信息,如电话号码$138****5678$ - 场景:适用于姓名、证件号等结构化数据 - 优势:保留数据格式,满足业务验证需求[^1] 2. **数据替换** - 原理:将真实值映射为虚构值,如地址"北京市朝阳区"→"上海市浦东区" - 工具示例:使用Python库`Faker`生成模拟数据 ```python from faker import Faker fake = Faker('zh_CN') print(fake.name(), fake.address()) ``` 3. **数据扰动** - 数学实现:对数值型数据添加随机噪声 $$x' = x + \epsilon,\ \epsilon \sim \mathcal{N}(0,\sigma^2)$$ - 适用场景:金融交易金额、医疗数据保护 4. **加密与令牌化** - AES加密公式: $$C = E(K, P)$$ - 令牌化应用:信用卡号→不可逆令牌(如VISA→tok_9s7d4f) #### 二、实验工具选型 | 工具类型 | 代表工具 | 特点 | |----------------|-----------------------------------|------------------------------------------| | 开源工具 | Apache ShardingSphere脱敏模块 | 支持SQL级别实时脱敏 | | 商业平台 | IBM InfoSphere Optim | 提供可视化策略配置界面 | | 大数据环境 | 数据沙箱[^4] | 支持PySpark分布式脱敏,物理隔离测试环境 | | 云原生服务 | AWS Glue DataBrew | 集成机器学习辅助脱敏策略生成 | #### 三、最佳实践案例 1. **测试数据脱敏流水线** - 生产库→脱敏网关→测试环境(引用[4]沙箱隔离方案) - 使用SHA-256哈希处理用户ID,保留关联性同时去标识化 2. **动态脱敏实践** ```sql -- 使用Oracle Data Redaction实现实时脱敏 BEGIN DBMS_REDACT.ADD_POLICY( object_schema => 'HR', object_name => 'EMPLOYEES', column_name => 'SSN', policy_name => 'redact_ssn', function_type => DBMS_REDACT.PARTIAL, function_parameters => 'VVVFVVFVVVV,VVV-VV-VVVV,*,1,9' ); END; ``` 3. **隐私计算融合方案** - 联邦学习框架中结合差分隐私: $$Pr[\mathcal{M}(D) \in S] \le e^\epsilon Pr[\mathcal{M}(D') \in S] + \delta$$ - 引用[2]峰会案例:医疗数据联合建模时的梯度扰动方案 #### 四、实验验证要点 1. **数据质量校验** - 统计特征对比:原始数据脱敏数据的均值方差差异率<5% - 关联关系验证:主外键约束保持率需达100% 2. **安全有效性测试** - 执行k-匿名性验证: $$\forall q_i \in Q, |\{r \in D | r[A] = q_i[A]\}| \geq k$$ - 实施重标识攻击模拟测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值