跳转到正文

脱敏与编码

展示脱敏、证件格式检查、摘要和加解密解决不同问题。本页先给出展示与信息处理用法,再说明旧协议接口的实际支持范围;方法名为 encrypt 不一定表示执行了加密。

你的目的选择与结果
页面上隐藏手机号等字段脱敏展示,得到展示文本;保留原数据
从大陆身份证文本提取生日严格解析示例,得到生日或空结果
将文本转为 Base64,或计算固定算法摘要编码与摘要,分别使用 BASE64 或 SHA_256 等入口
对接已存在的加密协议先核对实际支持范围中的签名与限制

需要为新敏感数据设计加密方案时,本模块当前没有可直接推荐的完整入口;不要直接套用旧协议接口。下文保留这些接口的行为,便于维护已有调用。

脱敏展示

DesensitizeUtilDesensitizeRule 位于 com.own.constant.util.desensitize

java
import com.own.constant.util.desensitize.DesensitizeRule;
import com.own.constant.util.desensitize.DesensitizeUtil;

String masked = DesensitizeUtil.build("13812345678", DesensitizeRule.Template.PHONE);
System.out.println(masked); // 138****5678
DesensitizeRule.Template规则
NAME保留首字符,其余按长度替换为星号
PHONE匹配手机号格式时保留前三、后四位
PHONE_PREFIX匹配手机号格式时仅保留后四位
ID_CARD_FOUR对匹配的 15/18 位文本保留前六位和最后一位,名称中的 FOUR 不表示保留后四位
STAR_ALL / PASSWORD全部替换星号,两者为同一规则实例
API_SK长度大于 4 时保留前四位,否则全部替换
EMAIL对匹配的邮箱保留本地部分首字符与域名,其余用星号替换

规则对 null、空或全空白原样返回,长度按 UTF-16 code unit 计算。PHONE、EMAIL、ID_CARD_FOUR 对不符合各自格式的文本可能原样返回,因此不能把调用成功视为所有输入都已隐藏;应用需要先确定字段格式,或使用适合自身输入的规则。

DesensitizeRule<T> 继承 Function<T,T>,可以提供自定义 Lambda。DesensitizeUtil.build(content, rule) 对 null content 返回 null,其他内容调用规则。

另有 build(object, getter, setter, rule) 就地修改对象,以及 build(list, getter, setter, rule) 通过 parallelStream() 并行修改所有元素。列表和函数需要非 null,重复引用对象或共享可变状态可能产生竞态。展示处理宜对独立 DTO 操作,避免脱敏后覆盖仍需保存的原数据。

证件格式与信息提取

IdCardModelIdCardUtil 位于 com.own.constant.util.verification。它们计算格式、校验位和可提取字段,不连接身份核验服务。

大陆身份证模型

需要从外部输入提取生日时,先裁剪空白、检查长度及校验位,再补充严格日期和地区判断。以下 example.CardBirthday 提供可直接调用的解析方法,不把结果视为实名核验:

java
package example;

import com.own.constant.util.verification.IdCardUtil;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

public class CardBirthday {
    public static Optional<LocalDate> parse(String input) {
        if (input == null) {
            return Optional.empty();
        }
        String card = IdCardUtil.convert15CardTo18(input.trim());
        if (card == null || !IdCardUtil.validateIdCard18(card)
                || IdCardUtil.getProvinceByIdCard(card) == null) {
            return Optional.empty();
        }
        try {
            LocalDate birthday = LocalDate.parse(card.substring(6, 14),
                    DateTimeFormatter.BASIC_ISO_DATE);
            return birthday.isAfter(LocalDate.now()) ? Optional.empty() : Optional.of(birthday);
        } catch (java.time.format.DateTimeParseException e) {
            return Optional.empty();
        }
    }
}

调用 CardBirthday.parse(input) 后,通过上述检查的输入得到 Optional<LocalDate>;空输入、格式或校验位错误、未知省份、非法生日及未来生日得到 Optional.empty()。调用方据此展示生日或提示格式错误。

new IdCardModel(text) 会先转换 15 位旧格式,再执行校验与解析。有效时 isAvailable() 为 true,getIdCardInfo() 提供 gender、age、birthday、year/month/day、province 的 getter;无效时返回默认信息对象,性别为 NULL、数字为 0、其他为 null。

validateIdCard18 只检查前 17 位数字与校验位,不检查生日或地区。IdCardModel 的日期 Formatter 使用默认解析规则,部分不合理日期可能被调整,另一些会抛 DateTimeParseException;不能保证任意非法输入都只得到 available=false。

身份证字段与兼容方法速查
入口实际范围与限制
convert15CardTo1815 位固定补 19 年代并计算校验位;18 位原样返回;其他长度为 null
verificationIdCard15检查数字、地区并经转换校验
validateCardtrim 后依次尝试大陆和港澳台分支;不补足 18 位生日校验
validDate(year, month, day)年份要求大于等于 1930 且小于当前年份,当前年份日期会被拒绝
getAgeByIdCard当前年份减出生年份,不按生日计算周岁
getBirth/Year/Month/DateByIdCard按号码提取日期信息,不统一执行完整校验;空或非法输入返回空字符串、0 或 null,数字解析也可抛异常
getGenderByIdCard / getProvinceByIdCard从数字位或内置地区表取值,未知分别为 NULL / null;非法性别数字可抛异常

