具有解码可能性(缩短url)的最短可能编码字符串仅使用PHP

Art*_*iak 15 php encoding decoding

我正在寻找一种方法,将字符串编码为尽可能的长度并让它可解码(纯PHP,没有SQL).我有工作脚本,但我对编码字符串的长度不满意.

场景:

链接到图像(取决于我想向用户显示的文件分辨率):

  • www.mysite.com/share/index.php?img=/dir/dir/hi-res-img.jpg&w=700&h=500

编码链接(因此用户无法猜测如何获取更大的图像):

  • www.mysite.com/share/encodedQUERYstring

所以,基本上我只想编码网址的搜索查询部分:

  • IMG = /目录/目录/高分辨率-img.jpg&W = 700,H = 500

我现在使用的方法将上面的查询字符串编码为:

  • y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA

我使用的方法是:

 $raw_query_string = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';

 $encoded_query_string = base64_encode(gzdeflate($raw_query_string));
 $decoded_query_string = gzinflate(base64_decode($encoded_query_string)); 
Run Code Online (Sandbox Code Playgroud)

如何缩短编码结果并仍然可以使用PHP 对其进行解码?

cal*_*nai 13

我怀疑如果你不想让用户解码你需要更多考虑你的散列方法.问题base64是base64字符串看起来像base64字符串.很有可能那些精通足以查看您的页面来源的人也可能会认出它.

第一部分:

将字符串编码为可能的最短长度的方法

如果您对URL词汇/字符的灵活性,这将是一个很好的起点.由于gzip使用后向引用可以获得很多收益,因此字符串太短没有什么意义.

考虑一下你的例子 - 你只在压缩中保存了2个字节,这些字节在base64填充中再次丢失:

非gzip压缩: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"

gzip压缩: string(52) "y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA=="

如果你减少你的词汇大小,这自然会让你更好地压缩.假设我们删除了一些冗余信息

看看功能:

function compress($input, $ascii_offset = 38){
    $input = strtoupper($input);
    $output = '';
    //We can try for a 4:3 (8:6) compression (roughly), 24 bits for 4 chars
    foreach(str_split($input, 4) as $chunk) {
        $chunk = str_pad($chunk, 4, '=');

        $int_24 = 0;
        for($i=0; $i<4; $i++){
            //Shift the output to the left 6 bits
            $int_24 <<= 6;

            //Add the next 6 bits
            //Discard the leading ascii chars, i.e make
            $int_24 |= (ord($chunk[$i]) - $ascii_offset) & 0b111111;
        }

        //Here we take the 4 sets of 6 apart in 3 sets of 8
        for($i=0; $i<3; $i++) {
            $output = pack('C', $int_24) . $output;
            $int_24 >>= 8;
        }
    }

    return $output;
}
Run Code Online (Sandbox Code Playgroud)

function decompress($input, $ascii_offset = 38) {

    $output = '';
    foreach(str_split($input, 3) as $chunk) {

        //Reassemble the 24 bit ints from 3 bytes
        $int_24 = 0;
        foreach(unpack('C*', $chunk) as $char) {
            $int_24 <<= 8;
            $int_24 |= $char & 0b11111111;
        }

        //Expand the 24 bits to 4 sets of 6, and take their character values
        for($i = 0; $i < 4; $i++) {
            $output = chr($ascii_offset + ($int_24 & 0b111111)) . $output;
            $int_24 >>= 6;
        }
    }

    //Make lowercase again and trim off the padding.
    return strtolower(rtrim($output, '='));
}
Run Code Online (Sandbox Code Playgroud)

在那里发生的事情基本上是删除冗余信息,然后将4个字节压缩为3.这是通过有效地拥有ascii表的6位子集来实现的.移动此窗口,以便偏移量从有用的字符开始,并包括您当前使用的所有字符.

使用我使用的偏移量,您可以使用ASCII 38到102之间的任何内容.这将为您提供30字节的结果字符串,即9字节(24%)压缩!不幸的是,你需要使它具有URL安全性(可能使用base64),这可以使它恢复到40个字节.

我想在这一点上,你可以非常安全地假设你达到了阻止99.9%的人所需的"通过默默无闻的安全"水平.让我们继续,到你问题的第二部分

所以用户无法猜测如何获得更大的图像

可以说上面已经解决了这个问题,但你需要做的是通过服务器上的秘密传递这个秘密,最好用php openssl.以下代码显示了上述函数的完整使用流程和加密:

$method = 'AES-256-CBC';
$secret = base64_decode('tvFD4Vl6Pu2CmqdKYOhIkEQ8ZO4XA4D8CLowBpLSCvA=');
$iv = base64_decode('AVoIW0Zs2YY2zFm5fazLfg==');

$input = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
var_dump($input);

$compressed = compress($input);
var_dump($compressed);

$encrypted = openssl_encrypt($compressed, $method, $secret, false, $iv);
var_dump($encrypted);

$decrypted = openssl_decrypt($encrypted, $method, $secret, false, $iv);
var_dump($decrypted);

$decompressed = decompress($compressed);
var_dump($decompressed);
Run Code Online (Sandbox Code Playgroud)

此脚本的输出如下:

string(39) "img=/dir/dir/hi-res-img.jpg&w=700&h=500"
string(30) "<??(??tJ??@?xH??G&(?%??%??xW"
string(44) "xozYGselci9i70cTdmpvWkrYvGN9AmA7djc5eOcFoAM="
string(30) "<??(??tJ??@?xH??G&(?%??%??xW"
string(39) "img=/dir/dir/hi-res-img.jpg&w=700&h=500"
Run Code Online (Sandbox Code Playgroud)

