You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java实现与.NET SHA1加盐密码校验兼容的方案需求

解决.NET SHA1加盐哈希与Java跨平台校验不一致问题

我之前做系统迁移的时候刚好踩过这个字节符号的坑,太懂这种哈希对不上的抓狂了!核心问题就是你说的:.NET用无符号字节,Java用有符号字节,导致哈希字节转十六进制时出现差异,只要对齐几个关键步骤就能解决。

问题根源拆解

原.NET的SHA1哈希逻辑大概是:

  1. 密码转UTF-8字节数组
  2. 拼接密码字节与盐字节
  3. 计算SHA1哈希得到无符号字节数组
  4. 每个字节转两位十六进制字符串(小写/大写)存储

而Java的MessageDigest.digest()返回的是有符号字节数组(比如0xFF在Java里是-1),如果直接转十六进制会把负数解析成错误的高位值,所以必须先把有符号字节转为无符号整数再处理。

完整Java实现代码

下面是和原.NET逻辑完全对齐的校验代码,包含工具方法和核心校验逻辑:

1. 字节数组转十六进制字符串(对齐.NET的无符号处理)

private static String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        // 将Java有符号字节转为无符号整数(关键!)
        int unsignedByte = b & 0xFF;
        // 这里用%02x对应.NET的b.ToString("x2")(小写),如果原系统存大写就换成%02X
        sb.append(String.format("%02x", unsignedByte));
    }
    return sb.toString();
}

2. 十六进制字符串转字节数组(如果盐是存十六进制的情况)

如果原系统的盐是数据库里的十六进制字符串,用这个方法转成字节数组:

private static byte[] hexToBytes(String hexStr) {
    byte[] bytes = new byte[hexStr.length() / 2];
    for (int i = 0; i < hexStr.length(); i += 2) {
        String hexPair = hexStr.substring(i, i + 2);
        // 解析十六进制为无符号值,再转成Java有符号字节(不影响后续哈希计算)
        bytes[i / 2] = (byte) Integer.parseInt(hexPair, 16);
    }
    return bytes;
}

3. 核心密码校验方法

public static boolean verifyPassword(String inputPassword, String storedHash, String storedSaltHex) 
        throws UnsupportedEncodingException, NoSuchAlgorithmException {
    
    // 1. 解析存储的盐
    byte[] salt = hexToBytes(storedSaltHex);
    // 2. 密码转UTF-8字节(必须和.NET的Encoding.UTF8一致,不能用默认编码)
    byte[] passwordBytes = inputPassword.getBytes("UTF-8");
    // 3. 拼接密码与盐字节(和.NET的Buffer.BlockCopy逻辑一致)
    byte[] combinedBytes = new byte[passwordBytes.length + salt.length];
    System.arraycopy(passwordBytes, 0, combinedBytes, 0, passwordBytes.length);
    System.arraycopy(salt, 0, combinedBytes, passwordBytes.length, salt.length);
    // 4. 计算SHA1哈希
    MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
    byte[] hashBytes = sha1.digest(combinedBytes);
    // 5. 生成哈希字符串并与存储值比较
    String computedHash = bytesToHex(hashBytes);
    // 可以用equalsIgnoreCase兼容大小写,或者严格匹配看原系统情况
    return computedHash.equals(storedHash);
}

关键注意事项

  • 编码必须一致:一定要用UTF-8编码密码,不能用Java默认的平台编码(比如GBK),否则字节数组和.NET完全不一样。
  • 大小写匹配:如果原.NET用b.ToString("X2")生成大写十六进制,Java里就把%02x改成%02X,确保哈希字符串大小写一致。
  • 盐的存储格式:如果原系统的盐是存二进制(比如SQL Server的varbinary),直接读取字节数组即可,不需要用hexToBytes方法。

测试验证

你可以找一组原系统的密码、盐、哈希值,用上面的代码测试,比如:

  • 密码:"test123"
  • 盐:new byte[]{0x12, 0x34, 0x56}(十六进制字符串是"123456")
  • .NET生成的哈希:"7c4a8d09ca3762af61e59520943dc26494f8941b"

用Java代码计算出来的哈希应该完全一致,这样登录校验就没问题了!

内容的提问来源于stack exchange,提问作者fadi aljabali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:22:29