IdCardInfo.builder() 只是构造数据,不检查字段一致性。准确周岁应在取得有效生日后按应用规则计算。

港澳台兼容入口

IdCardModel 的前置转换只接受 15/18 位,因此不适用于港澳台证件。IdCardUtil.verificationIdCard10 返回地区、性别、字符串校验结果三个元素,无法识别返回 null。

澳门分支没有填充第三项,正则字符类还接受 |,因此不能当作有效验证结果;台湾分支允许小写进入,但内部字母映射未归一化,可能抛空指针;香港入口也保留特殊号码兼容限制。verificationTwCardvalidateHKCard 假定输入格式满足要求,不适合对任意外部文本直接调用。

编码与摘要

统一入口 com.own.constant.util.encrypt.EncryptUtil 暴露共享算法实例。大多数字段的声明类型为 BaseEncryptUtil,额外的实现方法并不都能直接从字段调用。

java
import com.own.constant.util.encrypt.EncryptUtil;

String encoded = EncryptUtil.BASE64.encrypt("hello"); // aGVsbG8=
String decoded = EncryptUtil.BASE64.decrypt(encoded); // hello
String digest = EncryptUtil.SHA_256.encrypt("payload");
入口结果与边界
BASE64.encrypt(String)UTF-8 标准 Base64;不是保密加密
BASE64.decrypt(String)Base64 解码后使用默认字符集构造文本;非法 Base64 抛异常
MD5.encrypt(String)32 位小写十六进制摘要
SHA_1/SHA_256.encrypt(String)对应算法的小写十六进制摘要
SHA.encrypt(String, algorithm)自选 MessageDigest 算法;算法不存在时记录日志并返回原文
LOOP.encrypt/decrypt(String)对每个 char 与字符 t 做 XOR,仅为可逆混淆

对象重载先用 Fastjson2 序列化为 JSON;带 Class 的解码重载再反序列化。调用方要区分 String 与 Object 重载,尤其是静态类型为 Object 的字符串会先变成 JSON 字符串。摘要不提供解密或密码存储协议。

com.own.constant.util.encrypt.util.BcdUtil.str2Bcd 对奇数长度前补 0;bcd2Str 会去掉一个前导 0。它按十进制数字输出半字节,不能作为任意十六进制往返工具,空数组解码也会抛异常。

加密接口的实际支持范围

BaseEncryptUtil 的默认 buildKey() 返回 null,默认字符串 encrypt/decrypt 原文返回。未覆盖的重载不会抛“不支持”,例如对 MD5 调用 decrypt 或对 AES_128 调用 encrypt 可能直接取得原文。先确认实现支持的签名,再判断调用结果。

AES/AES_128 的共享 Cipher 不适合并发调用,RSA_PUBLIC 的私钥随包分发,SHA_256_WITH_RSA 还缺少配套验签。原文回退、共享状态和密钥问题需要调用方解决,不能只通过“可往返”验证适用性。

旧协议接口支持矩阵与密钥格式

密钥对象 CustomEncryptKey 位于 com.own.constant.util.encrypt.model,builder 可设置 publicKey、privateKey、pk、secret、iv 和 isContent。构造不校验字段组合;isContent 默认 false,当前算法未读取它。

入口与有效调用当前实现限制
HMAC_SHA_1/HMAC_SHA_256.encrypt(content, key)content 用作 HMAC 密钥,key.secret 用作数据,与参数名称直觉相反;结果为 Base64,异常为空字符串
AES.encrypt/decrypt(String)AES/CBC/PKCS5Padding,静态 Cipher、固定 IV;key 用固定种子交给默认 SecureRandom 生成,不保证跨进程/Provider 重现;捕获的加解密异常返回空字符串
AES_128.decrypt(content, key)secret、iv 和密文均按 Base64 解码,使用 AES/CBC/PKCS7Padding;模块没有注册对应扩展 Provider,失败为空字符串;未实现有效 encrypt
RSA.buildKey()RSA.encrypt/decrypt(content, key)buildKey 生成 1024 位密钥;公钥 X.509、私钥 PKCS#8,均为 Base64;使用默认 RSA transformation,不分段,JCA 异常包装为普通 RuntimeException
RSA_PUBLIC.encrypt/decrypt(String)私钥随源码和 JAR 分发,不提供秘密密钥隔离
SHA_256_WITH_RSA.encrypt(content, key)先将 publicKey 文本字节当作 PKCS#8 私钥解析,常规输入通常在此失败并返回空字符串;没有配套验签入口

EncryptUtil 字段是可重新赋值的 public static,应用不要在请求处理中替换实例。非法 Base64、null 等也可能抛出算法 catch 范围外的异常,不能把所有失败都统一判断为空串。

PrivateKeyUtil.getPrivateKey(filename) / getPrivateKeyByText(text) 支持 PKCS#8 RSA PEM 或纯 Base64;只移除 BEGIN PRIVATE KEY 标记,不支持 BEGIN RSA PRIVATE KEY 的 PKCS#1 文本。文件、Base64 和密钥格式异常会直接抛出,不自动生成备用密钥。