PHP:仅限数字的哈希?

52
在php中,是否有一种方法可以从字符串中生成一个唯一哈希值,但该哈希值仅由数字组成?
例如:
return md5(234); // returns 098f6bcd4621d373cade4e832627b4f6

但我需要

return numhash(234); // returns 00978902923102372190 
(20 numbers only)

问题在于我希望哈希值较短。

编辑: 好的,让我解释一下背景。 我有一个网站,为每个注册用户分配了一个ID。此外,我需要为用户分配一个ID以供使用和交换(因此它不能太长)。到目前为止,ID编号一直是00001、00002、00003等等...

  1. 这会让某些人看起来更重要
  2. 这会泄露我不想透露的应用程序信息。

为了解决第1点和第2点,我需要“隐藏”数字,同时保持其唯一性。

编辑+解决方案:

基于https://dev59.com/l3A75IYBdhLWcg3wSm64#23679870的代码实现的数字哈希函数。

/**
 * Return a number only hash
 * https://dev59.com/l3A75IYBdhLWcg3wSm64#23679870
 * @param $str
 * @param null $len
 * @return number
 */
public function numHash($str, $len=null)
{
    $binhash = md5($str, true);
    $numhash = unpack('N2', $binhash);
    $hash = $numhash[1] . $numhash[2];
    if($len && is_int($len)) {
        $hash = substr($hash, 0, $len);
    }
    return $hash;
}

// Usage
numHash(234, 20); // always returns 6814430791721596451

1
整数混淆,例如使用 (new Id())->obfuscate($id) 应该正是您所需要的。如果生成的字符串不必太短,最大长度为39个字符,您也可以保留MD5并将其转换为十进制。 - caw
如果您不想返回负值,可以使用 abs(); 函数。 - Fanky
7个回答

71

在PHP中,MD5或SHA1哈希返回一个十六进制数,所以你只需要进行基数转换。PHP有一个可以为您执行此操作的函数:

$bignum = hexdec( md5("test") );
或者
$bignum = hexdec( sha1("test") );

hexdec的PHP手册

如果你想要一个有限大小的数字,你可以使用模除来将它放到你想要的范围内。

$smallnum = $bignum % [put your upper bound here]

编辑

正如Artefacto在评论中所指出的,使用这种方法将导致PHP中整数最大值之外的数字,模运算后的结果始终为0。但是,截取哈希值中包含前16个字符的子串则没有此问题。计算初始大数字的修订版本如下:

$bignum = hexdec( substr(sha1("test"), 0, 15) );

如果我将“test”变量限制为有限的数字集合,会有减小哈希大小的方法吗? - Timo Huovinen
2
@YuriKolovsky - 虽然自然而然地,使用较小的数字会增加哈希碰撞的风险。避免碰撞有多重要,这取决于您自己的决定。 - derekerdmann
5
需要补充的是,md5/sha1哈希值太长了,无法适应PHP整数。当您调用hexdec时,您已经丢失了字节。实际上,我担心因此取模会引起麻烦。 - Artefacto
1
@YuriKolovsky 当然它不一定是唯一的。唯一的问题是碰撞的可能性有多大。话虽如此,如果需要,我认为你应该使用 mt_rand,如果有冲突,就重复执行。 - Artefacto
如果您计划稍后在 $bignum % $smallnum 中使用模块,那么 $bignum = hexdec( substr(sha1("test"), -15) ); 是更好的解决方案。这样做是为了保持与 sha1 的一致性,因为我们保留字符串的末尾以进行取模运算,而不需要截断。 - edlerd
显示剩余9条评论

19

你可以尝试使用crc32()函数。请参阅文档:http://php.net/manual/zh/function.crc32.php

$checksum = crc32("The quick brown fox jumped over the lazy dog.");
printf("%u\n", $checksum); // prints 2191738434 

话虽如此,crc应该仅仅被用来验证数据的完整性。


