Java实现与.NET SHA1加盐密码校验兼容的方案需求
解决.NET SHA1加盐哈希与Java跨平台校验不一致问题
我之前做系统迁移的时候刚好踩过这个字节符号的坑,太懂这种哈希对不上的抓狂了!核心问题就是你说的:.NET用无符号字节,Java用有符号字节,导致哈希字节转十六进制时出现差异,只要对齐几个关键步骤就能解决。
问题根源拆解
原.NET的SHA1哈希逻辑大概是:
- 密码转UTF-8字节数组
- 拼接密码字节与盐字节
- 计算SHA1哈希得到无符号字节数组
- 每个字节转两位十六进制字符串(小写/大写)存储
而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
相关产品推荐
相关产品推荐

