经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
C#.NET与JAVA互通之MD5哈希V2024
来源:cnblogs  作者:runliuv  时间:2024/6/14 7:26:18  对本文有异议

C#.NET与JAVA互通之MD5哈希V2024

 

配套视频:

 

 

要点:

1.计算MD5时,SDK自带的计算哈希(ComputeHash)方法,输入输出参数都是byte数组。就涉及到字符串转byte数组转换时,编码选择的问题。

2.输入参数,字符串转byte数组时,编码双方要统一,一般为:UTF-8。

3.输出参数,byte数组转字符串时,编码双方要统一,一般为:16进制字符串(注意大小写);也有人选择BASE64字符串。

4.如果你的MD5用于存储密码,最好要加盐。

5.MD5用于签名,常见的运算过程。

 

开整:

一、.NET 算MD5

1.常用.NET MD5 代码:

  1. string orgKey = "HelloWorld";
  2. Console.WriteLine("待哈希字符串:" + orgKey);
  3. MD5 md = MD5.Create();
  4. byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
  5. byte[] buffer2 = md.ComputeHash(bytes);
  6. string str = "";
  7. for (int i = 0; i < buffer2.Length; i++)
  8. {
  9. str = str + buffer2[i].ToString("x2");
  10. }
  11. Console.WriteLine("哈希后转16进制小写:" + str);

输出效果(小写x):

  1. 待哈希字符串:HelloWorld
  2. 哈希后转16进制小写:68e109f0f40ca72a15e05cc22786f8e6

注意:.ToString("x2") 里面的x是小写,即输出16进制小写。

 

 如果X是大写,则输出16进制大写字符串。代码如下:

  1. str = "";
  2. for (int i = 0; i < buffer2.Length; i++)
  3. {
  4. str = str + buffer2[i].ToString("X2");
  5. }
  6. Console.WriteLine("哈希后转16进制大写:" + str);

输出效果(大写X):

  1. 待哈希字符串:HelloWorld
  2. 哈希后转16进制大写:68E109F0F40CA72A15E05CC22786F8E6

 

如果不使用.ToString("x2") ,我们还可以使用BitConverter来转换为16进制字符串。

但BitConverter默认转出的字符串是大写并带“-”符号。代码如下:

  1. string orgKey = "HelloWorld";
  2. Console.WriteLine("待哈希字符串:" + orgKey);
  3. MD5 md = MD5.Create();
  4. byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
  5. byte[] buffer2 = md.ComputeHash(bytes);
  6. string str = "";
  7. str = BitConverter.ToString(buffer2);
  8. Console.WriteLine("哈希后用BitConverter转16进制原始:" + str);

BitConverter.ToString 默认输出效果:

  1. 待哈希字符串:HelloWorld
  2. 哈希后用BitConverter16进制原始:68-E1-09-F0-F4-0C-A7-2A-15-E0-5C-C2-27-86-F8-E6
  3. 结束

我们BitConverter.ToString转小写,并不带“-”符号。代码如下:

  1. string orgKey = "HelloWorld";
  2. Console.WriteLine("待哈希字符串:" + orgKey);
  3. MD5 md = MD5.Create();
  4. byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
  5. byte[] buffer2 = md.ComputeHash(bytes);
  6. string str = "";
  7. str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
  8. Console.WriteLine("哈希后用BitConverter转16进制小写:" + str);

效果:

  1. 待哈希字符串:HelloWorld
  2. 哈希后用BitConverter16进制小写:68e109f0f40ca72a15e05cc22786f8e6
  3. 结束

 

 

二、JAVA MD5

JAVA 算MD5代码:

 

  1. public static void main( String[] args )
  2. {
  3. try
  4. {
  5. String input = "HelloWorld";
  6. String md5Hash = getMD5Hash(input);
  7. System.out.println("待哈希字符串 '" + input + "' 16进制小写: " + md5Hash);
  8. }catch (Exception ex){
  9. System.out.println( "ex!"+ex.getMessage() );
  10. }
  11. System.out.println( "结束!" );
  12. }
  13. public static String getMD5Hash(String input) {
  14. try {
  15. byte[] byInput=input.getBytes();//默认是UTF-8
  16. // 获取MD5 MessageDigest实例
  17. MessageDigest md = MessageDigest.getInstance("MD5");
  18. // 更新要计算哈希的输入
  19. md.update(byInput);
  20. // 完成哈希计算并返回
  21. byte[] digest = md.digest();
  22. // 将字节数组转换为16进制的字符串
  23. StringBuilder sb = new StringBuilder();
  24. for (byte b : digest) {
  25. sb.append(String.format("%02x", b & 0xff));
  26. }
  27. // 转换为小写(如果需要)
  28. return sb.toString().toLowerCase();
  29. } catch (NoSuchAlgorithmException e) {
  30. throw new RuntimeException("MD5 not supported", e);
  31. }
  32. }

 

 

