String.regionMatches() 不适合加密指纹比对,因其非恒定时间,存在时序攻击风险;应先将十六进制或Base64字符串解码为字节数组,再用MessageDigest.isEqual()等恒定时间方法比较。
string.regionmatches() 不能用于安全地比对加密文本的“指纹段”是否一致,因为它不是恒定时间比较(timing-secure),存在**时序攻击风险**。如果你的目标是验证加密输出(如哈希、签名、密钥派生结果)中某一段是否完全匹配,必须使用**恒定时间比较算法**,而非 regionmatches()。
为什么 regionMatches() 不适合加密指纹比对
该方法在遇到第一个不匹配字符时立即返回 false,执行时间随不匹配位置提前而缩短。攻击者可通过高精度计时推测出匹配长度,逐步恢复敏感值(例如 HMAC 签名、AES-GCM 认证标签、PBKDF2 输出片段)。这在密码学场景中是严重漏洞。
即使你只比对“特定段”(比如哈希值的前 8 字节),只要该段承载认证或完整性语义,就必须防时序侧信道。
正确做法:用恒定时间字节数组比较
加密指纹本质是二进制数据。应先将字符串解码为字节数组(明确指定编码,如 UTF-8 或十六进制/ Base64 解码),再用恒定时间逻辑逐字节异或比较:
- 若指纹以十六进制字符串形式存在(如
"a1b2c3d4..."),先用Hex.decode(...)(Apache Commons Codec)或java.util.HexFormat(Java 17+)转为byte[] - 若为 Base64(如
"oTKj1M..."),用java.util.Base64.getDecoder().decode(...) - 调用恒定时间比较工具:
– Java 17+:直接使用MessageDigest.isEqual(byte[], byte[])
– 更早版本:手写或使用org.bouncycastle.crypto.params.KeyParameter.equals()等经审计的实现
– 自行实现时,务必遍历全部字节,累积异或结果,最后判断总和是否为 0
示例:安全比对 Hex 字符串表示的指纹前 16 字节
假设两个指纹字符串 f1 = "a1b2c3d4e5f678901234567890abcdef" 和 f2 = "a1b2c3d4e5f67890fedcba9876543210",需比对前 16 字节(即前 32 个 hex 字符):
- 用
HexFormat.of().parseHex(f1.substring(0, 32))得到byte[16] - 同理解析
f2.substring(0, 32) - 调用
MessageDigest.isEqual(bytes1, bytes2)—— 此方法内部已确保恒定时间
注意:切勿对原始字符串直接调用 regionMatches(),哪怕它们看起来“只是 ASCII 十六进制字符”——因为比较逻辑仍非恒定时间。
补充提醒:避免常见误用
- 不要依赖
String.equals()或regionMatches()做任何密码学意义的相等判断 - 确保截取段落长度一致;若长度不同,直接返回
false(但此判断本身也应恒定时间,例如先比较长度再进入字节循环,或统一补零至固定长) - 如果指纹来自不可信来源(如 HTTP 请求头),还需校验其格式合法性(如 hex 字符数是否为偶数),否则解码可能抛异常,破坏服务稳定性










