手动实现String.GetHashCode()?

时间:2017-09-06 18:37:10

标签: .net hash

我们有一个用.NET编写的遗留系统,我们正在迁移到Node.js。

原始系统使用("some string value").GetHashCode()根据用户数据生成一些令牌。

我正在寻找一种在JavaScript中实现此功能的方法,以便将系统的这一部分移植到Node.js。

因此,我对String.GetHashCode()实际如何运作感兴趣。在某处记录了算法吗?它甚至是一个稳定的算法,还是在各种.NET版本中都有变化?

我试图找到有关其实施的一些细节,但这对我来说真的很难,因为.NET不是我的主要技术,而且我并不熟悉它的资源和信息来源。

2 个答案:

答案 0 :(得分:1)

取自微软的Reference Source,其中一个实现是:

        // Gets a hash code for this string.  If strings A and B are such that A.Equals(B), then
        // they will return the same hash code.
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public override int GetHashCode() {

#if FEATURE_RANDOMIZED_STRING_HASHING
            if(HashHelpers.s_UseRandomizedStringHashing)
            {
                return InternalMarvin32HashString(this, this.Length, 0);
            }
#endif // FEATURE_RANDOMIZED_STRING_HASHING

            unsafe {
                fixed (char *src = this) {
                    Contract.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
                    Contract.Assert( ((int)src)%4 == 0, "Managed string should start at 4 bytes boundary");

#if WIN32
                    int hash1 = (5381<<16) + 5381;
#else
                    int hash1 = 5381;
#endif
                    int hash2 = hash1;

#if WIN32
                    // 32 bit machines.
                    int* pint = (int *)src;
                    int len = this.Length;
                    while (len > 2)
                    {
                        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
                        hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
                        pint += 2;
                        len  -= 4;
                    }

                    if (len > 0)
                    {
                        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
                    }
#else
                    int     c;
                    char *s = src;
                    while ((c = s[0]) != 0) {
                        hash1 = ((hash1 << 5) + hash1) ^ c;
                        c = s[1];
                        if (c == 0)
                            break;
                        hash2 = ((hash2 << 5) + hash2) ^ c;
                        s += 2;
                    }
#endif
#if DEBUG
                    // We want to ensure we can change our hash function daily.
                    // This is perfectly fine as long as you don't persist the
                    // value from GetHashCode to disk or count on String A 
                    // hashing before string B.  Those are bugs in your code.
                    hash1 ^= ThisAssembly.DailyBuildNumber;
#endif
                    return hash1 + (hash2 * 1566083941);
                }
            }
        }

这在.NET版本中是不稳定的,并且在分散在string.cs源代码中的注释中,它在同一进程中的AppDomain之间甚至可能不稳定。

如果你想要一个真实,稳定的哈希码,可以“安全”地保存在AppDomain之外,请查看System.Security.Cryptography中的哈希函数。 MD5对于低安全性工作是可以接受的,SHAx风格甚至更好。

True Hashes只是一种方式,不可能真正反转哈希,因为它是一个“有损”的过程。如果您获得代码的开发人员声称他们可以反转哈希,那么他们要么撒谎,要么弄错,要么没有实现正确的哈希。

答案 1 :(得分:1)

添加to Bradley's answer这是一个基于String.GetHashCode()的64位实现的稳定哈希代码,它不使用我之前为答案编写的不安全代码。

public static class StringExtensionMethods
{
    public static int GetStableHashCode(this string str)
    {
        unchecked
        {
            int hash1 = 5381;
            int hash2 = hash1;

            for(int i = 0; i < str.Length && str[i] != '\0'; i += 2)
            {
                hash1 = ((hash1 << 5) + hash1) ^ str[i];
                if (i == str.Length - 1 || str[i+1] == '\0')
                    break;
                hash2 = ((hash2 << 5) + hash2) ^ str[i+1];
            }

            return hash1 + (hash2*1566083941);
        }
    }
}