对包含负数的类对象进行哈希处理(使用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)的问题

问题是,您正在读取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-,0x64d,这很好。但是,例如,65535和65532将具有相同的"字符串"表示,因为0xFFFF0xFFFC都是不正确的代码点,并且将被解析为。当您将其提供给哈希函数时,两个输入字符串将完全相同。例如,对于负数,-3和-655532将产生不同的散列。

感谢大家的回答。我几乎已经朝着使用stream的方向发展了。ToArray()和Convert.ToBase64返回字符串。目前的结果看起来很有希望。

我很抱歉这个问题引起了很多"wtf",我理解接下来会有更多的人投反对票!我不是一个铁杆的C#开发人员,现在我正在做一个大项目。我也不应该参与这件事!试图把这个项目拼凑在一起有点挑战,尤其是当一个完成一半的变更涉及负数时。

再次感谢。