16

有一些不错的答案,但对我来说这些方法看起来很傻。
他们首先迫使php创建一个十六进制数,然后再将其转换回来(hexdec)到BigInteger,然后缩小到几个字母的数字... 这是很费力的!

相反为什么不直接

以二进制形式读取哈希值:

$binhash = md5('[input value]', true);
使用:

然后使用

$numhash = unpack('N2', $binhash); //- or 'V2' for little endian

将其转换为两个 INT$numhash 是包含两个元素的数组)。现在,您可以使用 AND 操作来简单地减少数字中的位数。例如:

$result = $numhash[1] & 0x000FFFFF; //- to get numbers between 0 and 1048575

但要注意避免碰撞!减少数量意味着增加两个不同的[输入值]产生相同输出的概率。

我认为更好的方法是使用具有双向函数的“ID加密”。这样就不会发生碰撞!对于最简单的类型,只需使用仿射密码

以0到25的最大输入值范围为例:

function numcrypt($a)
{
   return ($a * 15) % 26;
}

function unnumcrypt($a)
{
   return ($a * 7) % 26;
}

输出:

numcrypt(1) : 15
numcrypt(2) : 4
numcrypt(3) : 19

unnumcrypt(15) : 1
unnumcrypt(4)  : 2
unnumcrypt(19) : 3
例如。
$id = unnumcrypt($_GET('userid'));

... do something with the ID ...

echo '<a href="do.php?userid='. numcrypt($id) . '"> go </a>';

当然,这种方法并不安全,但是如果没有人知道你使用的加密方法,那么就没有安全问题,这种方式更快且可以避免冲突。


我不明白如何使用你的第二个解决方案来获取数字哈希,你能再详细解释一下吗? - Timo Huovinen
这不是哈希,但您可以使用 Blowfish 等方法将 ID 转换为唯一的“随机”数字。然后用户就无法计算“ID+1”。最终,您可以将其用作代理方法:内部应用程序使用“ID: 1,2,3,...”,但您向用户提供加密数字。许多大型网站都是这样做的:例如,Google 的 cookie 就是加密的 ID。 - Thomas
你如何反转(或打包)unpack('N2', ...)数组? - Xeoncross

8
问题在于哈希冲突,为避免此问题,请尝试以下方法:
return  hexdec(crc32("Hello World"));

crc32()

生成str的32位循环冗余校验多项式。通常用于验证正在传输的数据的完整性。

这将给我们一个32位的整数,在32位安装中为负数,在64位中为正数。这个整数可以像ID一样存储在数据库中。它不会发生冲突问题,因为它适合32位变量,一旦您使用hexdec()函数将其转换为十进制。


您能否解释一下在这种情况下如何通过哈希字符串避免碰撞?据我所知,所有哈希算法都存在碰撞。 - Leo Galleguillos

1
首先,MD5基本上已经被破解了,所以你不应该将其用于任何关键哈希之外的东西。 PHP5有hash()函数,请参见http://www.php.net/manual/en/function.hash.php
将最后一个参数设置为true将给您一串二进制数据。或者,您可以将结果十六进制哈希拆分成2个字符的片段,并逐个将它们转换为整数,但我认为这会慢得多。

1
速度不是问题,我唯一的问题是num哈希值不要太长,但必须保证唯一性。 - Timo Huovinen

0

尝试使用hashid
它可以将数字哈希成您可以定义的格式。这些格式包括多少个字符以及包含哪些字符。
例如:
$hashids->encode(1);
根据您的格式,将返回“28630”。


0

只需使用我下面的手动哈希方法:

将数字(例如6位数)除以质数3、5、7。

并获取小数点后的前6个值作为要使用的ID。在实际创建ID之前,进行唯一性检查,如果存在冲突,则将最后一位数字增加1,直到没有冲突。
例如:123456给出771428 123457给出780952 123458给出790476。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接