输出效果:

  1. 待哈希字符串 'HelloWorld' 16进制小写: 68e109f0f40ca72a15e05cc22786f8e6
  2. 结束!

 

 

三、同一个字符串 .NET MD5 与 JAVA 是否一致

双方待哈希字符串都为“HelloWorld”,且转16进制小写。将.NET算得的结果,放到JAVA中进行比较:

  1. String input = "HelloWorld";
  2. String javaMd5Hash = getMD5Hash(input);
  3. System.out.println("JAVA算出的MD5值:" + javaMd5Hash );
  4. String netStr="68e109f0f40ca72a15e05cc22786f8e6";
  5. System.out.println(".NET算出的MD5值:" + netStr );
  6. boolean bPP=netStr.equals(javaMd5Hash);
  7. System.out.println("两者是否匹配:" + bPP );

效果:

  1. JAVA算出的MD5值:68e109f0f40ca72a15e05cc22786f8e6
  2. .NET算出的MD5值:68e109f0f40ca72a15e05cc22786f8e6
  3. 两者是否匹配:true
  4. 结束!

 

 

四、MD5加盐

假设你的登录名为:HelloWorld,密码是:123456,密码用MD5 HASH后存储,未加盐,数据库被脱库,对方得到了HelloWorld对应的密码存储MD5值:e10adc3949ba59abbe56e057f20f883e。

对方就可以用撞库,即:计算从 100000 到 999999 之间的MD5 HASH值,每次算出MD5值后,与e10adc3949ba59abbe56e057f20f883e比对,如果相等,则反推出HelloWorld的密码。

代码模拟:

  1. static void Main(string[] args)
  2. {
  3. try
  4. {
  5. string targetMd5 = "e10adc3949ba59abbe56e057f20f883e";
  6. Console.WriteLine("目标MD5 值:" + targetMd5);
  7. for (int i = 100000; i <= 999999; i++)
  8. {
  9. string tmpMd5 = GetMd5(i.ToString());
  10. if (tmpMd5 == targetMd5) {
  11. Console.WriteLine("MD5 已匹配,对应密码:" + i.ToString());
  12. break;
  13. }
  14. }
  15. }
  16. catch (Exception ex)
  17. {
  18. Console.WriteLine("ex:"+ex.Message);
  19. }
  20. Console.WriteLine("结束 。" );
  21. Console.ReadKey();
  22. }
  23. static string GetMd5(string orgKey) {
  24. MD5 md = MD5.Create();
  25. byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
  26. byte[] buffer2 = md.ComputeHash(bytes);
  27. string str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
  28. return str;
  29. }

效果:

  1. 目标MD5 值:e10adc3949ba59abbe56e057f20f883e
  2. MD5 已匹配,对应密码:123456
  3. 结束

所谓加盐,就是将原始字符串的头部或尾部加上一段固定的字符串,然后再去算MD5值。大致代码如下:

  1. static void Main(string[] args)
  2. {
  3. try
  4. {
  5. string orgStr = "123456";
  6. Console.WriteLine("原始字符串:" + orgStr);
  7. string salt = "salt10086";//硬编码或写配置文件中,一种场景固定一个盐值。
  8. Console.WriteLine("盐字符串:" + salt);
  9. string md5NoSalt = GetMd5(orgStr);
  10. Console.WriteLine("原始字符串:" + orgStr+ "不加盐:"+ md5NoSalt);
  11. string orgWithSalt = orgStr + salt;//盐值拼接在头部还是尾部,自行决定
  12. Console.WriteLine("原始字符串与盐拼接后的字符串:" + orgWithSalt);
  13. string md5WithSalt = GetMd5(orgWithSalt);
  14. Console.WriteLine("原始字符串:" + orgStr + "加盐:" + md5WithSalt);
  15. }
  16. catch (Exception ex)
  17. {
  18. Console.WriteLine("ex:"+ex.Message);
  19. }
  20. Console.WriteLine("结束 。" );
  21. Console.ReadKey();
  22. }
  23. static string GetMd5(string orgKey) {
  24. MD5 md = MD5.Create();
  25. byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
  26. byte[] buffer2 = md.ComputeHash(bytes);
  27. string str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
  28. return str;
  29. }

