减少 MD5 - 使用不同的基础

Reduce MD5 - Using a different base

我的客户生成的促销优惠券代码不过是 32 个字符的 MD5 哈希值。

我的工作是将 MD5 字符串从 32 个字符减少到少于 10 个字符,这样可以从减少的字符串中重新创建散列。

减少很重要,因为用户可以更轻松地重现减少的哈希值。

例如:719bedacf2e560b27f39d80accc67ffd => ZjKa1Gh(数学上不正确)

我遇到了这个:How to reduce hash value's length?

它建议:使用不同的基数

我不知道如何在 PHP 中执行此操作,我们可以将字符串解码为其 ASCII 并重新编码吗?

PHP 中是否有我可以在这种情况下使用的内置函数?

Update using https://packagist.org/packages/aza/math

$original = '719bedacf2e560b27f39d80accc67ffd';
$long1 = NumeralSystem::convert($original, 16, 10);
$short = NumeralSystem::convertTo($long1, 62);
$long2 = NumeralSystem::convertFrom($short, 62);
$recovered = NumeralSystem::convert($long2, 10, 16);

var_dump($long1);
var_dump($short);
var_dump($long2);
var_dump($recovered);

// output
string(39) "151012390170261082849236619706853916669"
string(22) "3SNOKWefotgnnCmWnYkTOf"
string(39) "151012390170261082849236619706853916669"
string(32) "719bedacf2e560b27f39d80accc67ffd"

似乎我从 32 个字符 MD5 可以达到的最低值是 22 个字符。我仍在寻找可以将其进一步减少到 10 个字符的方法。

Update: Using first half of MD5

$original = '719bedacf2e560b';
$coupon = NumeralSystem::convert($original, 16, 62);
$recovered = NumeralSystem::convert($coupon, 62, 16);

var_dump($coupon);
var_dump($recovered);

// output
string(10) "bnMR3RjZil"
string(15) "719bedacf2e560b"

如果用户提供 bnMR3RjZil,我可以用它来重新创建 719bedacf2e560b,然后进行 MySQL LIKE 搜索以获得完整的 MD5。如果它 returns 连续我可以继续促销 activity。

密码哈希实际上是一个位序列,但它可以解释为一个数字。因此,理论上您可以使用旧的 base_convert() 来表示基数很大。不幸的是,此函数仅适用于 36 进制,并且仅限于实际数字(即适合 PHP_INT_MAX 的短整数)——否则会丢失数据。

这就是第三方库可以提供帮助的地方。唯一的问题是它们往往很难找到,因为它们通常处理非常具体的用例(比特币、ID 混淆等)。

我发现例如aza/math,这可能有点矫枉过正,但应该可以完成工作。我还没有机会测试它,但它应该是这样的:

$original = '719bedacf2e560b27f39d80accc67ffd';
$short = NumeralSystem::convert($original, 16, 62);
$recovered = NumeralSystem::convert($short, 62, 16);

使用不同碱基的方法可以如下进行。请注意,下面的代码只是为了说明该方法,为了有效地实现它,需要直接使用二进制表示。

这个想法是将输入字符串解释为 128 位序列。现在,如果您指定您的新字母表(新基本系统的字符)是 A-Za-z0-9+-,您有 64 个字符,这意味着您需要 6 位来对它们中的每一个进行编码。因此,您可以先将输入字符串转换为二进制表示形式,将此表示形式拆分为 6 位的块,并在指定的字符集 A-Za-z0-9+-:

内表示每个块
<?php

$s = "719bedacf2e560b27f39d80accc67ffd";

function conv($s){
  $ret = base_convert($s, 16, 2);
  return str_repeat("0", 8 - strlen($ret)) . $ret;
}

$binary_repr = implode(array_map(conv, str_split($s, 2)), '');

$items = str_split($binary_repr, 6);

function item2char($str){
  $code = base_convert($str, 2, 10);
  $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-';
  return $alphabet[$code];
}

$result = implode(array_map(item2char, $items), '');
echo $result;

?>

如评论中所述,这基本上是背后的想法:

<?php
$s = "719bedacf2e560b27f39d80accc67ffd";

echo base64_encode(hex2bin($s));
//cZvtrPLlYLJ/OdgKzMZ//Q==

echo bin2hex(base64_decode("cZvtrPLlYLJ/OdgKzMZ//Q=="));
//719bedacf2e560b27f39d80accc67ffd

?>

My job is to reduce the MD5 string from 32 chars to less than 10 chars in a way that the hash can be recreated from the reduced string.

那是不可能的。 MD5 散列为 128 位;一个 ASCII 字符是 7 位。无法将 MD5 哈希值存储在少于 128÷7 = 18.2(四舍五入到 19)个 ASCII 字符中,甚至包括不可打印的控制字符。