【问题标题】:Hash function that produces short hashes?产生短散列的散列函数?
【发布时间】:2023-03-23 07:27:01
【问题描述】:

有没有一种加密方法可以采用任意长度的字符串并产生一个小于 10 个字符的散列?我想根据消息内容而不是随机生成合理唯一的 ID。

如果任意长度的字符串是不可能的,我可以忍受将消息限制为整数值。但是,在这种情况下,两个连续整数的哈希值不能相似。

【问题讨论】:

标签: encryption uniqueidentifier


【解决方案1】:

您可以使用 Python 的 hashlib 库。 shake_128shake_256 算法提供可变长度的散列。这是一些工作代码(Python3):

import hashlib
>>> my_string = 'hello shake'
>>> hashlib.shake_256(my_string.encode()).hexdigest(5)
'34177f6a0a'

请注意,使用长度参数 x(例如 5)该函数返回长度为 2x 的哈希值。

【讨论】:

    【解决方案2】:

    现在是 2019 年,还有更好的选择。即xxhash

    ~ echo test | xxhsum                                                           
    2d7f1808da1fa63c  stdin
    

    【讨论】:

    • 此链接已损坏。最好提供更完整的答案。
    • 链接现在有效。
    【解决方案3】:

    我最近需要一些类似于简单字符串缩减功能的东西。基本上,代码看起来像这样(前面的 C/C++ 代码):

    size_t ReduceString(char *Dest, size_t DestSize, const char *Src, size_t SrcSize, bool Normalize)
    {
        size_t x, x2 = 0, z = 0;
    
        memset(Dest, 0, DestSize);
    
        for (x = 0; x < SrcSize; x++)
        {
            Dest[x2] = (char)(((unsigned int)(unsigned char)Dest[x2]) * 37 + ((unsigned int)(unsigned char)Src[x]));
            x2++;
    
            if (x2 == DestSize - 1)
            {
                x2 = 0;
                z++;
            }
        }
    
        // Normalize the alphabet if it looped.
        if (z && Normalize)
        {
            unsigned char TempChr;
            y = (z > 1 ? DestSize - 1 : x2);
            for (x = 1; x < y; x++)
            {
                TempChr = ((unsigned char)Dest[x]) & 0x3F;
    
                if (TempChr < 10)  TempChr += '0';
                else if (TempChr < 36)  TempChr = TempChr - 10 + 'A';
                else if (TempChr < 62)  TempChr = TempChr - 36 + 'a';
                else if (TempChr == 62)  TempChr = '_';
                else  TempChr = '-';
    
                Dest[x] = (char)TempChr;
            }
        }
    
        return (SrcSize < DestSize ? SrcSize : DestSize);
    }
    

    它可能有比预期更多的冲突,但它不打算用作加密哈希函数。如果您遇到太多冲突,您可以尝试各种乘数(即将 37 更改为另一个素数)。这个 sn-p 的一个有趣特性是,当 Src 比 Dest 短时,Dest 以原样输入字符串结束(0 * 37 + value = value)。如果您希望在流程结束时获得“可读”的内容,Normalize 将以增加冲突为代价来调整转换后的字节。

    来源:

    https://github.com/cubiclesoft/cross-platform-cpp/blob/master/sync/sync_util.cpp

    【讨论】:

    • std::hash 不能解决某些用例(例如,当只需要几行额外的代码就足够时,避免拖入臃肿的 std:: 模板)。这里没有什么可笑的。处理 Mac OSX 中的主要限制是经过深思熟虑的。我不想要一个整数。为此,我可以使用 djb2 并且仍然避免使用 std:: 模板。
    • 这听起来还是很傻。当哈希本身如此糟糕时,为什么您永远使用大于 4(32 位)的DestSize?如果您希望输出大于 int 所提供的碰撞阻力,则可以使用 SHA。
    • 看,它并不是真正的传统哈希。它具有有用的属性,用户可以在某些操作系统(例如 Mac OSX)上缓冲区空间非常有限的地方声明字符串大小,并且结果必须适合真实文件名的有限域并且他们不想只是截断名称,因为这会导致冲突(但较短的字符串会被单独保留)。加密哈希并不总是正确的答案,std::hash 也不总是正确的答案。
    【解决方案4】:

    如果您需要"sub-10-character hash" 您可以使用 Fletcher-32 算法产生 8 个字符散列(32 位)、CRC-32Adler-32

    CRC-32 比 Adler32 慢 20% - 100%。

    Fletcher-32 比 Adler-32 稍微可靠一些。它的计算成本低于 Adler 校验和:Fletcher vs Adler comparison

    下面给出了一个带有几个 Fletcher 实现的示例程序:

        #include <stdio.h>
        #include <string.h>
        #include <stdint.h> // for uint32_t
    
        uint32_t fletcher32_1(const uint16_t *data, size_t len)
        {
                uint32_t c0, c1;
                unsigned int i;
    
                for (c0 = c1 = 0; len >= 360; len -= 360) {
                        for (i = 0; i < 360; ++i) {
                                c0 = c0 + *data++;
                                c1 = c1 + c0;
                        }
                        c0 = c0 % 65535;
                        c1 = c1 % 65535;
                }
                for (i = 0; i < len; ++i) {
                        c0 = c0 + *data++;
                        c1 = c1 + c0;
                }
                c0 = c0 % 65535;
                c1 = c1 % 65535;
                return (c1 << 16 | c0);
        }
    
        uint32_t fletcher32_2(const uint16_t *data, size_t l)
        {
            uint32_t sum1 = 0xffff, sum2 = 0xffff;
    
            while (l) {
                unsigned tlen = l > 359 ? 359 : l;
                l -= tlen;
                do {
                    sum2 += sum1 += *data++;
                } while (--tlen);
                sum1 = (sum1 & 0xffff) + (sum1 >> 16);
                sum2 = (sum2 & 0xffff) + (sum2 >> 16);
            }
            /* Second reduction step to reduce sums to 16 bits */
            sum1 = (sum1 & 0xffff) + (sum1 >> 16);
            sum2 = (sum2 & 0xffff) + (sum2 >> 16);
            return (sum2 << 16) | sum1;
        }
    
        int main()
        {
            char *str1 = "abcde";  
            char *str2 = "abcdef";
    
            size_t len1 = (strlen(str1)+1) / 2; //  '\0' will be used for padding 
            size_t len2 = (strlen(str2)+1) / 2; // 
    
            uint32_t f1 = fletcher32_1(str1,  len1);
            uint32_t f2 = fletcher32_2(str1,  len1);
    
            printf("%u %X \n",    f1,f1);
            printf("%u %X \n\n",  f2,f2);
    
            f1 = fletcher32_1(str2,  len2);
            f2 = fletcher32_2(str2,  len2);
    
            printf("%u %X \n",f1,f1);
            printf("%u %X \n",f2,f2);
    
            return 0;
        }
    

    输出:

    4031760169 F04FC729                                                                                                                                                                                                                              
    4031760169 F04FC729                                                                                                                                                                                                                              
    
    1448095018 56502D2A                                                                                                                                                                                                                              
    1448095018 56502D2A                                                                                                                                                                                                                              
    

    同意Test vectors

    "abcde"  -> 4031760169 (0xF04FC729)
    "abcdef" -> 1448095018 (0x56502D2A)
    

    Adler-32 对几百字节的短消息有一个弱点,因为这些消息的校验和对 32 个可用位的覆盖范围很差。检查这个:

    The Adler32 algorithm is not complex enough to compete with comparable checksums.

    【讨论】:

    • 从技术上讲,如果它慢 100%,它永远不会结束。
    • @Christian 我会将“100% 慢”解析为:它花费了原始时间 + 原始时间的 100%。即它花了2倍的时间。我是少数派吗?
    【解决方案5】:

    只是总结一个对我有帮助的答案(注意@erasmospunk 关于使用 base-64 编码的评论)。我的目标是拥有一个大部分唯一的短字符串...

    我不是专家,所以如果它有任何明显的错误,请更正它(在 Python 中再次像接受的答案一样):

    import base64
    import hashlib
    import uuid
    
    unique_id = uuid.uuid4()
    # unique_id = UUID('8da617a7-0bd6-4cce-ae49-5d31f2a5a35f')
    
    hash = hashlib.sha1(str(unique_id).encode("UTF-8"))
    # hash.hexdigest() = '882efb0f24a03938e5898aa6b69df2038a2c3f0e'
    
    result = base64.b64encode(hash.digest())
    # result = b'iC77DySgOTjliYqmtp3yA4osPw4='
    

    这里的result 不仅仅使用十六进制字符(如果您使用hash.hexdigest(),您会得到什么),因此它不太可能发生冲突(也就是说,截断应该比十六进制摘要更安全) .

    注意:使用 UUID4(随机)。其他类型见http://en.wikipedia.org/wiki/Universally_unique_identifier

    【讨论】:

      【解决方案6】:

      您可以使用任何常用的哈希算法(例如 SHA-1),它会为您提供比您需要的稍长的结果。只需将结果截断为所需的长度,这可能就足够了。

      例如,在 Python 中:

      >>> import hashlib
      >>> hash = hashlib.sha1("my message".encode("UTF-8")).hexdigest()
      >>> hash
      '104ab42f1193c336aa2cf08a2c946d5c6fd0fcdb'
      >>> hash[:10]
      '104ab42f11'
      

      【讨论】:

      • 任何合理的哈希函数都可以被截断。
      • 这不会大大增加碰撞风险吗?
      • @erasmospunk:使用 base64 编码对抗碰撞性没有任何作用,因为如果 hash(a)hash(b) 发生冲突,那么 base64(hash(a)) 也会与 base64(hash(b)) 发生冲突。
      • @GregHewgill 你是对的,但我们不是在谈论原始哈希算法冲突(是的,sha1 冲突,但这是另一个故事)。如果你有一个 10 个字符的散列,如果它用base64base16(或十六进制)编码,你会得到更高的熵。多高?使用base16,每个字符可以获得 4 位信息,使用base64,这个数字是 6 位/字符。总共 10 个字符的“十六进制”哈希将有 40 位熵,而 base64 有 60 位。所以它稍微更有抵抗力,如果我不是很清楚,对不起。
      • @erasmospunk:哦,我明白你的意思了,是的,如果你的结果有一个有限的固定大小,那么你可以使用 base64 编码而不是十六进制编码来打包更多有效位。
      【解决方案7】:

      只需在终端中运行(在 MacOS 或 Linux 上):

      crc32 <(echo "some string")
      

      8 个字符长。

      【讨论】:

        【解决方案8】:

        您需要对内容进行哈希处理以得出摘要。有许多可用的哈希值,但 10 个字符对于结果集来说非常小。很久以前,人们使用 CRC-32,它产生 33 位散列(基本上是 4 个字符加 1 位)。还有产生 65 位散列的 CRC-64。产生 128 位散列(16 个字节/字符)的 MD5 被认为是出于加密目的而损坏的,因为可以找到具有相同散列的两条消息。不用说,任何时候你从任意长度的消息中创建一个 16 字节的摘要,你最终都会得到重复。摘要越短,冲突的风险就越大。

        但是,您担心两个连续消息(无论是否为整数)的哈希值不相似,这对于所有哈希值都是正确的。即使是原始消息中的一点点变化也会产生截然不同的结果摘要。

        因此,使用 CRC-64 之类的东西(以及对结果进行 base-64 处理)应该可以让您进入您正在寻找的社区。​​p>

        【讨论】:

        • 对 SHA-1 哈希值进行 CRC 处理,然后对结果进行 base-64 处理是否会使生成的 ID 更耐冲突?
        • “但是,您担心两个连续消息的哈希值不相似 [...] 对于所有哈希值都应该是正确的。” ——这不一定是真的。例如,对于用于聚类或克隆检测的哈希函数,实际上恰恰相反:您希望相似的文档产生相似(甚至相同)的哈希值。 Soundex 是专门为相似输入产生相同值的散列算法的一个著名示例。
        • 我正在使用哈希来验证消息的签名。所以基本上,对于已知的消息和指定的签名,散列必须是正确的。不过,我不在乎是否会有一小部分误报。这是完全可以接受的。为了方便起见,我目前使用用 base62 压缩的截断 SHA-512 哈希(我很快就搞定了)。
        • @JörgWMittag SoundEx 上的好点子。我站得更正了。并非所有哈希都具有相同的特征。
        【解决方案9】:

        您可以使用现有的哈希算法来生成简短的内容,例如 MD5(128 位)或 SHA1(160)。然后,您可以通过将摘要的部分与其他部分进行异或来进一步缩短它。这会增加冲突的机会,但不会像简单地截断摘要那么糟糕。

        此外,您可以将原始数据的长度作为结果的一部分包含在内,以使其更加独特。例如,将 MD5 摘要的前半部分与后半部分进行异或运算将得到 64 位。为数据长度添加 32 位(如果您知道长度总是适合更少的位,则添加 32 位或更低)。这将产生一个 96 位(12 字节)的结果,然后您可以将其转换为 24 个字符的十六进制字符串。或者,您可以使用 base 64 编码使其更短。

        【讨论】:

        • FWIW,这被称为 XOR 折叠。
        【解决方案10】:

        如果您不需要强大的算法来防止故意修改,我发现了一个名为 adler32 的算法,它产生的结果非常短(约 8 个字符)。从这里的下拉列表中选择它来尝试一下:

        http://www.sha1-online.com/

        【讨论】:

        • 太老了,不太靠谱。
        • @Mascarpone “不太可靠” - 来源?它有局限性,如果你了解它们,它的年龄并不重要。
        • @Mascarpone “更少的弱点” - 再次,什么 弱点?为什么你认为这个算法对于 OP 的使用不是 100% 完美?
        • @Mascarpone OP 并没有说他们想要加密级哈希。 OTOH,Adler32 是校验和,而不是哈希,因此它可能不合适,具体取决于 OP 实际使用它做什么。
        • 对 Adler32 有一个警告,引用 Wikipedia: Adler-32 对于几百字节的短消息有一个弱点,因为这些消息的校验和对 32可用位。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-20
        • 2018-03-09
        • 2011-06-17
        • 1970-01-01
        • 2014-05-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多