效果:

  1. 原始字符串:123456
  2. 盐字符串:salt10086
  3. 原始字符串:123456不加盐:e10adc3949ba59abbe56e057f20f883e
  4. 原始字符串与盐拼接后的字符串:123456salt10086
  5. 原始字符串:123456加盐:6ff7189064eea9523e3814d440ec6adc
  6. 结束

不加盐:e10adc3949ba59abbe56e057f20f883e,加盐:6ff7189064eea9523e3814d440ec6adc,明显不一致。

如果你的密码加了盐,对方不仅要脱库,还要知道你的盐是多少,盐是如何拼接的,才能撞库出原密码。

 

五、常用MD5签名算法

.NET注意:对KEY排序时,无论是Array.Sort,还是SortedDictionary,必须要加 string.CompareOrdinal 参数,不然默认不区分大小写,和JAVA默认区分大小写的行为不一致,导致双方签名不一致。

运算过程:

1.准备一个键值对集合,

2.集合的键按ASCII 从小到大排序,

3.使用&和=拼接,

4.算MD5哈希,注意对方要求的大小写。

.NET代码:

  1. static void Main(string[] args)
  2. {
  3. try
  4. {
  5. //D、a、E三者的ASCII码分别为68、97、69
  6. //1.准备一个键值对集合
  7. Dictionary<string,string> dic1 = new Dictionary<string,string>();
  8. dic1.Add("Dip4","10086");
  9. dic1.Add("aip3", "10000");
  10. dic1.Add("Eip2", "10010");
  11. Console.WriteLine("1.准备一个键值对集合");
  12. foreach (string key in dic1.Keys) {
  13. Console.WriteLine("key:"+ key+" value:"+ dic1[key]);
  14. }
  15. Console.WriteLine("2.集合的键按ASCII排序");
  16. IDictionary<string, string> dic2=HashUtil.AsciiDictionary(dic1);
  17. foreach (string key in dic2.Keys)
  18. {
  19. Console.WriteLine("key:" + key + " value:" + dic2[key]);
  20. }
  21. Console.WriteLine("3.使用&和=拼接");
  22. string finalStr=HashUtil.BuildQueryString(dic2);
  23. Console.WriteLine("拼接后的字符串"+ finalStr);
  24. Console.WriteLine("4.算MD5哈希,注意对方要求的大小写");
  25. string md5Str = GetMd5(finalStr);
  26. Console.WriteLine("MD5哈希:" + md5Str);
  27. }
  28. catch (Exception ex)
  29. {
  30. Console.WriteLine("ex:"+ex.Message);
  31. }
  32. Console.WriteLine("结束 。" );
  33. Console.ReadKey();
  34. }
  35. static string GetMd5(string orgKey) {
  36. MD5 md = MD5.Create();
  37. byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
  38. byte[] buffer2 = md.ComputeHash(bytes);
  39. string str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
  40. return str;
  41. }

 

HashUtil 工具类:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Security.Cryptography;
  5. using System.Text;
  6. namespace CommonUtils
  7. {
  8. /// <summary>
  9. /// 工具类,runliuv,2024-06-12
  10. /// </summary>
  11. public static class HashUtil
  12. {
  13. public static string GetMd5(string src)
  14. {
  15. MD5 md = MD5.Create();
  16. byte[] bytes = Encoding.UTF8.GetBytes(src);
  17. byte[] buffer2 = md.ComputeHash(bytes);
  18. string str = "";
  19. for (int i = 0; i < buffer2.Length; i++)
  20. {
  21. str = str + buffer2[i].ToString("x2");
  22. }
  23. return str;
  24. }
  25. public static IDictionary<string, string> ModelToDic<T1>(T1 cfgItem)
  26. {
  27. IDictionary<string, string> sdCfgItem = new Dictionary<string, string>();
  28. System.Reflection.PropertyInfo[] cfgItemProperties = cfgItem.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
  29. foreach (System.Reflection.PropertyInfo item in cfgItemProperties)
  30. {
  31. string name = item.Name;
  32. object value = item.GetValue(cfgItem, null);
  33. if (value != null && (item.PropertyType.IsValueType || item.PropertyType.Name.StartsWith("String")) && !string.IsNullOrWhiteSpace(value.ToString()))
  34. {
  35. sdCfgItem.Add(name, value.ToString());
  36. }
  37. }
  38. return sdCfgItem;
  39. }
  40. public static IDictionary<string, string> AsciiDictionary(IDictionary<string, string> sArray)
  41. {
  42. IDictionary<string, string> asciiDic = new Dictionary<string, string>();
  43. string[] arrKeys = sArray.Keys.ToArray();
  44. Array.Sort(arrKeys, string.CompareOrdinal);
  45. foreach (var key in arrKeys)
  46. {
  47. string value = sArray[key];
  48. asciiDic.Add(key, value);
  49. }
  50. return asciiDic;
  51. }
  52. public static string BuildQueryString(IDictionary<string, string> sArray)
  53. {
  54. //拼接 K=V&A=B&c=1 这种URL
  55. StringBuilder sc = new StringBuilder();
  56. foreach (var item in sArray)
  57. {
  58. string name = item.Key;
  59. string value = item.Value;
  60. if (!string.IsNullOrWhiteSpace(value))
  61. {
  62. sc.AppendFormat("{0}={1}&", name, value);
  63. }
  64. }
  65. string fnlStr = sc.ToString();
  66. fnlStr = fnlStr.TrimEnd('&');
  67. return fnlStr;
  68. }
  69. }
  70. }

 

 

