文章目录
- 什么是数据脱敏?
- 数据脱敏在生活中的应用
- 静态脱敏与动态脱敏
- 数据脱敏的几种方案
- sql数据脱敏
- java代码实现脱敏
- mybatis-mate实现脱敏
- springCloud网关拦截响应体实现脱敏
- openGauss 动态数据脱敏解决方案
什么是数据脱敏?
数据脱敏也叫数据的去隐私化,采用专门的脱敏算法对敏感数据进行屏蔽和仿真替换,将敏感数据转化为虚构数据,将个人信息匿名化,为数据的安全使用提供基础保障。同时,在不改变业务系统逻辑的前提下,保证脱敏后的数据保留原数据的特征和分布,同时保证数据的有效性和可用性,使脱敏后的数据能够安全地应用于测试、开发、分析和第三方使用环境中。
数据脱敏在生活中的应用
像政府、医疗行业、金融机构、移动运营商是比较早开始应用数据脱敏工具的,因为他们所掌握的都是用户最核心的私密数据,如果泄露后果是不可估量的。
数据脱敏的应用在生活中是比较常见的,比如我们在淘宝买东西订单详情中,商家账户信息会被用 * 遮挡,保障了商户隐私不泄露,这就是一种数据脱敏方式。
静态脱敏与动态脱敏
数据静态脱敏(SDM):适用于将数据抽取出生产环境脱敏后分发至测试、开发、培训、数据分析等场景。
有时我们可能需要将生产环境的数据 copy 到测试、开发库中,以此来排查问题或进行数据分析,但出于安全考虑又不能将敏感数据存储于非生产环境,此时就要把敏感数据从生产环境脱敏完毕之后再在非生产环境使用。
这样脱敏后的数据与生产环境隔离,满足业务需要的同时又保障了生产数据的安全。
数据动态脱敏(DDM): 一般用在生产环境,访问敏感数据时实时进行脱敏,因为有时在不同情况下对于同一敏感数据的读取,需要做不同级别的脱敏处理,例如:不同角色、不同权限所执行的脱敏方案会不同。
数据脱敏的几种方案
sql数据脱敏
-- 电话号码脱敏sql:
SELECT mobilePhone AS 脱敏前电话号码,CONCAT(LEF(mobilePhone,3), '********' ) AS 脱敏后电话号码 FROM t_s_user
-- 身份证号码脱敏sql:
SELECT idcard AS 未脱敏身份证, CONCAT(LEFT(idcard,3), '****' ,RIGHT(idcard,4)) AS 脱敏后身份证号 FROM t_s_user
java代码实现脱敏
/**
* 手机号脱敏
* @param phone 原始手机号信息
* @return 脱敏后信息
*/
public String maskIdNo(String phone){
// 结果 136****1024
return phone.replaceAll("(\\w{3})\\w*(\\w{4})", "$1****$2");
}
/**
* 身份证脱敏
* @param idNo 原始身份证号信息
* @return 脱敏后信息
*/
public String maskIdNo(String idNo){
// 结果 202210******141024
return idNo.replaceAll("(\\w{6})\\w*(\\w{6})", "$1******$2");
}
mybatis-mate实现脱敏
pom依赖
<dependency><!--MyBatis-Plus 企业级模块-->
<groupId>com.baomidou</groupId>
<artifactId>mybatis-mate-starter</artifactId>
<version>1.2.8</version>
</dependency>
YML配置
spring:
datasource:
# 配置安全:https://blog.csdn.net/tongxin_tongmeng/article/details/128664932
url: mpw:IlcV2VrLIr+z3ruf0oHP1sV3JuEvntw9QZDEYhQWDNHJ9Xkm7qZokxkEeTCPNqma
username: mpw:aoVz0lDJNymnmrhw6LkQow==
password: mpw:StRVtLG7vB6iKVt83du7fw==
driver-class-name: com.mysql.cj.jdbc.Driver
# Mybatis Mate 配置
mybatis-mate:
cert:
# 请添加微信wx153666购买授权,不白嫖从我做起! 测试证书会失效,请勿正式环境使用
grant: thisIsTestLicense
license: TtY9GC88CzSkEmUhzIyvM2MJKvsgPyxoNCExH4/GhaBwuTQ93aeLaR6/dM49wMSk+oQdmqUibCM8b5H74s1Nx+2C5V3U1gKiLtddVc8Eg8oC1F2nLxOiDKDvPpdxWFGsPW6mQE2LDr+tK8GXpFS3N8xwmYy/gHCwQ4Avqp9JqBbke7pZzL2adIlxYHmCYpfNTN+NRHIEFaGFTBlzZHDb3UfJaeqLaAtWBol0QOPEM69Kz3JSemxBHnEO1ID75bwwmkgqC7Ps4z9iYAK9GLzzaPwSiFELNCmIvwa5YSJLxP9NMQUWbVGIRqehxnVqfgx/68+yIfpByqGTMxLR33yeEQ==
脱敏策略
@Configuration
public class SensitiveStrategyConfig {
/**
* 注入脱敏策略
*/
@Bean
public ISensitiveStrategy sensitiveStrategy() {
return new SensitiveStrategy();
}
}
字段脱敏
/**
*
* @TableName sentive
*/
@TableName(value ="sentive")
@Data
public class Sentive implements Serializable {
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* chineseName
*/
@FieldSensitive(SensitiveType.chineseName)
@TableField(value = "chineseName")
private String chinesename;
/**
* idCard
*/
@FieldSensitive(SensitiveType.idCard)
@TableField(value = "idCard")
private String idcard;
/**
* phone
*/
@FieldSensitive(SensitiveType.phone)
@TableField(value = "phone")
private String phone;
/**
* mobile
*/
@FieldSensitive(SensitiveType.mobile)
@TableField(value = "mobile")
private String mobile;
/**
* address
*/
@FieldSensitive(SensitiveType.address)
@TableField(value = "address")
private String address;
/**
* email
*/
@FieldSensitive(SensitiveType.email)
@TableField(value = "email")
private String email;
/**
* bankCard
*/
@FieldSensitive(SensitiveType.bankCard)
@TableField(value = "bankCard")
private String bankcard;
/**
* password
*/
@FieldSensitive(SensitiveType.password)
@TableField(value = "password")
private String password;
/**
* carNumber
*/
@FieldSensitive(SensitiveType.carNumber)
@TableField(value = "carNumber")
private String carnumber;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
自定义策略
注意:strategyA strategyB strategyC 为自定义脱敏策略,字段添加@FieldSensitive(“strategyA”)注解
即可使用strategyA脱敏策略,该策略将原始数据转为大写后将所有"A"替换为"#",strategyB strategyC同理
@Configuration
public class SensitiveStrategyConfig {
/**
* 注入脱敏策略
*/
@Bean
public ISensitiveStrategy sensitiveStrategy() {
return new SensitiveStrategy()
.addStrategy("strategyA", t -> t.toUpperCase().replace("A", "#"))
.addStrategy("strategyB", t -> t.toUpperCase().replace("B", "$"))
.addStrategy("strategyC", t -> t.toUpperCase().replace("C", "&"));
自定义策略使用
/**
* chineseName
*/
@FieldSensitive("strategyA")
@TableField(value = "chineseName")
private String chinesename;
/**
* idCard
*/
@FieldSensitive("strategyB")
@TableField(value = "idCard")
private String idcard;
/**
* phone
*/
@FieldSensitive("strategyC")
@TableField(value = "phone")
private String phone;
springCloud网关拦截响应体实现脱敏
@Slf4j
@Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
Configuration conf = Configuration.builder()
.options(Option.AS_PATH_LIST).build();
@Override
public int getOrder() {
return FilterOrderEnum.GATEWAY_CONTEXT_FILTER.getOrder();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate().response(responseDecorator(exchange)).build());
}
private ServerHttpResponseDecorator responseDecorator(ServerWebExchange exchange) {
return new ServerHttpResponseDecorator(exchange.getResponse()) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return super.writeWith(DataBufferUtils.join(Flux.from(body))
.map(dataBuffer -> {
// 获取响应体
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
return content;
}).flatMap(bytes -> {
// 对响应体进行业务判断(返回值是OK,格式为JSON)
if (exchange.getResponse().getStatusCode().equals(HttpStatus.OK)
&& exchange.getResponse().getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0).equals(MediaType.APPLICATION_JSON_UTF8.toString())) {
// 将响应体转换为String
String bodyString = new String(uncompress(bytes), StandardCharsets.UTF_8);
log.info("bodyString: {}", bodyString);
// 进行业务处理
// TODO 调用业务处理方法
// 读取配置文件内容
List<String> filterField = null;
String dataHandling = dataHandling(responseData, filterField);
log.info("dataHandling: {}", dataHandling);
// 最后将返回的数据类型转换为byte
bytes = dataHandling.getBytes();
} else {
}
return Mono.just(bufferFactory.wrap(bytes));
}));
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
public static byte[] uncompress(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
try {
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
} catch (IOException e) {
log.error("gzip uncompress error.", e);
}
return out.toByteArray();
}
/*编码 gzip*/
public static byte[] compress(String str, String encoding) {
if (str == null || str.length() == 0) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip;
try {
gzip = new GZIPOutputStream(out);
gzip.write(str.getBytes(encoding));
gzip.close();
} catch (IOException e) {
log.error("gzip compress error.", e);
}
return out.toByteArray();
}
public String dataHandling(String responseData, List<String> filterField) {
try {
String content = responseData;
for (String filterFieldS : filterField) {
//获取所有的节点
List<String> pathList = JsonPath.using(conf).parse(responseData).read("$.." + filterFieldS);
for (String path : pathList) {
//获取父节点
String parentPath = StrUtil.removeSuffix(path, "['" + filterFieldS + "']");
//读取值
Object json = JsonPath.parse(content).read(path);
if (json != null) {
String read = json.toString();
//脱敏
String mobileMask = mask(read);
String keyName = filterFieldS + "Mask";
//父节点下添加元素
content = JsonPath.parse(content).put(parentPath, keyName, mobileMask).jsonString();
}
}
}
return content;
} catch (Exception e) {
log.error(responseData, e);
}
return null;
}
public static String mask(String val) {
if(StrUtil.isBlank(val)){
return val;
}
if(val.length() < 3){
return StrUtil.hide(val,1,val.length());
}
if(val.length() < 4){
return StrUtil.hide(val,1,val.length()-1);
}
if(PhoneUtil.isMobile(val) || PhoneUtil.isMobileHk(val) || PhoneUtil.isMobileTw(val)){
return StrUtil.desensitized(val,DesensitizedUtil.DesensitizedType.MOBILE_PHONE);
}
if(EnumUtil.isEnum(val)){
return StrUtil.desensitized(val,DesensitizedUtil.DesensitizedType.EMAIL);
}
//TODO 银行卡的工具类hutool我没找到可以自行在网上查找
return StrUtil.desensitized(val, DesensitizedUtil.DesensitizedType.ID_CARD);
}
}
openGauss 动态数据脱敏解决方案
动态数据脱敏功能在工业界通常以中间插件或数据脱敏系统(Data Masking System)的形态加载,通过在客户端与服务端数据库之间拦截命令或结果集来实现脱敏,而openGauss内置动态数据脱敏特性,使数据库无需借助外部插件就可以实现数据脱敏,有效地降低数据中间传输而导致敏感数据泄漏的风险。
openGauss从1.1.0版本定义了一套完整的内置安全策略模型,基于该模型用户可以定义资源标签来标识敏感数据,针对不同的资源标签类别和内容可定义相关的安全策略机制,而动态数据脱敏就是其中一种。