MessageDigest 计算的是消息摘要,不是可以解密还原的加密。它接收字节,按选定算法产生固定长度结果;相同字节与同一算法得到相同结果,但不能反过来把摘要当作原文的唯一编号。
update 会累积,digest 后会重置
下面是 Java 8 示例,演示分段输入与对象状态:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
public class DigestExample {
public static void main(String[] args) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update("a".getBytes(StandardCharsets.UTF_8));
byte[] first = md.digest("b".getBytes(StandardCharsets.UTF_8));
byte[] second = md.digest("b".getBytes(StandardCharsets.UTF_8));
byte[] expected = MessageDigest.getInstance("SHA-256")
.digest("ab".getBytes(StandardCharsets.UTF_8));
System.out.println(Arrays.equals(first, expected));
System.out.println(Arrays.equals(first, second));
}
}
first 计算 a 接着 b,也就是 ab 的摘要;digest 完成后状态重置,因此 second 只计算 b。这个例子对应的比较应分别为 true 和 false,不是说任意不同输入的摘要都必然不同。
digest(byte[]) 相当于先提供这批字节再完成计算;reset() 可以主动放弃当前累计状态。处理文本时指定字符编码,不要依赖机器默认值。Java 8 MessageDigest
有多个输入字段时,还要定义编码与边界,否则简单拼接可能把不同字段组合变成同一串字节。可变的摘要对象也不应被多个线程无保护地共享。
盐解决的不是“解密”问题
如果直接对密码作快速摘要,攻击者可以对常见密码逐个计算比较,也可以复用预计算结果。
为每份密码记录独立生成随机盐,再用适当的密码哈希方案计算,有助于让相同密码不简单产生相同存储结果,并阻止通用预计算表直接复用。盐通常可以与派生结果一起保存,不需要像密码那样保密。
登录验证时,使用同一记录的盐与参数重新派生后比较。固定一个所有用户共用的“秘密字符串”,不是随机盐的替代品;秘密 pepper 又是另一种需要单独管理的机制。
MD5 加盐不是现代密码存储方案
快速摘要的目的与密码哈希不同。仅把“密码+盐”交给 MD5,仍便于离线猜测;即使用普通 SHA-256 代替,也没有自动获得合适的工作因子和资源成本。
真实认证应使用成熟库与专门的密码哈希方案,例如按安全指南选择 Argon2id 等,并妥善保存版本与参数。OWASP 密码存储指南
上面的 SHA-256 代码只用于学习摘要 API,不是密码存储实现,也不代替真实认证系统的设计和安全检查。