.NET效果:

  1. 1.准备一个键值对集合
  2. key:Dip4 value:10086
  3. key:aip3 value:10000
  4. key:Eip2 value:10010
  5. 2.集合的键按ASCII排序
  6. key:Dip4 value:10086
  7. key:Eip2 value:10010
  8. key:aip3 value:10000
  9. 3.使用&和=拼接
  10. 拼接后的字符串Dip4=10086&Eip2=10010&aip3=10000
  11. 4.MD5哈希,注意对方要求的大小写
  12. MD5哈希:35dffab906c15e2f9d7d1f60c8817584

 

JAVA代码:

  1. public static void main( String[] args )
  2. {
  3. try
  4. {
  5. // 创建一个未排序的Map
  6. Map<String, String> unsortedMap = new HashMap<>();
  7. unsortedMap.put("Dip4", "10086");
  8. unsortedMap.put("aip3", "10000");
  9. unsortedMap.put("Eip2", "10010");
  10. // 创建一个新的TreeMap来保存排序后的键值对
  11. Map<String, String> sortedMap = new TreeMap<>(unsortedMap);
  12. // 打印排序后的Map
  13. for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
  14. System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
  15. }
  16. //3.使用&和=拼接
  17. StringBuilder sb = new StringBuilder();
  18. for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
  19. if (sb.length() > 0) {
  20. sb.append("&"); // 在第一个键值对之后添加"&"
  21. }
  22. sb.append(entry.getKey()).append("=").append(entry.getValue());
  23. }
  24. String result = sb.toString();
  25. System.out.println(result); // 输出: key1=value1&key2=value2&key3=value3
  26. //4.算MD5哈希
  27. String javaMd5Hash = getMD5Hash(result);
  28. System.out.println("JAVA算出的MD5值:" + javaMd5Hash );
  29. }catch (Exception ex){
  30. System.out.println( "ex!"+ex.getMessage() );
  31. }
  32. System.out.println( "结束!" );
  33. }
  34. public static String getMD5Hash(String input) {
  35. try {
  36. byte[] byInput=input.getBytes();//默认是UTF-8
  37. // 获取MD5 MessageDigest实例
  38. MessageDigest md = MessageDigest.getInstance("MD5");
  39. // 更新要计算哈希的输入
  40. md.update(byInput);
  41. // 完成哈希计算并返回
  42. byte[] digest = md.digest();
  43. // 将字节数组转换为16进制的字符串
  44. StringBuilder sb = new StringBuilder();
  45. for (byte b : digest) {
  46. sb.append(String.format("%02x", b & 0xff));
  47. }
  48. // 转换为小写(如果需要)
  49. return sb.toString().toLowerCase();
  50. } catch (NoSuchAlgorithmException e) {
  51. throw new RuntimeException("MD5 not supported", e);
  52. }
  53. }

JAVA 效果:

  1. Key = Dip4, Value = 10086
  2. Key = Eip2, Value = 10010
  3. Key = aip3, Value = 10000
  4. Dip4=10086&Eip2=10010&aip3=10000
  5. JAVA算出的MD5值:35dffab906c15e2f9d7d1f60c8817584

 

 

--END

 

原文链接:https://www.cnblogs.com/runliuv/p/18242044

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号