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<string,string> dic1 = new Dictionary<string,string>();
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<string, string> 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
{
/// <summary>
/// 工具类,runliuv,2024-06-12
/// </summary>
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<string, string> ModelToDic<T1>(T1 cfgItem)
{
IDictionary<string, string> sdCfgItem = new Dictionary<string, string>(); 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<string, string> AsciiDictionary(IDictionary<string, string> sArray)
{
IDictionary<string, string> asciiDic = new Dictionary<string, string>();
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<string, string> 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<String, String> unsortedMap = new HashMap<>();
unsortedMap.put("Dip4", "10086");
unsortedMap.put("aip3", "10000");
unsortedMap.put("Eip2", "10010");
// 创建一个新的TreeMap来保存排序后的键值对
Map<String, String> sortedMap = new TreeMap<>(unsortedMap);
// 打印排序后的Map
for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
//3.使用&和=拼接
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> 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

C#.NET与JAVA互通之MD5哈希V2024的更多相关文章

  1. java单向加密算法小结(2)--MD5哈希算法

    上一篇文章整理了Base64算法的相关知识,严格来说,Base64只能算是一种编码方式而非加密算法,这一篇要说的MD5,其实也不算是加密算法,而是一种哈希算法,即将目标文本转化为固定长度,不可逆的字符 ...

  2. java生成字符串md5函数类

    import java.security.MessageDigest; /** * Md5 工具 */ public class Md5Util { private static MessageDig ...

  3. Java 自带MD5加密 Demo

    package demo; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; pub ...

  4. Java 常见摘要算法——md5、sha1、sha256

    目录 摘要算法简介 md5 使用jdk内置方法实现md5加密 使用bc方式实现md5加密 使用cc方式实现md5加密 sha1 使用jdk内置方法实现sha1加密 使用bc方式实现sha1加密 使用c ...

  5. Java 两次MD5

    导入: import org.apache.commons.codec.digest.DigestUtils; 代码: public static String md5(String src) { r ...

  6. 全面解决.Net与Java互通时的RSA加解密问题,使用PEM格式的密钥文件

    作者: zyl910 一.缘由 RSA是一种常用的非对称加密算法.所以有时需要在不用编程语言中分别使用RSA的加密.解密.例如用Java做后台服务端,用C#开发桌面的客户端软件时. 由于 .Net.J ...

  7. Java基础-使用JAVA代码剖析MD5算法实现过程

    Java基础-使用JAVA代码剖析MD5算法实现过程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.

  8. java中使用MD5进行加密 BASE64Encoder 编码

    原文地址:http://www.cnblogs.com/weiwangnuanyang/articles/4326336.html java中使用MD5进行加密     在各种应用系统的开发中,经常需 ...

  9. java中使用MD5进行加密

    java中使用MD5进行加密     在各种应用系统的开发中,经常需要存储用户信息,很多地方都要存储用户密码,而将用户密码直接存储在服务器上显然是不安全的,本文简要介绍工作中常用的 MD5加密算法,希 ...

  10. Java 自带MD5 校验文件

    http://www.iteye.com/topic/1127319 前天第一次发表博客到论坛,关于Java文件监控一文,帖子地址在:http://www.iteye.com/topic/112728 ...

随机推荐

  1. 阿里云EMAS旗下低代码平台Mobi开放定向内测

    ​简介:[低代码深度共创]EMAS旗下低代码平台Mobi开放定向内测名额,限时限量,参与调研先到先得! Mobi是面向全端(Web.Native App.H5.全平台小程序等)场景,模型驱动的低代码开 ...

  2. DataX在数据迁移中的应用

    简介: DataX在数据迁移中的应用 1. DataX定义 首先简单介绍下datax是什么.DataX是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL.Oracle.SqlSe ...

  3. [FAQ] FinalCutPro 竖版视频 加模糊背景变 横版视频

    把一段影片拖到时间轴上面,注意自定义尺寸选择 1920 x 1080,因为竖版的是 1080 x 1920. 切换到仅视频,并选择变形,视频区左右拖动视频到最大. 设置模糊效果为高斯曲线. 切回到全部 ...

  4. [FAQ] uni-app 导航路由切换时如何强制刷新页面?

    使用 this.$forceUpdate() 强制刷新页面. Refer:uni-app自定义导航 Link:https://www.cnblogs.com/farwish/p/13870801.ht ...

  5. [Caddy2] Caddyfile 指令

    以下是 Caddyfile 的标准指令. acme_server An embedded ACME server basicauth Enforces HTTP Basic Authenticatio ...

  6. dotnet 通过 DockerfileContext 解决项目放在里层文件夹导致 VisualStudio 构建失败

    本文告诉大家,如何解决 csproj 项目文件放入到里层的文件夹,不放在 sln 所在文件夹的第一层子文件夹,导致 VisualStudio 2022 在构建 docker 映像提示找不到文件的问题 ...

  7. 2018-7-15-WPF-在-DrawingContext-的-push-如何使用

    title author date CreateTime categories WPF 在 DrawingContext 的 push 如何使用 lindexi 2018-7-15 15:51:0 + ...

  8. SQL server 游标使用实例

    --创建一个游标 DECLARE my_cursor CURSOR FOR SELECT id, Bran_number, Bran_taxis FROM dbo.Base_Branch; --打开游 ...

  9. Rancher管理K8s集群(14)

    一.Rancher介绍 1.1 Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台,实现了 Kubernetes 集群在混合云+本地 数据中心的集中部署与管理 ...

  10. 羽夏闲谈——TeeWorlds 中文问题

      不久前 削微寒 园友发布了一篇博文 误入 GitHub 游戏区,意外地收获颇丰 ,看到了一个游戏 TeeWorlds .有一说一挺好玩的,下面是那个博客的原图:   官方的下载连接:https:/ ...