产生短哈希的哈希函数?


97

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

但是,如果不可能使用任意长度的字符串,我可以将消息限制为整数值。但是,在这种情况下,对于两个连续的整数,散列一定不能相似。


这就是所谓的哈希。它不会是唯一的。
SLaks 2010年

1
这也是一个哈希值截断问题,因此另请参见stackoverflow.com/q/4784335
Peter Krauss,

2
仅供参考,请参阅Wikipedia 中的哈希函数列表
罗勒·布尔克

Answers:


76

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

例如,在Python中:

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

2
任何合理的哈希函数都可以被截断。
总统詹姆斯·波尔克(James K. Polk)2010年

88
这样会不会更大程度地增加碰撞的风险?
加布里埃尔·桑马汀

143
@erasmospunk:使用base64编码不会产生任何抗碰撞性,因为如果hash(a)与发生碰撞,hash(b)那么base64(hash(a))也会与发生碰撞base64(hash(b))
Greg Hewgill 2013年

56
@GregHewgill你是对的,但是我们不是在谈论原始哈希算法的冲突(是的,sha1冲突,但这是另一个故事)。如果您使用10个字符的哈希值,则使用base64vs base16(或十六进制)编码时,熵值会更高。多高?随着base16你的每个字符的信息4位,base64这个数字是6比特/字符。总共10个字符的“十六进制”哈希将具有40位的熵,而base64为60位。因此,它的抵抗力稍强,如果我不太清楚的话,抱歉。
John L. Jegutanis

19
@erasmospunk:哦,我明白你的意思了,是的,如果结果的固定大小有限,那么可以使用base64编码和hex编码来打包更多有效位。
Greg Hewgill 2013年

46

如果您不需要强大的针对故意修改的算法,那么我发现一种叫做adler32的算法可以产生较短的(约8个字符)结果。从这里的下拉列表中选择它来尝试:

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


2
它很旧,不是很可靠。
Mascarpone

1
@Mascarpone“不是很可靠”-来源?它有局限性,如果您知道它们的年龄,则无所谓。
英国电信

8
@Mascarpone“弱点更少”-再说一遍,什么弱点?为什么您认为此算法对于OP的使用而言不是100%完美?
英国电信

3
@Mascarpone OP没有说他们想要加密级哈希。OTOH,Adler32是校验和,而不是哈希,因此可能不适合,具体取决于OP实际使用的方式。
下午18年

2
Adler32有一个警告,引用WikipediaAdler-32对于短消息只有几百个字节有一个弱点,因为这些消息的校验和覆盖了32个可用位。
罗勒·布尔克

13

您需要对内容进行哈希处理以得出摘要。可用的哈希很多,但结果集的10个字符很小。追溯到过去,人们使用了CRC-32,它会产生33位的哈希值(基本上是4个字符加一位)。还有CRC-64产生65位哈希值。出于加密目的,产生128位哈希(16个字节/字符)的MD5被认为是损坏的,因为可以找到两个具有相同哈希的消息。不用说,任何时候从任意长度的消息中创建一个16字节的摘要时,都会导致重复。摘要越短,发生碰撞的风险就越大。

但是,您担心两个连续消息(无论是否为整数)的哈希值都不相似,所有散列都应为真。即使原始消息中的一点点更改也将产生截然不同的结果摘要。

因此,使用CRC-64之类的方法(对结果进行base-64处理)应该可以使您进入所需的社区。


1
CRC对SHA-1哈希值进行“ CRC”运算,然后对结果进行base-64运算,是否会使所得ID更耐冲突?

5
“但是,对于所有连续的散列,对于连续两个消息的散列都不相似的担心应该是正确的。” -不一定是真的。例如,对于用于聚类或克隆检测的哈希函数,实际上恰恰相反:您希望相似的文档产生相似(甚至相同)的哈希值。Soundex 是专门设计用于为相似输入产生相同值的哈希算法的一个著名示例。
约尔格W¯¯米塔格

我正在使用散列来验证消息的签名。因此,基本上,对于已知消息和指定的签名,哈希必须正确。不过,我不在乎是否会有一小部分误报。这是完全可以接受的。为了方便起见,我目前使用以base62压缩的截断的SHA-512哈希值(我很快就提出来了)。

@JörgWMittagSoundEx上的出色点。我站得住了。并非所有哈希都具有相同的特征。
约翰,

12

只是总结一个对我有用的答案(注意@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


7

您可以使用现有的散列算法来生成较短的内容,例如MD5(128位)或SHA1(160)。然后,您可以通过将摘要的各个部分与其他部分进行异或来进一步缩短该时间。这将增加碰撞的机会,但不像简单地截断摘要那样糟糕。

另外,您可以将原始数据的长度作为结果的一部分,以使其更具唯一性。例如,将MD5摘要的前半部分与后半部分异或将产生64位。添加32位作为数据的长度(如果您知道该长度总是适合较少的位,则增加32位)。这将导致96位(12字节)的结果,然后您可以将其转换为24个字符的十六进制字符串。或者,您可以使用base 64编码使其更短。


2
FWIW,这称为XOR折叠。
下午18年

7

如果需要"sub-10-character hash" ,可以使用Fletcher-32算法,该算法产生8个字符的哈希(32位), CRC-32Adler-32

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

Fletcher-32比Adler-32更加可靠。它的计算成本比Adler校验和低:Fletcher与Adler比较

下面给出了具有一些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                                                                                                                                                                                                                              

同意测试向量

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

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

Adler32算法不够复杂,无法与可比较的校验和竞争


6

只需在终端(在MacOS或Linux上)上运行此命令即可:

crc32 <(echo "some string")

8个字符长。


4

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

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

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


1

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

~ echo test | xxhsum                                                           
2d7f1808da1fa63c  stdin

该链接已断开。最好提供一个更完整的答案。
eri0o

0

最近,我需要一些简单的字符串缩减功能。基本上,代码看起来像这样(前面的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更改为另一个质数)。此代码段有趣的功能之一是,当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 ::模板。
CubicleSoft

这听起来仍然很愚蠢。为什么你会永远使用DestSize大于4(32位)时,哈希本身是如此糟糕?如果希望输出大于int的输出提供的耐碰撞性,则可以使用SHA。
纳文

看,它不是真正的传统哈希。它具有有用的属性,用户可以在某些操作系统(例如Mac OSX)上的缓冲区空间极其有限的地方声明字符串大小,并且结果必须适合真实文件名的有限域,并且他们不想截断名称,因为那样会导致冲突(但是较短的字符串将被保留)。密码哈希并不总是正确的答案,而std :: hash也不总是正确的答案。
CubicleSoft
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.