您将看到整个周期:压缩>加密> base64编码/解码>解密>解压缩.这个输出尽可能接近你可能得到的,接近你可以得到的最短长度.

除了一切之外,我觉得有必要以这样一个事实作为结论:这只是理论上的事情,这是一个很好的挑战.肯定有更好的方法来实现你想要的结果 - 我将是第一个承认我的解决方案有点荒谬的人!

  • 感谢您对这个问题的一些阐述。它让我更好地理解整件事 (2认同)

小智 5

理论

\n

理论上我们需要一个短的输入字符集和一个大的输出字符集。\n我将通过下面的例子来演示它。我们有数字 2468 作为整数,具有 10 个字符 (0-9) 作为字符集。我们可以将其转换为以 2 为基数的相同数字(二进制​​数字系统)。那么我们有一个较短的字符集(0和1),结果较长:\n100110100100

\n

但如果我们转换为字符集为 16(0-9 和 AF)的十六进制数(基数 16)。然后我们得到一个更短的结果:\n9A4

\n

实践

\n

因此,在您的情况下,我们有以下输入字符集:

\n
$inputCharacterSet = "0123456789abcdefghijklmnopqrstuvwxyz=/-.&";\n
Run Code Online (Sandbox Code Playgroud)\n

总共 41 个字符:数字、小写字母和特殊字符 = / - 。&

\n

输出的字符集有点棘手。我们只想使用 URL 保存字符。我从这里获取了它们:GET 参数中允许的字符

\n

所以我们的输出字符集是(73个字符):

\n
$outputCharacterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-_.!*\'(),$";\n
Run Code Online (Sandbox Code Playgroud)\n

数字、小写大写以及一些特殊字符。

\n

我们的输出集中的字符多于输入的字符。理论上我们可以缩短输入字符串。查看

\n

编码

\n

现在我们需要一个从 41 进制到 73 进制的编码函数。对于这种情况,我不知道 PHP 函数。幸运的是,我们可以从这里获取函数“convBase”:将任意大的数字从任何基数转换为任何基数

\n
<?php\nfunction convBase($numberInput, $fromBaseInput, $toBaseInput)\n{\n    if ($fromBaseInput == $toBaseInput) return $numberInput;\n    $fromBase = str_split($fromBaseInput, 1);\n    $toBase = str_split($toBaseInput, 1);\n    $number = str_split($numberInput, 1);\n    $fromLen = strlen($fromBaseInput);\n    $toLen = strlen($toBaseInput);\n    $numberLen = strlen($numberInput);\n    $retval = \'\';\n    if ($toBaseInput == \'0123456789\')\n    {\n        $retval = 0;\n        for ($i = 1;$i <= $numberLen; $i++)\n            $retval = bcadd($retval, bcmul(array_search($number[$i-1], $fromBase), bcpow($fromLen, $numberLen-$i)));\n        return $retval;\n    }\n    if ($fromBaseInput != \'0123456789\')\n        $base10 = convBase($numberInput, $fromBaseInput, \'0123456789\');\n    else\n        $base10 = $numberInput;\n    if ($base10<strlen($toBaseInput))\n        return $toBase[$base10];\n    while($base10 != \'0\')\n    {\n        $retval = $toBase[bcmod($base10,$toLen)] . $retval;\n        $base10 = bcdiv($base10, $toLen, 0);\n    }\n    return $retval;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们可以缩短 URL。最终代码为:

\n
$input = \'img=/dir/dir/hi-res-img.jpg&w=700&h=500\';\n$inputCharacterSet = "0123456789abcdefghijklmnopqrstuvwxyz=/-.&";\n$outputCharacterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-_.!*\'(),$";\n$encoded = convBase($input, $inputCharacterSet, $outputCharacterSet);\nvar_dump($encoded); // string(34) "BhnuhSTc7LGZv.h((Y.tG_IXIh8AR.$!t*"\n$decoded = convBase($encoded, $outputCharacterSet, $inputCharacterSet);\nvar_dump($decoded); // string(39) "img=/dir/dir/hi-res-img.jpg&w=700&h=500"\n
Run Code Online (Sandbox Code Playgroud)\n

编码后的字符串只有 34 个字符。

\n

优化

\n

您可以通过以下方式优化字符数

\n
    \n
  • 减少输入字符串的长度。您真的需要 URL 参数语法的开销吗?也许您可以按如下方式格式化字符串:
  • \n
\n

$input = \'/dir/dir/hi-res-img.jpg,700,500\';

\n

这减少了输入本身输入字符集。那么您减少的输入字符集是:

\n

$inputCharacterSet = "0123456789abcdefghijklmnopqrstuvwxyz/-.,";

\n

最终输出:

\n

string(27) "E$AO.Y_JVIWMQ9BB_Xb3!Th*-Ut"

\n

string(31) "/dir/dir/hi-res-img.jpg,700,500"

\n
    \n
  • 减少输入字符集;-)。也许您可以排除更多字符?\n您可以先将数字编码为字符。那么你的输入字符集就可以减少10个了!

    \n
  • \n
  • 增加输出字符集。所以我给定的设置在两分钟内就被谷歌搜索到了。也许你可以使用更多的URL保存字符。

    \n
  • \n
\n

安全

\n

注意:代码中没有加密逻辑。因此,如果有人猜出字符集,他/她可以轻松解码字符串。但您可以打乱字符集(一次)。那么对于攻击者来说就有点困难了,但并不真正安全。无论如何,也许它 \xe2\x80\x99 足以满足您的用例。

\n