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


    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 代码:

    复制代码
    string orgKey = "HelloWorld";
    Console.WriteLine("待哈希字符串:" + orgKey);
    MD5 md = MD5.Create();
    byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
    byte[] buffer2 = md.ComputeHash(bytes);
    string str = "";
    for (int i = 0; i < buffer2.Length; i++)
    {
        str = str + buffer2[i].ToString("x2");
    }
    Console.WriteLine("哈希后转16进制小写:" + str);
    复制代码

    输出效果(小写x):

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

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

     

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

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

    输出效果(大写X):

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

     

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

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

    复制代码
    string orgKey = "HelloWorld";
    Console.WriteLine("待哈希字符串:" + orgKey);
    MD5 md = MD5.Create();
    byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
    byte[] buffer2 = md.ComputeHash(bytes);
    string str = "";
    str = BitConverter.ToString(buffer2);
    Console.WriteLine("哈希后用BitConverter转16进制原始:" + str);
    复制代码

    BitConverter.ToString 默认输出效果:

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

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

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

    效果:

    待哈希字符串:HelloWorld
    哈希后用BitConverter转16进制小写:68e109f0f40ca72a15e05cc22786f8e6
    结束 。

     

     

    二、JAVA MD5

    JAVA 算MD5代码:

     

    复制代码
    public static void main( String[] args )
        {
            try
            {
                String input = "HelloWorld";
                String md5Hash = getMD5Hash(input);
                System.out.println("待哈希字符串 '" + input + "' 16进制小写: " + md5Hash);
            }catch (Exception ex){
                System.out.println( "ex!"+ex.getMessage() );
            }
            System.out.println( "结束!" );
        }
    
        public static String getMD5Hash(String input) {
            try {
                byte[] byInput=input.getBytes();//默认是UTF-8
                // 获取MD5 MessageDigest实例
                MessageDigest md = MessageDigest.getInstance("MD5");
                // 更新要计算哈希的输入
                md.update(byInput);
                // 完成哈希计算并返回
                byte[] digest = md.digest();
                // 将字节数组转换为16进制的字符串
                StringBuilder sb = new StringBuilder();
                for (byte b : digest) {
                    sb.append(String.format("%02x", b & 0xff));
                }
                // 转换为小写(如果需要)
                return sb.toString().toLowerCase();
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("MD5 not supported", e);
            }
        }
    复制代码

     

     

    输出效果:

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

     

     

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

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

    复制代码
    String input = "HelloWorld";
                String javaMd5Hash = getMD5Hash(input);
                System.out.println("JAVA算出的MD5值:" + javaMd5Hash  );
                String netStr="68e109f0f40ca72a15e05cc22786f8e6";
                System.out.println(".NET算出的MD5值:" + netStr  );
                boolean bPP=netStr.equals(javaMd5Hash);
                System.out.println("两者是否匹配:" + bPP  );
    复制代码

    效果:

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

     

     

    四、MD5加盐

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

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

    代码模拟:

    复制代码
    static void Main(string[] args)
    {
        try
        {
            string targetMd5 = "e10adc3949ba59abbe56e057f20f883e";
            Console.WriteLine("目标MD5 值:" + targetMd5);
            for (int i = 100000; i <= 999999; i++)
            {
                string tmpMd5 = GetMd5(i.ToString());
                if (tmpMd5 == targetMd5) {
                    Console.WriteLine("MD5 已匹配,对应密码:" + i.ToString());
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("ex:"+ex.Message);
        }
        Console.WriteLine("结束 。"  );
        Console.ReadKey();
    }
    
    static string GetMd5(string orgKey) {             
        MD5 md = MD5.Create();
        byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
        byte[] buffer2 = md.ComputeHash(bytes);            
        string str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
        return str;
    }
    复制代码

    效果:

    目标MD5 值:e10adc3949ba59abbe56e057f20f883e
    MD5 已匹配,对应密码:123456
    结束 。

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

    复制代码
    static void Main(string[] args)
    {
        try
        {
            string orgStr = "123456";
            Console.WriteLine("原始字符串:" + orgStr);
            string salt = "salt10086";//硬编码或写配置文件中,一种场景固定一个盐值。
            Console.WriteLine("盐字符串:" + salt);
            string md5NoSalt = GetMd5(orgStr);
            Console.WriteLine("原始字符串:" + orgStr+ "不加盐:"+ md5NoSalt);
            string orgWithSalt = orgStr + salt;//盐值拼接在头部还是尾部,自行决定
            Console.WriteLine("原始字符串与盐拼接后的字符串:" + orgWithSalt);
            string md5WithSalt = GetMd5(orgWithSalt);
            Console.WriteLine("原始字符串:" + orgStr + "加盐:" + md5WithSalt);
        }
        catch (Exception ex)
        {
            Console.WriteLine("ex:"+ex.Message);
        }
        Console.WriteLine("结束 。"  );
        Console.ReadKey();
    }
    
    static string GetMd5(string orgKey) {             
        MD5 md = MD5.Create();
        byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
        byte[] buffer2 = md.ComputeHash(bytes);            
        string str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
        return str;
    }
    复制代码

    效果:

    原始字符串:123456
    盐字符串:salt10086
    原始字符串:123456不加盐:e10adc3949ba59abbe56e057f20f883e
    原始字符串与盐拼接后的字符串:123456salt10086
    原始字符串:123456加盐:6ff7189064eea9523e3814d440ec6adc
    结束 。

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

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

     

    五、常用MD5签名算法

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

    运算过程:

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

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

    3.使用&和=拼接,

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

    .NET代码:

    复制代码
    static void Main(string[] args)
    {
        try
        {
            //D、a、E三者的ASCII码分别为68、97、69
            //1.准备一个键值对集合
            Dictionary dic1 = new Dictionary();
            dic1.Add("Dip4","10086");
            dic1.Add("aip3", "10000");
            dic1.Add("Eip2", "10010");
            Console.WriteLine("1.准备一个键值对集合");
            foreach (string key in dic1.Keys) {
                Console.WriteLine("key:"+ key+" value:"+ dic1[key]);
            }
            Console.WriteLine("2.集合的键按ASCII排序");
            IDictionary dic2=HashUtil.AsciiDictionary(dic1);                
            foreach (string key in dic2.Keys)
            {
                Console.WriteLine("key:" + key + " value:" + dic2[key]);
            }
            Console.WriteLine("3.使用&和=拼接");
            string finalStr=HashUtil.BuildQueryString(dic2);
            Console.WriteLine("拼接后的字符串"+ finalStr);
            Console.WriteLine("4.算MD5哈希,注意对方要求的大小写");
            string md5Str = GetMd5(finalStr);
            Console.WriteLine("MD5哈希:" + md5Str);
        }
        catch (Exception ex)
        {
            Console.WriteLine("ex:"+ex.Message);
        }
        Console.WriteLine("结束 。"  );
        Console.ReadKey();
    }
    
    static string GetMd5(string orgKey) {             
        MD5 md = MD5.Create();
        byte[] bytes = Encoding.UTF8.GetBytes(orgKey);
        byte[] buffer2 = md.ComputeHash(bytes);            
        string str = BitConverter.ToString(buffer2).Replace("-", "").ToLower();
        return str;
    }
    复制代码

     

    HashUtil 工具类:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    
    namespace CommonUtils
    {
        /// 
        /// 工具类,runliuv,2024-06-12
        /// 
        public static class HashUtil
        {
    
            public static string GetMd5(string src)
            {
                MD5 md = MD5.Create();
                byte[] bytes = Encoding.UTF8.GetBytes(src);
                byte[] buffer2 = md.ComputeHash(bytes);
                string str = "";
                for (int i = 0; i < buffer2.Length; i++)
                {
                    str = str + buffer2[i].ToString("x2");
                }
                return str;
    
            }
    
            public static IDictionary ModelToDic(T1 cfgItem)
            {
                IDictionary sdCfgItem = new Dictionary();
    
                System.Reflection.PropertyInfo[] cfgItemProperties = cfgItem.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
                foreach (System.Reflection.PropertyInfo item in cfgItemProperties)
                {
                    string name = item.Name;
                    object value = item.GetValue(cfgItem, null);
                    if (value != null && (item.PropertyType.IsValueType || item.PropertyType.Name.StartsWith("String")) && !string.IsNullOrWhiteSpace(value.ToString()))
                    {
                        sdCfgItem.Add(name, value.ToString());
                    }
                }
    
                return sdCfgItem;
            }
    
            public static IDictionary AsciiDictionary(IDictionary sArray)
            {
                IDictionary asciiDic = new Dictionary();
                string[] arrKeys = sArray.Keys.ToArray();
                Array.Sort(arrKeys, string.CompareOrdinal);
                foreach (var key in arrKeys)
                {
                    string value = sArray[key];
                    asciiDic.Add(key, value);
                }
                return asciiDic;
            }
    
            public static string BuildQueryString(IDictionary sArray)
            {
    
                //拼接 K=V&A=B&c=1 这种URL
    
                StringBuilder sc = new StringBuilder();
    
                foreach (var item in sArray)
                {
                    string name = item.Key;
                    string value = item.Value;
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        sc.AppendFormat("{0}={1}&", name, value);
                    }
    
                }
    
                string fnlStr = sc.ToString();
                fnlStr = fnlStr.TrimEnd('&');
    
                return fnlStr;
            }
    
        }
    }
    复制代码

     

     

    .NET效果:

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

     

    JAVA代码:

    复制代码
    public static void main( String[] args )
        {
            try
            {
                // 创建一个未排序的Map
                Map unsortedMap = new HashMap<>();
                unsortedMap.put("Dip4", "10086");
                unsortedMap.put("aip3", "10000");
                unsortedMap.put("Eip2", "10010");
                // 创建一个新的TreeMap来保存排序后的键值对
                Map sortedMap = new TreeMap<>(unsortedMap);
                // 打印排序后的Map
                for (Map.Entry entry : sortedMap.entrySet()) {
                    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
                }
                //3.使用&和=拼接
                StringBuilder sb = new StringBuilder();
                for (Map.Entry entry : sortedMap.entrySet()) {
                    if (sb.length() > 0) {
                        sb.append("&"); // 在第一个键值对之后添加"&"
                    }
                    sb.append(entry.getKey()).append("=").append(entry.getValue());
                }
                String result = sb.toString();
                System.out.println(result); // 输出: key1=value1&key2=value2&key3=value3
                //4.算MD5哈希
                String javaMd5Hash = getMD5Hash(result);
                System.out.println("JAVA算出的MD5值:" + javaMd5Hash  );
    
            }catch (Exception ex){
                System.out.println( "ex!"+ex.getMessage() );
            }
            System.out.println( "结束!" );
        }
    
        public static String getMD5Hash(String input) {
            try {
                byte[] byInput=input.getBytes();//默认是UTF-8
                // 获取MD5 MessageDigest实例
                MessageDigest md = MessageDigest.getInstance("MD5");
                // 更新要计算哈希的输入
                md.update(byInput);
                // 完成哈希计算并返回
                byte[] digest = md.digest();
                // 将字节数组转换为16进制的字符串
                StringBuilder sb = new StringBuilder();
                for (byte b : digest) {
                    sb.append(String.format("%02x", b & 0xff));
                }
                // 转换为小写(如果需要)
                return sb.toString().toLowerCase();
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("MD5 not supported", e);
            }
        }
    复制代码

    JAVA 效果:

    Key = Dip4, Value = 10086
    Key = Eip2, Value = 10010
    Key = aip3, Value = 10000
    Dip4=10086&Eip2=10010&aip3=10000
    JAVA算出的MD5值:35dffab906c15e2f9d7d1f60c8817584

     

     

    --END

     

  • 相关阅读:
    Feed流之微博系统设计
    Playwright 入门介绍和使用指南
    三种内存分配的库比较
    【Linux】Ubuntu20.04版本安装谷歌中文输入法【教程】
    分享 TiDB Dashboard 会话
    Android混淆笔记
    web中缓存的几种方式
    Redis和Memcached网络模型详解
    python
    Jenkins 相关内容
  • 原文地址:https://www.cnblogs.com/runliuv/p/18242044