开发笔记:如何消除秘钥数据对RPC负荷、日志、系统安全的伤害?

0x01. 秘钥数据特征

以我们系统的数据表levy_merchant_relation为例,该数据表存储的是商户服务商关联关系。下面是与之对应的 LevyMerchantRelation 实体类结构,除了包括商户与服务商相关字段,还包括HTTP接口通信的秘钥和口令,如RSA公私钥、加密秘钥。

LevyMerchantRelation ​{id, merId, merName, levyId, levyName, levyMerId, levyMerName, interfaceKey, publicKey, privateKey, loginPassword, memo, createTime, updateTime}

本文要说的是这些秘钥字段数据。

这些秘钥数据,尤其是RSA秘钥,有两个特征:

  1. 是比较长的字符串。
  2. 属于系统隐私数据。

这些秘钥数据会对系统带来如下伤害:

  1. 对于微服务架构系统来说,这些数据通过RPC传输,一来泄露了隐私数据,致使系统存在安全风险;其次,这些数据急剧增大RPC数据传输的payload(响应结果包含秘钥数据的list接口或page接口,所带来的影响尤其明显)。
  2. 程序日志方面,这些数据打印在日志文件里,一来泄露了隐私数据,致使系统存在安全风险;其次,这些大字符串数据会使日志体积变大。再者,当我们在通过分析日志来排查系统问题时,这些大字符串往往比较碍眼,会降低我们排查问题的效率。

0x02. 如何解决秘钥数据对系统产生的伤害?

*⬮*解决对RPC传输的伤害 ←RPC传输层的数据隔离

1. DTO职责分离设计:一分为二

作为RPC传输的LevyMerchantRelationDTO,其结构不能直接是LevyMerchantRelation实体类的"副本",而要排除掉秘钥字段,毕竟,90%的调用方是使用relation关系,而不需要秘钥数据。另外,对于那些需要秘钥数据的功能,如页面的CRUD,则定义单独的秘钥DTO。是的,仅包含秘钥字段。

LevyMerchantRelation ​{id, merId, merName, levyId, levyName, levyMerId, levyMerName, interfaceKey, publicKey, privateKey, loginPassword, memo, createTime, updateTime}

⬇ ⬇ ⬇

LevyMerchantRelationDTO ​{id, merId, merName, levyId, levyName, levyMerId, levyMerName, memo, createTime, updateTime}

ApiSecretDTO ​{interfaceKey, publicKey, privateKey, loginPassword}

一分为二后的两个DTO,一个仅包含业务字段,一个仅包含秘钥字段,职责清晰,边界分明,各司其职。

2. RPC接口的精细化设计

对应的服务接口应做相应拆分,确保"按需知密"的安全原则。

  • 对于查询接口,单独定义个获取秘钥的接口(如果需要的话),其他接口均返回 LevyMerchantRelationDTO。
  • 新增/修改接口则同时包含LevyMerchantRelationDTO和ApiSecretDTO这2个参数。通过RPC接口层面彻底隔离,把伤害降低至0。

*⬮*解决对日志的伤害

有了上面RPC传输层的数据隔离,LevyMerchantRelationDTO 本身不用考虑了。我们需要考虑的,是entity和ApiSecretDTO等包含秘钥的POJO。

我们在kibana日志平台经常看到长长的秘钥串,像金灿灿的油菜花田里一株突兀的、傲慢的绿草,它肆意摇曳,便将整片田野的和谐平衡,轻易撕开一道口子。

如何解决呢?

取决于我们打印日志的方式。

不外乎如下2种:

  • 方式一:log.info("XX服务商】-查询企业账户信息,接口入参:relation={}", levyMerchantRelation);
  • 方式二:log.info("XX服务商】-查询企业账户信息,接口入参:relation={}", JSON.toJSONString(levyMerchantRelation));

针对方式一,重写entity等模型类的 toString() 方法,在拼接字符串时去掉秘钥field。

复制代码
import lombok.Data;
 
@Data
public class LevyMerchantRelation {
    ...
 
