使用 MessageDigest 进行加密散列
使用 SHA-256 对密码进行散列,添加盐值防御彩虹表攻击,并安全地比较摘要
使用 MessageDigest 进行加密散列 是 CoddyKit 上的免费 Java Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
什么是密码学哈希?
密码学哈希函数会将任意大小的数据映射为固定大小的 digest。它具有单向性(无法逆向还原)、确定性和抗碰撞性。哈希可用于密码存储、数据完整性检查和数字签名。
MessageDigest 应用程序接口
MessageDigest.getInstance("SHA-256") 会返回 SHA-256 摘要器。调用 digest(bytes) 可计算哈希并获取字节数组。
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest("password123".getBytes(StandardCharsets.UTF_8));
String hex = HexFormat.of().formatHex(hash);
System.out.println(hex); // 64-char hex string常见算法
JDK 支持:MD5(已被攻破,绝不能用于安全场景)、SHA-1(出于安全原因已弃用)、SHA-256(标准算法)、SHA-512(更强)、SHA3-256(现代算法)。对于新代码,请始终使用 SHA-256 或 SHA-512。
MessageDigest.getInstance("SHA-256"); // recommended
MessageDigest.getInstance("SHA-512"); // stronger
MessageDigest.getInstance("SHA3-256"); // SHA-3 family
// Never: MessageDigest.getInstance("MD5"); // cryptographically broken将哈希转换为十六进制字符串
字节数组结果必须转换为十六进制字符串,才能进行存储或比较。请使用 HexFormat(Java 17+),旧版本则使用 String.format。
// Java 17+:
String hex = HexFormat.of().formatHex(hash);
// Older Java:
StringBuilder sb = new StringBuilder();
for (byte b : hash) sb.append(String.format("%02x", b));
String hex = sb.toString();盐值:破解彩虹表
彩虹表会将常见密码映射到对应的哈希。加盐是在每个密码进行哈希前加入随机值,使彩虹表失效。请将盐值与哈希一起存储。
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(salt);
byte[] hash = md.digest("password".getBytes(StandardCharsets.UTF_8));
// Store: Base64.encode(salt) + ":" + Base64.encode(hash)密码哈希:请改用 BCrypt
带盐的 SHA-256 比直接使用 SHA-256 更好,但速度仍然太快,攻击者每秒可以尝试数十亿次猜测。请使用速度较慢的 KDF:密码场景可使用 BCryptPasswordEncoder(Spring Security)、PBKDF2 或 Argon2。
// Spring Security — proper password hashing:
PasswordEncoder encoder = new BCryptPasswordEncoder(12); // cost factor 12
String hash = encoder.encode("password123");
boolean match = encoder.matches("password123", hash); // true文件完整性检查
对文件计算哈希,以检测文件损坏或被篡改。将计算出的哈希与已知的正确值进行比较。
MessageDigest md = MessageDigest.getInstance("SHA-256");
try (InputStream is = Files.newInputStream(Path.of("app.jar"))) {
byte[] buf = new byte[8192];
int n;
while ((n = is.read(buf)) > 0) md.update(buf, 0, n);
}
String hash = HexFormat.of().formatHex(md.digest());
System.out.println("SHA-256: " + hash);安全地比较哈希
请使用 MessageDigest.isEqual() 进行常量时间比较,以防止时序攻击。比较哈希时,绝不要使用 String.equals()。
byte[] expected = computeHash(storedHash);
byte[] actual = computeHash(inputPassword);
boolean match = MessageDigest.isEqual(expected, actual); // constant-time连续更新
在调用 md.digest() 之前,可以多次调用 md.update(bytes),以便对分块到达的数据进行哈希处理(流式处理)。
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update("part1".getBytes());
md.update("part2".getBytes());
byte[] hash = md.digest(); // hash of "part1" + "part2"为存储编码哈希
请将二进制哈希存储为 Base64(比十六进制更短)或十六进制字符串。SHA-256 使用 Base64 时需要 43 个字符,而十六进制需要 64 个字符。两种方式都正确,请保持一致。
byte[] hash = md.digest(data);
String base64 = Base64.getEncoder().encodeToString(hash); // 44 chars (with padding)
String hex = HexFormat.of().formatHex(hash); // 64 chars用于流式处理的 DigestInputStream
请使用 DigestInputStream 包装 InputStream,以便在读取数据时计算哈希,避免将整个文件加载到内存中。
MessageDigest md = MessageDigest.getInstance("SHA-256");
try (DigestInputStream dis = new DigestInputStream(Files.newInputStream(path), md)) {
dis.transferTo(OutputStream.nullOutputStream());
}
String hash = HexFormat.of().formatHex(md.digest());快速检查
为什么带盐的 SHA-256 仍不建议用于密码存储?
回顾
请使用 MessageDigest.getInstance("SHA-256") 执行数据完整性检查。密码始终要加盐,但密码存储应使用 BCrypt 或 Argon2,而不是原始 SHA。请使用常量时间比较(MessageDigest.isEqual)来防止时序攻击。
常见问题解答
「使用 MessageDigest 进行加密散列」课时是免费的吗?
是的 — 「使用 MessageDigest 进行加密散列」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「使用 MessageDigest 进行加密散列」这节课中我会学到什么?
使用 SHA-256 对密码进行散列,添加盐值防御彩虹表攻击,并安全地比较摘要 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「使用 MessageDigest 进行加密散列」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 MessageDigest 进行加密散列
- AES 对称加密
- 用于消息完整性的 HMAC-SHA256
- JWT:结构、创建与验证