对包含负数的类对象进行哈希处理(使用BinaryFormatter)的问题
本文关键字:处理 使用 BinaryFormatter 哈希 问题 包含负 对象 | 更新日期: 2023-09-27 18:00:28
基本上,我们有许多POCO,我们将其转换为哈希值。其目的是使用哈希字符串作为该特定对象的唯一标识符。如果我们找到另一个具有相同值的对象,则哈希字符串应该是相同的,等等。
然而,我们遇到了一个问题,如果整数字段包含负数,则哈希结果似乎是相同的。
下面是我们对给定对象进行序列化和散列的扩展方法:-
public static string Serialize<T>(this T classObject) where T : class
{
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
formatter.Serialize(stream, classObject);
stream.Position = 0;
var sr = new StreamReader(stream);
var text = sr.ReadToEnd();
return text;
}
}
public static string ToHash(this string str)
{
var bytes = Encoding.UTF8.GetBytes(str);
var md5 = new SHA256CryptoServiceProvider();
byte[] result = md5.ComputeHash(bytes);
return Convert.ToBase64String(result);
}
为了演示这个问题,我创建了一个示例类:-
[Serializable]
public class TestClass
{
public string StringA;
public string StringB;
public int? Created;
}
这是我的测试代码。。。
var testZero = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = 0,
};
var testNull = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = null,
};
var testMinusOne = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = -1
};
var testMinusTwo = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = -2
};
var testMinusThree = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = -3
};
var testMinusOneHundred = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = -100
};
var testOneHundred = new TestClass
{
StringA = "String A",
StringB = "String B",
Created = 100
};
var rHashZero = testZero.Serialize().ToHash();
var rHashNull = testNull.Serialize().ToHash();
var rHashMinusOne = testMinusOne.Serialize().ToHash();
var rHashMinusTwo = testMinusTwo.Serialize().ToHash();
var rHashMinusThree = testMinusThree.Serialize().ToHash();
var rHashMinusHundred = testMinusOneHundred.Serialize().ToHash();
var rHashHundred = testOneHundred.Serialize().ToHash();
变量(末尾)包含以下值:-
rHashZero = "aFJROVaqEbWneZJkDnB00qkxPf4TF/w+22VhgR+4nHU=";
rHashNull = "0/tsIhQzZK+Jirnee1o8QTjU8G1hOB/ODdnr2UipBPU=";
rHashMinusOne = "Q5xsfYpm/Em2vw19N9283Gq9fUoI7WxN+ip61S/m3h0=";
rHashMinusTwo = "Q5xsfYpm/Em2vw19N9283Gq9fUoI7WxN+ip61S/m3h0=";
rHashMinusThree = "Q5xsfYpm/Em2vw19N9283Gq9fUoI7WxN+ip61S/m3h0=";
rHashMinusHundred = "Q5xsfYpm/Em2vw19N9283Gq9fUoI7WxN+ip61S/m3h0=";
rHashHundred = "3q6S9vZPujnSc5b2YAbtD61Dj+4B5ZzoILnL1lH291M=";
我的主要问题是,为什么具有负整数值的对象都返回相同的哈希字符串?尽管StringA和StringB是相同的,但Created字段并不相同。
如果有人能向我解释,那就太好了。还有,有解决方案吗?我还通过从int中删除nullable(?)进行了测试,但结果是一样的。
附言——我确信我遇到过一个网站,上面提到了一些关于负数的内容,但在后来的.net发布中,我确信它是"固定的"。这要追溯到一段时间前,所以该网站可能不再存在。
我试着在网上找到这方面的信息,但没有成功。也许我在搜索引擎上没有使用正确的单词?
感谢您的帮助。
问题是,您正在读取BinaryFormatter
的结果,就好像它是一个格式正确的UTF-16字符串一样。事实并非如此。
与ASCII不同,Unicode不是字节和字符之间简单的1:1映射。这意味着您设法使数据发生了错误。当您打印出SerializeMethod
:产生的字符串时,这是显而易见的
对于100个案例,我得到
□□□□□����□□□□□□□□□□□□□Cquery_rtzxks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null□□□□□□UserQuery+TestClass□□□□□StringA□StringB□Created□□□□System.Int32□□□□□□□□□□String A□□□□□□String B□□d□□□□
而对于-100,我得到
□□□□□����□□□□□□□□□□□□□Cquery_rtzxks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null□□□□□□UserQuery+TestClass□□□□□StringA□StringB□Created□□□□System.Int32□□□□□□□□□□String A□□□□□□String B□□����□
(名称空间等来自LINQPad。重要的一点是值,比如末尾的□����□
)
很明显,您的"转换"正在丢弃大量数据。由于内存的组织方式,这使得您的代码有时会出现,但是个例外-只是碰巧,序列化整数的一些值恰好是正确的unicode字符,这将导致不同的字符串-如果它们是而不是正确的字符,它们将是相同的。
解决方案很简单——不要假装随机字节序列是有效的UTF-16字符串。只要通过你从stream.ToArray()
得到的byte[]
就可以了。如果你出于某种原因绝对想要string
,可以使用Convert.ToBase64String
。
此外,由于这一点在你的问题中并不清楚,所以不要将哈希视为唯一的——它们不是。关系是"如果值相同,则哈希必须相同",但不是"如果哈希相同,则值必须相同"。所以在某种程度上,你的哈希函数是很好的,它不会违反这个关系。不过,它也没那么有用。
那么,为什么这会给负数带来麻烦呢?简短的回答是"没有"。这与数字如何保存在BinaryFormatter
中有关——负值确实很大,例如,-1将是0xFFFFFFFF
。当然,由于没有代码点映射,所以它们被转换为�
。另一方面,您使用的测试正值相对较小,并且很有可能达到类似ASCII的代码点。例如,值100是0x64000000
-,0x64
是d
,这很好。但是,例如,65535和65532将具有相同的"字符串"表示,因为0xFFFF
和0xFFFC
都是不正确的代码点,并且将被解析为�
。当您将其提供给哈希函数时,两个输入字符串将完全相同。例如,对于负数,-3和-655532将产生不同的散列。
感谢大家的回答。我几乎已经朝着使用stream的方向发展了。ToArray()和Convert.ToBase64返回字符串。目前的结果看起来很有希望。
我很抱歉这个问题引起了很多"wtf",我理解接下来会有更多的人投反对票!我不是一个铁杆的C#开发人员,现在我正在做一个大项目。我也不应该参与这件事!试图把这个项目拼凑在一起有点挑战,尤其是当一个完成一半的变更涉及负数时。
再次感谢。