hashcode varbinary(20)c#

时间:2016-03-17 13:14:01

标签: c# .net

我有这堂课:

public class SomeClass
{
    public string Str1 { get; set; }
    public string Str2 { get; set; }
    public string Str3 { get; set; }
    public string Str4 { get; set; }
}

我想创建一个hashkey,它作为varbinary(20)保存在数据库中,以确定类的唯一性(不区分大小写)。我想在这种情况下不能使用通常的GetHashCode方法。在这种情况下最佳做法是什么?

2 个答案:

答案 0 :(得分:3)

简单示例:

public class SomeClass
{
    public string Str1 { get; set; }
    public string Str2 { get; set; }
    public string Str3 { get; set; }
    public string Str4 { get; set; }

    public byte[] SHA256()
    {
        using (var sha256 = new SHA256Managed())
        {
            var strings = new[] { Str1, Str2, Str3, Str4 };

            for (int i = 0; i < strings.Length; i++)
            {
                string str = strings[i];

                if (str != null)
                {
                    // Commented lines are for using ToUpperInvariant()
                    //str = str.ToUpperInvariant()
                    byte[] length2 = BitConverter.GetBytes(str.Length);
                    sha256.TransformBlock(length2, 0, length2.Length, length2, 0);

                    // byte[] sortKeyBytes = Encoding.UTF8.GetBytes(str);
                    byte[] sortKeyBytes = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(str, CompareOptions.IgnoreCase).KeyData;

                    sha256.TransformBlock(sortKeyBytes, 0, sortKeyBytes.Length, sortKeyBytes, 0);
                } 
                else
                {
                    byte[] length2 = BitConverter.GetBytes(-1);
                    sha256.TransformBlock(length2, 0, length2.Length, length2, 0);
                }
            }

            sha256.TransformFinalBlock(new byte[0], 0, 0);

            byte[] hash = sha256.Hash;
            return hash;
        }
    }
}

我使用SHA256,解决方案基于@usr在https://stackoverflow.com/a/10452967/613130中建议的解决方案。生成的哈希码长度为32个字节,但您可以将其截断为20(显然您将减少其唯一性)。

我将各种字符串的长度添加到字符串中。这样{ "ABCD", "", "", "" }将生成与{ "A", "B", "C", "D" }不同的哈希值。

如果你愿意,你可以使用好的旧ToUpperInvariant()和基于它的哈希(代码中有一些注释行...你取消注释它们,移除byte[] sortKeyBytes = CultureInfo.InvariantCulture并过得开心:-) )。

我必须说实话,我不确定&#34;稳定性&#34; GetSortKey ... GetSortKey将在5年内返回相同的权重,在.NET 10.0中使用Unicode 11.0?谁知道?我当然不会!

MSDN表明他们可以改变:

  

如果应用程序序列化SortKey对象,当有新版本的.NET Framework时,应用程序必须重新生成所有排序键。

所以最后我建议基于.ToUpperInvariant()的替代解决方案(要清楚,如果我的老板让我这样做,我会告诉他:使用.ToUpperInvariant())。请注意,即使使用.ToUpperInvariant(),将来也可能会发生微小变化。可以为现有的小写字符引入新的大写字符。请参阅http://unicode.org/faq/casemap_charprop.html&#34;如果其中一对已经编码,是否可以添加案例对?&#34;

答案 1 :(得分:2)

varbinary(20)是160位,因此您正在寻找160位哈希算法。 SHA-1算法产生160位散列值。

您的问题的目的似乎是创建一个预期对于SomeClass的给定实例唯一的哈希值,因此您应该支持快速哈希算法而不是加密哈希算法。 SHA-1是一种加密算法,但速度非常快,.NET Framework中有一个实现。此外,还存在对SHA-1算法的攻击,因此您不应将其用于加密目的,而是选择SHA-256等算法(速度较慢)。

总而言之,我相信SHA-1非常适合您的问题。使用该算法很简单。 1)连接字符串,2)将它们转换为大写,3)使用合适的编码将它们转换为字节(我使用UTF-8)和4)计算哈希:

Byte[] GetHash(SomeClass someClass) {
  if (someClass == null)
    throw new ArgumentNullException("someClass");

  var byteBuffers = GetStrings(someClass).Select(
    s => String.IsNullOrEmpty(s)
         ? new Byte[0] : Encoding.UTF8.GetBytes(s.ToUpperInvariant())
  );
  var bytes = byteBuffers
    .Aggregate(new List<Byte>(), (l, b) => { l.AddRange(b); return l; })
    .ToArray();
  using (var sha1 = new SHA1Managed())
    return sha1.ComputeHash(bytes);
}

IEnumerable<String> GetStrings(SomeClass someClass) {
  yield return someClass.Str1;
  yield return someClass.Str2;
  yield return someClass.Str3;
  yield return someClass.Str4;
}

请注意,任何哈希算法(也称为加密算法)都会产生冲突。

Xanatos有一个非常好的观点:

  

我将各种字符串的长度添加到字符串中。这样{ "ABCD", "", "", "" }将生成与{ "A", "B", "C", "D" }不同的哈希值。

这是一个替代解决方案,以稍微不同的方式解决相同的问题,其中每个字符串长度模256包含在散列中:

Byte[] GetHash(SomeClass someClass) {
  if (someClass == null)
    throw new ArgumentNullException("someClass");

  var byteBuffers = GetBuffers(GetStrings(someClass));
  var bytes = byteBuffers
    .Aggregate(new List<Byte>(), (l, b) => { l.AddRange(b); return l; })
    .ToArray();
  using (var sha1 = new SHA1Managed())
    return sha1.ComputeHash(bytes);
}

IEnumerable<String> GetStrings(SomeClass someClass) {
  yield return someClass.Str1?.ToUpperInvariant();
  yield return someClass.Str2?.ToUpperInvariant();
  yield return someClass.Str3?.ToUpperInvariant();
  yield return someClass.Str4?.ToUpperInvariant();
}

IEnumerable<Byte[]> GetBuffers(IEnumerable<String> strings) {
  foreach (var @string in strings) {
    if (!String.IsNullOrEmpty(@string)) {
      yield return new[] { (Byte) (@string.Length%256) };
      yield return Encoding.UTF8.GetBytes(@string);
    }
    else
      yield return new Byte[1];
  }
}