|
@@ -8,10 +8,12 @@ import java.util.regex.Pattern;
|
|
|
* 【★ 覆盖哪些秘密 —— 2026-09-23 扩过一次,它**不再只是付款码**】
|
|
* 【★ 覆盖哪些秘密 —— 2026-09-23 扩过一次,它**不再只是付款码**】
|
|
|
* 两层各管一摊,只读其中一层就下结论会错:
|
|
* 两层各管一摊,只读其中一层就下结论会错:
|
|
|
* - **值层** {@link #LONG_DIGITS}:不看键名,只认「连续 ≥12 位数字」。
|
|
* - **值层** {@link #LONG_DIGITS}:不看键名,只认「连续 ≥12 位数字」。
|
|
|
- * - **键层** {@link #MASKED_KEYS}(原名 `AUTH_CODE_KEY`):按**键名**认,名单现在 7 个 ——
|
|
|
|
|
- * 付款码 `auth_code` 之外,另有 6 个**进件腿的 PII 键名**:`legal_cert_no`(法人证件号)/
|
|
|
|
|
|
|
+ * - **键层** {@link #MASKED_KEYS}(原名 `AUTH_CODE_KEY`):按**键名**认,名单现在 11 个 ——
|
|
|
|
|
+ * 付款码 `auth_code` 之外,另有 10 个**进件腿的键名**:`legal_cert_no`(法人证件号)/
|
|
|
* `account_idcard_num`(结算人证件号)/ `principal_cert_no`(身份证)/ `account_card_num`(结算卡号)/
|
|
* `account_idcard_num`(结算人证件号)/ `principal_cert_no`(身份证)/ `account_card_num`(结算卡号)/
|
|
|
- * `contact_phone`(联系电话)/ `account_card_phone`(结算卡手机号)。
|
|
|
|
|
|
|
+ * `contact_phone`(联系电话)/ `account_card_phone`(结算卡手机号)/
|
|
|
|
|
+ * `manager_phone`(门店管理员手机号)/ `shop_phone`(门店电话)/ `contact_service`(商户客服电话)/
|
|
|
|
|
+ * `check_code`(安心签验证码)。
|
|
|
* **为什么这一类只能落在键层**:出站那一行 trace 用的是 {@link #maskByKey},而值层是 `\d{12,}`
|
|
* **为什么这一类只能落在键层**:出站那一行 trace 用的是 {@link #maskByKey},而值层是 `\d{12,}`
|
|
|
* —— 18 位证件号接得住,**11 位手机号接不住**。理由与收/不收的判据见 {@link #MASKED_KEYS} 的 javadoc。
|
|
* —— 18 位证件号接得住,**11 位手机号接不住**。理由与收/不收的判据见 {@link #MASKED_KEYS} 的 javadoc。
|
|
|
*
|
|
*
|
|
@@ -22,7 +24,7 @@ import java.util.regex.Pattern;
|
|
|
* {@code LsyNotifyService} 落库的 `payload` 是 `LeshouyingSensitive.mask(rawBody)` 的产物
|
|
* {@code LsyNotifyService} 落库的 `payload` 是 `LeshouyingSensitive.mask(rawBody)` 的产物
|
|
|
* ⇒ 含这批键名的回调报文,**入库文本会因此改变**(多遮几个值)。拿旧报文比对会看到差异。
|
|
* ⇒ 含这批键名的回调报文,**入库文本会因此改变**(多遮几个值)。拿旧报文比对会看到差异。
|
|
|
*
|
|
*
|
|
|
- * 【2026-09-23 那次改动**只动了名单**,没动结构】判据、明确**不**遮的名单(含代价)、
|
|
|
|
|
|
|
+ * 【2026-09-23 的两次改动**都只动了名单**,没动结构】判据、明确**不**遮的名单(含代价)、
|
|
|
* 明确**不得**加的名字、前缀约束、「必须是按键名」这条硬边界 —— 全在 {@link #MASKED_KEYS}
|
|
* 明确**不得**加的名字、前缀约束、「必须是按键名」这条硬边界 —— 全在 {@link #MASKED_KEYS}
|
|
|
* 的 javadoc 里。**改名单前先读那一段。**
|
|
* 的 javadoc 里。**改名单前先读那一段。**
|
|
|
*
|
|
*
|
|
@@ -89,31 +91,65 @@ public final class LeshouyingSensitive {
|
|
|
* 便于排障时仍能看懂报文结构。
|
|
* 便于排障时仍能看懂报文结构。
|
|
|
*
|
|
*
|
|
|
* <p>【★ 名字从 {@code AUTH_CODE_KEY} 改成 {@code MASKED_KEYS}(2026-09-23)】它早已不只认
|
|
* <p>【★ 名字从 {@code AUTH_CODE_KEY} 改成 {@code MASKED_KEYS}(2026-09-23)】它早已不只认
|
|
|
- * {@code auth_code}:名单现在有 7 个键名,管两类凭据 —— 付款码({@code auth_code})与
|
|
|
|
|
- * **进件腿的 PII**。旧名字在说一件不再成立的事,而「PII 键管不管」正是读这个文件的人
|
|
|
|
|
|
|
+ * {@code auth_code}:名单现在有 11 个键名,管**两类** —— 判据 (a) 的**标识符** 9 个
|
|
|
|
|
+ * (进件腿的证件号 / 卡号 / 电话:`legal_cert_no`、`account_idcard_num`、`principal_cert_no`、
|
|
|
|
|
+ * `account_card_num`、`contact_phone`、`account_card_phone`、`manager_phone`、`shop_phone`、
|
|
|
|
|
+ * `contact_service`),判据 (b) 的**凭据** 2 个(付款码 `auth_code`、安心签验证码 `check_code`)。
|
|
|
|
|
+ * 旧名字在说一件不再成立的事,而「PII 键管不管」正是读这个文件的人
|
|
|
* 要问的第一个问题;留着一个失实的名字,等于用一行代码把答案说反。
|
|
* 要问的第一个问题;留着一个失实的名字,等于用一行代码把答案说反。
|
|
|
* 改名成本实测为零:全仓引用只有本文件的声明处与 {@link #maskByKey} 里那一处,
|
|
* 改名成本实测为零:全仓引用只有本文件的声明处与 {@link #maskByKey} 里那一处,
|
|
|
* 测试文件里零引用。
|
|
* 测试文件里零引用。
|
|
|
*
|
|
*
|
|
|
- * <p>【★★ 一个键名收不收,判据只有一条 —— 改名单前先读这段】
|
|
|
|
|
|
|
+ * <p>【★★ 一个键名收不收,判据是**两段式** —— 改名单前先读这段】
|
|
|
* <blockquote>
|
|
* <blockquote>
|
|
|
- * <b>值的形态<u>本身就是标识符</u>(证件号 / 卡号 / 手机号)⇒ 收;
|
|
|
|
|
- * 值是<u>业务语义</u>(名称 / 地址 / 邮箱)⇒ 不收。</b>
|
|
|
|
|
|
|
+ * <b>收,两类:</b><br>
|
|
|
|
|
+ * <b>(a) 值的形态<u>本身就是标识符</u></b> —— 证件号 / 卡号 / 手机号(含**电话形态的值**);<br>
|
|
|
|
|
+ * <b>(b) 值是<u>凭据</u></b> —— 一次泄漏即可被冒用(付款码 / 验证码)。<br>
|
|
|
|
|
+ * <b>不收:</b>值是<u>业务语义</u>(名称 / 地址 / 邮箱)、<u>公开登记信息</u>
|
|
|
|
|
+ * (营业执照号 / 统一社会信用代码),以及<u>我们自己的对账号</u>
|
|
|
|
|
+ * ({@code mch_no} / {@code out_trade_no} / {@code trade_no} / {@code pay_trace_no} /
|
|
|
|
|
+ * {@code trace_no} —— 遮了等于删掉这条日志)。
|
|
|
* </blockquote>
|
|
* </blockquote>
|
|
|
- * 名单里那 7 个就是按这条判据逐条核过的,不是「看着像敏感就加」:
|
|
|
|
|
|
|
+ * ★ (a) 是 2026-09-23 原有的那一条;(b) 是**同日修复轮 1 补的**,补的理由不是修饰:
|
|
|
|
|
+ * **旧句子今天就不完全成立** —— 表里 {@code auth_code} 那一行的理由自己写的是
|
|
|
|
|
+ * 「不是 PII,是能直接发起支付的凭据」,{@code check_code} 同理。一段式装不下这两行。
|
|
|
|
|
+ * 名单里那 11 个就是按这两段逐条核过的,不是「看着像敏感就加」:
|
|
|
* <table border="1">
|
|
* <table border="1">
|
|
|
- * <caption>键层名单</caption>
|
|
|
|
|
|
|
+ * <caption>键层名单(11 个)</caption>
|
|
|
* <tr><th>键名</th><th>是什么</th><th>为什么收</th></tr>
|
|
* <tr><th>键名</th><th>是什么</th><th>为什么收</th></tr>
|
|
|
* <tr><td>{@code auth_code}</td><td>付款码</td>
|
|
* <tr><td>{@code auth_code}</td><td>付款码</td>
|
|
|
- * <td>不是 PII,是**能直接发起支付的凭据**,与「付款码刻意不落库」的决策同源</td></tr>
|
|
|
|
|
- * <tr><td>{@code legal_cert_no}</td><td>法人证件号</td><td>证件号</td></tr>
|
|
|
|
|
- * <tr><td>{@code account_idcard_num}</td><td>结算人证件号</td><td>证件号</td></tr>
|
|
|
|
|
- * <tr><td>{@code principal_cert_no}</td><td>身份证(安心签两腿)</td><td>证件号</td></tr>
|
|
|
|
|
- * <tr><td>{@code account_card_num}</td><td>结算卡号</td><td>卡号</td></tr>
|
|
|
|
|
- * <tr><td>{@code contact_phone}</td><td>联系电话</td><td>手机号</td></tr>
|
|
|
|
|
- * <tr><td>{@code account_card_phone}</td><td>结算卡手机号</td><td>手机号</td></tr>
|
|
|
|
|
|
|
+ * <td>不是 PII,是**能直接发起支付的凭据**,与「付款码刻意不落库」的决策同源 —— (b)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code legal_cert_no}</td><td>法人证件号</td><td>证件号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code account_idcard_num}</td><td>结算人证件号</td><td>证件号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code principal_cert_no}</td><td>身份证(安心签两腿)</td><td>证件号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code account_card_num}</td><td>结算卡号</td><td>卡号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code contact_phone}</td><td>联系电话</td><td>手机号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code account_card_phone}</td><td>结算卡手机号</td><td>手机号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code manager_phone}</td><td>门店管理员手机号({@code createShop} 必填)</td>
|
|
|
|
|
+ * <td>手机号 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code shop_phone}</td><td>门店电话({@code createShop} 必填)</td>
|
|
|
|
|
+ * <td>电话形态的值 (a)</td></tr>
|
|
|
|
|
+ * <tr><td>{@code contact_service}</td><td>商户客服电话</td>
|
|
|
|
|
+ * <td>电话形态的值 (a) —— **键名里没有 "phone"**:判据管的是**值的形态**,不是拼写。
|
|
|
|
|
+ * 若它实为 400 热线,遮掉也只是这条诊断日志少一个字段值,**代价方向安全**</td></tr>
|
|
|
|
|
+ * <tr><td>{@code check_code}</td><td>安心签验证码</td><td>凭据 (b):一次泄漏即可被冒用</td></tr>
|
|
|
* </table>
|
|
* </table>
|
|
|
*
|
|
*
|
|
|
|
|
+ * <p>【★ 三条已判定的边界 —— 写在这里,免得后来者反复摇摆】
|
|
|
|
|
+ * {@code sign} / {@code license_no}(含 {@code business_licence})/ {@code code_url}
|
|
|
|
|
+ * **都不收**,三条理由各不相同:
|
|
|
|
|
+ * <ol>
|
|
|
|
|
+ * <li>{@code sign} ⇒ 不收。它是**派生值**,不是凭据本身;而且它恰恰是验签排障**唯一可比对**
|
|
|
|
|
+ * 的值 —— 上游约束「验签绝不跳过」把这条排障路径设为**必须可用**。
|
|
|
|
|
+ * <b>残余风险如实写明:{@code sign} + 报文在原理上可重放。接受。</b></li>
|
|
|
|
|
+ * <li>{@code license_no} / {@code business_licence} ⇒ 不收。**公开登记信息**。
|
|
|
|
|
+ * {@code business_licence} 的值域文档未说明({@code LsyAnxinSignCompanyDTO} 自己写了这句),
|
|
|
|
|
+ * 但它的两种可能读法(营业执照号 / 证照文件)**都不落在 (a)(b) 里**
|
|
|
|
|
+ * ⇒ 这条裁定**与那个未知无关**。</li>
|
|
|
|
|
+ * <li>{@code code_url} ⇒ 不收。它**已落库**({@code LsyCancelService} 从 order 取
|
|
|
|
|
+ * {@code getCodeUrl()})—— 我们本来就不当它机密。</li>
|
|
|
|
|
+ * </ol>
|
|
|
|
|
+ *
|
|
|
* <p>【★ 为什么这件事只能落在键层(出站路径)】出站那一行 trace 用
|
|
* <p>【★ 为什么这件事只能落在键层(出站路径)】出站那一行 trace 用
|
|
|
* {@link #maskByKey},而它**不能**改用 {@link #mask}(值层会把 {@code out_trade_no} /
|
|
* {@link #maskByKey},而它**不能**改用 {@link #mask}(值层会把 {@code out_trade_no} /
|
|
|
* {@code pay_trace_no} 这些**我们自己的对账号**一起遮掉 —— 那等于把这条日志删了,
|
|
* {@code pay_trace_no} 这些**我们自己的对账号**一起遮掉 —— 那等于把这条日志删了,
|
|
@@ -136,13 +172,15 @@ public final class LeshouyingSensitive {
|
|
|
* 换句话说:**这是拿 PII 的可见性换可诊断性的一次明账,不是漏网。**
|
|
* 换句话说:**这是拿 PII 的可见性换可诊断性的一次明账,不是漏网。**
|
|
|
*
|
|
*
|
|
|
* <p>【★ 明确<u>不得</u>收进来的】{@code mch_no} / {@code out_trade_no} / {@code trade_no} /
|
|
* <p>【★ 明确<u>不得</u>收进来的】{@code mch_no} / {@code out_trade_no} / {@code trade_no} /
|
|
|
- * {@code pay_trace_no} —— 这四个是**我们自己的对账号**({@code mch_no} 还是我们自己
|
|
|
|
|
- * 发布给运维的应答字段名),收进来 = 删掉这条日志,正是上面那条判据要防的。
|
|
|
|
|
|
|
+ * {@code pay_trace_no}(同类的还有 {@code trace_no})—— 这些是**我们自己的对账号**
|
|
|
|
|
+ * ({@code mch_no} 还是我们自己发布给运维的应答字段名),收进来 = 删掉这条日志,
|
|
|
|
|
+ * 正是上面那两段判据里「不收」那半句要防的。
|
|
|
* {@code license_no}(营业执照号,统一社会信用代码,**可公开查询**)也不收。
|
|
* {@code license_no}(营业执照号,统一社会信用代码,**可公开查询**)也不收。
|
|
|
- * <b>要加,先来问</b>,别顺手。
|
|
|
|
|
|
|
+ * 另有 {@code sign} / {@code business_licence} / {@code code_url} 三个边界,
|
|
|
|
|
+ * 逐条理由见上面那段「三条已判定的边界」。<b>要加,先来问</b>,别顺手。
|
|
|
*
|
|
*
|
|
|
* <p>【★ 名单的形态约束:两两之间不得有前缀关系、不得重复】
|
|
* <p>【★ 名单的形态约束:两两之间不得有前缀关系、不得重复】
|
|
|
- * 当前 7 个两两之间 **0 对前缀关系**、无重复(判定器带正对照:对
|
|
|
|
|
|
|
+ * 当前 **11 个**两两之间 **0 对前缀关系**、无重复(判定器带正对照:对
|
|
|
* {@code ['auth_code','auth_code_v2']} 确实报得出 1 对)。
|
|
* {@code ['auth_code','auth_code_v2']} 确实报得出 1 对)。
|
|
|
* 有了这条,alternation 的**书写顺序**就是无关变量(无害改动:换个顺序行为不变)。
|
|
* 有了这条,alternation 的**书写顺序**就是无关变量(无害改动:换个顺序行为不变)。
|
|
|
* 将来若要加名字,先重跑这个检查再落地。
|
|
* 将来若要加名字,先重跑这个检查再落地。
|
|
@@ -196,7 +234,8 @@ public final class LeshouyingSensitive {
|
|
|
*/
|
|
*/
|
|
|
private static final Pattern MASKED_KEYS = Pattern.compile(
|
|
private static final Pattern MASKED_KEYS = Pattern.compile(
|
|
|
"(?i)([\"']?(?:auth_?code|legal_cert_no|account_idcard_num|principal_cert_no"
|
|
"(?i)([\"']?(?:auth_?code|legal_cert_no|account_idcard_num|principal_cert_no"
|
|
|
- + "|account_card_num|contact_phone|account_card_phone)[\"']?"
|
|
|
|
|
|
|
+ + "|account_card_num|contact_phone|account_card_phone"
|
|
|
|
|
+ + "|manager_phone|shop_phone|contact_service|check_code)[\"']?"
|
|
|
+ "\\s*[:=]\\s*[\"']?)[^\"'&\\s,}]*");
|
|
+ "\\s*[:=]\\s*[\"']?)[^\"'&\\s,}]*");
|
|
|
|
|
|
|
|
private static final String MASK = "***";
|
|
private static final String MASK = "***";
|