    @Override public String toString() {
        return "LevyMerchantRelation{" +
                ...
                ", interfaceKey='" + StringUtils.length(interfaceKey) + " characters'" +
                ", publicKey='" + StringUtils.length(publicKey) + " characters'" +
                ", privateKey='" + StringUtils.length(privateKey) + " characters'" +
                ...
                '}';
    }
}

针对方式二,则要说道说道了。一种办法是不用json序列化,改用方式一。但难免未来在系统迭代中又会出现这种方式的log.info。我们也不能将此纳入到团队编码规范里。毕竟,在log中查看经json序列化后的对象字符串,更利于肉眼识别关键信息。由此,JSONLog出现了。

JSONLog是什么?是一个自定义的工具类,是一个在IDE中键入"JSON"就能联想出来的工具类。

JSONLog的定义如下,它在将对象进行序列化时,过滤掉了 publicKey、privateKey、password等秘钥字段关键字。

复制代码
package com.sby.common.json;
 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.google.common.collect.Sets;
 
public class JSONLog {
    private static final SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
 
    static {
        filter.getExcludes().addAll(Sets.newHashSet(
                "privateKey", "publicKey", "password", "secret",
                "accessKey", "secretKey", "token",
                "interfaceKey", "encryptKey", "apiKey"
        ));
    }
 
    /**
     * 安全序列化,自动排除 敏感字段(RSA公钥还是长字符串)
     */
    public static String toJSONString(Object obj, String... fields) {
        if (obj == null) return "null";
 
        return JSON.toJSONString(obj, filter);
    }
}

然后,开发团队内部广而告之,相信会比其他解决方案要好。

0x03. 总结

这种从数据模型、接口设计到工具支持的全方位思考,正是构建安全、高性能系统的关键所在。

彼时的2025年5月份,消费券系统开始立项研发,在程序设计阶段,我们要为系统的两个关键参与者------消费企业 与 核销企业------设计数据表结构,我主张为两者分别创建各自的表,至于两者的营业资质信息,则存储到一个共同的营业资质表。看来,通过这种数据隔离方式的数据结构设计,亦可以有效规避这些营业资质信息在上述两方面对系统产生的伤害。 ------------设若 levy_merchant_relation 这张表 不包含秘钥字段(关联关系与关联关系的秘钥分开存储),那么,就不存在 LevyMerchantRelation"因秘钥数据而对系统产生伤害"的事情。------------anyway,从数据设计源头规避风险,也是很重要的一件事。

ref: ⬮ 如何在程序日志中不打印LevyMerchantRelation中的privateKey、publicKey这些大字符串field?程序日志优化:精准捕获与日志分级,践行数字低碳20231130-调用上海银行timeout,哐哐报错。日复一日月复一月,我们视而不见。近几日日志文件动辄60~70G,我们的系统往往以日志量取胜。 < 摘自公司内部confluence-wiki>

相关推荐
am心2 小时前
学习笔记-小程序-导入商品浏览功能实现
笔记·学习
咒法师无翅鱼2 小时前
【西电计网学习笔记】网络层【RIP,OSPF,ARP,ICMP,IGMP,逻辑寻址(ABCD四类)】
网络
Ares-Wang2 小时前
网络》》以太网交换安全
网络·安全
XiaoHu02072 小时前
Linux网络编程套接字
linux·服务器·网络·git
hkNaruto2 小时前
【AI】AI学习笔记:LangGraph入门 三大典型应用场景与代码示例及MCP、A2A与LangGraph核心对比
人工智能·笔记·学习
kingmax542120082 小时前
北京高一历史上学期期末考情分析与核心知识点精讲(完整版)-吐血整理-全网最全
笔记·学习方法·历史
以太浮标3 小时前
华为eNSP模拟器综合实验之- VLAN聚合(VLAN Aggregation或Super VLAN)解析
运维·网络·华为·信息与通信
宵时待雨3 小时前
数据结构(初阶)笔记归纳3:顺序表的应用
c语言·开发语言·数据结构·笔记·算法
智者知已应修善业3 小时前
【C语言 dfs算法 十四届蓝桥杯 D飞机降落问题】2024-4-12
c语言·c++·经验分享·笔记·算法·蓝桥杯·深度优先