【问题标题】:Shortest possible encoded string with a decode possibility (shorten URL) using only PHP仅使用 PHP 具有解码可能性(缩短 url)的最短可能编码字符串
【发布时间】:2015-03-11 22:22:03
【问题描述】:

我正在寻找一种将字符串编码为最短可能长度并使其可解码(纯PHP,无SQL)的方法。我有工作脚本,但我对编码字符串的长度不满意。

场景:

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

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

编码的链接(所以用户无法猜测如何获得更大的图像):

  • www.mysite.com/share/encodedQUERYstring

所以,基本上我只想对 url 的搜索查询部分进行编码:

  • img=/dir/dir/hi-res-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)); 

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

【问题讨论】:

  • 我会咬人的:为什么你要这么做?
  • 看起来像自制的“默默无闻”的东西。不要那样做。没有意义,也是死路一条。
  • 为什么你试图阻止用户获得高分辨率图像?
  • 如果您希望您的用户购买高分辨率图像,那么请不要在网页中显示它们......显示较低分辨率的图像和/或为您显示的图像添加水印
  • 您在网站上显示图像的实例,当他们显示该页面时,它会下载到用户的 PC....如果您正在显示高分辨率图像,那么他们现在有了图像在他们的 PC 上......不管你混淆了多少链接

标签: php encoding decoding


【解决方案1】:

理论

理论上我们需要一个短的输入字符集和一个大的输出字符集。 我将通过以下示例进行演示。我们将数字 2468 作为整数,以 10 个字符 (0-9) 作为字符集。我们可以将其转换为以 2 为底的相同数字(二进制​​数系统)。然后我们有一个更短的字符集(0 和 1),结果更长: 100110100100

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

练习

所以在你的情况下,我们有以下输入字符集:

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

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

输出的字符集有点棘手。我们只想使用 URL 保存字符。我从这里抓取了它们:Characters allowed in GET parameter

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

$outputCharacterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-_.!*'(),$";

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

我们的集合中用于输出的字符比用于输入的字符多。理论说我们可以缩短我们的输入字符串。检查!

编码

现在我们需要一个从 41 到 73 的编码函数。对于这种情况,我不知道 PHP 函数。幸运的是,我们可以从这里获取函数“convBase”:http://php.net/manual/de/function.base-convert.php#106546(如果有人知道更智能的函数,请告诉我)

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

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

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

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

优化

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

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

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

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

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

    最终输出:

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

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

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

  • 增加您的输出字符集。所以我给定的集合在 2 分钟内被谷歌搜索。也许您可以使用更多的 url 保存字符。不知道...也许有人有一个清单

安全

注意:代码中没有加密逻辑。因此,如果有人猜到了字符集,他就可以轻松解码字符串。但是您可以随机播放字符集(一次)。然后对攻击者来说有点困难,但不是很安全。无论如何,它可能足以满足您的用例。

【讨论】:

    【解决方案2】:

    关于编码如何无助于安全性的说法很多,所以我只专注于缩短和美观。

    与其将其视为一个字符串,不如将其视为 3 个单独的组件。然后,如果您限制每个组件的代码空间,您可以将东西打包在一起更小。

    例如

    • path - 仅由 26 个字符 (a-z) 和 / - 组成。 (可变长度)
    • 宽度 - 整数 (0 - 65k)(固定长度,16 位)
    • 高度 - 整数 (0 - 65k)(固定长度,16 位)

    我将路径限制为最多只能包含 31 个字符,因此我们可以使用 5 位分组。

    首先打包您的固定长度尺寸,并将每个路径字符附加为 5 位。可能还需要添加一个特殊的空字符来填充结束字节。显然你需要使用相同的字典字符串进行编码和解码。

    参见下面的代码。

    这表明,通过限制您编码的内容和可以编码的数量,您可以获得更短的字符串。您可以通过仅使用 12 位维度整数(最大 2048)来使其更短,或者甚至删除已知的部分路径,例如基本路径或文件扩展名(参见最后一个示例)。

    <?php
    
    function encodeImageAndDimensions($path, $width, $height) {
        $dictionary = str_split("abcdefghijklmnopqrstuvwxyz/-."); //Max 31 chars please
    
        if ($width >= pow(2,16)) {
            throw new Exception("Width value is too high to encode with 16 bits");
        }
        if ($height >= pow(2,16)) {
            throw new Exception("Height value is too high to encode with 16 bits");
        }
    
        //Pack width, then height first
        $packed = pack("nn", $width, $height);
    
        $path_bits = "";
        foreach (str_split($path) as $ch) {
            $index = array_search($ch, $dictionary, true);
            if ($index === false) {
                throw new Exception("Cannot encode character outside of the allowed dictionary");
            }
    
            $index++; //Add 1 due to index 0 meaning NULL rather than a.
    
            //Work with a bit string here rather than using complicated binary bit shift operators.
            $path_bits .=  str_pad(base_convert($index, 10, 2), 5, "0", STR_PAD_LEFT);
        }
    
        //Remaining space left?
        $modulo = (8 - (strlen($path_bits) % 8)) %8;
    
        if ($modulo >=5) {
            //There is space for a null character to fill up to the next byte
            $path_bits .= "00000";
            $modulo -= 5;
        }
    
        //Pad with zeros
        $path_bits .= str_repeat("0", $modulo);
    
        //Split in to nibbles and pack as a hex string
        $path_bits = str_split($path_bits, 4);
        $hex_string = implode("", array_map(function($bit_string) {
            return base_convert($bit_string, 2, 16);
        }, $path_bits));
        $packed .= pack('H*', $hex_string);
    
        return base64_url_encode($packed);
    }
    
    function decodeImageAndDimensions($str) {
        $dictionary = str_split("abcdefghijklmnopqrstuvwxyz/-.");
    
        $data = base64_url_decode($str);
    
        $decoded = unpack("nwidth/nheight/H*path", $data);
    
        $path_bit_stream = implode("", array_map(function($nibble) {
            return str_pad(base_convert($nibble, 16, 2), 4, "0", STR_PAD_LEFT);
        }, str_split($decoded['path'])));
    
        $five_pieces = str_split($path_bit_stream, 5);
    
        $real_path_indexes = array_map(function($code) {
            return base_convert($code, 2, 10) - 1;
        }, $five_pieces);
    
        $real_path = "";
        foreach ($real_path_indexes as $index) {
            if ($index == -1) {
                break;
            }
            $real_path .= $dictionary[$index];
        }
    
        $decoded['path'] = $real_path;
    
        return $decoded;
    }
    
    //These do a bit of magic to get rid of the double equals sign and obfuscate a bit.  It could save an extra byte.
    function base64_url_encode($input) {
        $trans = array('+' => '-', '/' => ':', '*' => '$', '=' => 'B', 'B' => '!');
        return strtr(str_replace('==', '*', base64_encode($input)), $trans);
    }
    function base64_url_decode($input) {
        $trans = array('-' => '+', ':' => '/', '$' => '*', 'B' => '=', '!' => 'B');
        return base64_decode(str_replace('*', '==',strtr($input,$trans)));
    }
    
    //Example usage
    
    $encoded = encodeImageAndDimensions("/dir/dir/hi-res-img.jpg", 700, 500);
    var_dump($encoded); // string(27) "Arw!9NkTLZEy2hPJFnxLT9VA4A$"
    $decoded = decodeImageAndDimensions($encoded);
    var_dump($decoded); // array(3) { ["width"]=> int(700) ["height"]=> int(500) ["path"]=> string(23) "/dir/dir/hi-res-img.jpg" } 
    
    $encoded = encodeImageAndDimensions("/another/example/image.png", 4500, 2500);
    var_dump($encoded); // string(28) "EZQJxNhc-iCy2XAWwYXaWhOXsHHA"
    $decoded = decodeImageAndDimensions($encoded);
    var_dump($decoded); // array(3) { ["width"]=> int(4500) ["height"]=> int(2500) ["path"]=> string(26) "/another/example/image.png" }
    
    $encoded = encodeImageAndDimensions("/short/eg.png", 300, 200);
    var_dump($encoded); // string(19) "ASwAyNzQ-VNlP2DjgA$"
    $decoded = decodeImageAndDimensions($encoded);
    var_dump($decoded); // array(3) { ["width"]=> int(300) ["height"]=> int(200) ["path"]=> string(13) "/short/eg.png" }
    
    $encoded = encodeImageAndDimensions("/very/very/very/very/very-hyper/long/example.png", 300, 200);
    var_dump($encoded); // string(47) "ASwAyN2LLO7FlndiyzuxZZ3Yss8Rm!ZbY9x9lwFsGF7!xw$"
    $decoded = decodeImageAndDimensions($encoded);
    var_dump($decoded); // array(3) { ["width"]=> int(300) ["height"]=> int(200) ["path"]=> string(48) "/very/very/very/very/very-hyper/long/example.png" } 
    
    $encoded = encodeImageAndDimensions("only-file-name", 300, 200);
    var_dump($encoded); //string(19) "ASwAyHuZnhksLxwWlA$"
    $decoded = decodeImageAndDimensions($encoded);
    var_dump($decoded); // array(3) { ["width"]=> int(300) ["height"]=> int(200) ["path"]=> string(14) "only-file-name" }
    

    【讨论】:

      【解决方案3】:

      你说你想要那个尺寸,所以如果有一天你决定预览图像太小,你想增加尺寸 - 这里的解决方案是将图像尺寸硬编码到 php 脚本中并消除它从网址。如果您以后想更改大小,请更改 php 脚本(或包含在脚本中的 config.php 中)中的硬编码值。

      您还说过您已经在使用文件将图像数据存储为 JSON 对象,例如:nametitledescription。利用这一点,您不需要数据库,可以使用 JSON 文件名作为查找图像数据的键。

      当用户访问这样的网址时:

      www.mysite.com/share/index.php?ax9v
      

      您从已存储 json 文件的位置加载 ax9v.json,并在该 json 文件中存储图像的真实路径。然后加载图像,根据脚本中硬编码的大小调整大小并将其发送给用户。

      https://blog.codinghorror.com/url-shortening-hashes-in-practice/ 中的结论得出,要获得 URL 的最小搜索字符串部分,您需要在上传新文件时迭代有效字符组合(例如,第一个是“AAA”,然后是“AAB”, “AAC”等),而不是使用散列算法。然后,您的解决方案将在您上传的前 238,328 张照片的字符串中只有 3 个字符。

      我已经开始在 phpfiddle 上设计一个 php 解决方案的原型,但是代码消失了(不要使用 phpfiddle)。

      【讨论】:

        【解决方案4】:

        我认为生成的 url 不能比您自己的示例更短。 但我建议采取一些步骤来更好地混淆图像。

        首先,我会从你正在压缩的基本 url 和 base64 编码中删除所有你可以删除的内容,而不是

        img=/dir/dir/hi-res-img.jpg&w=700&h=500

        我会用

        s=hi-res-img.jpg,700,500,062c02153d653119

        最后 16 个字符是否是用于验证打开的 url 是否与您在代码中提供的相同的哈希 - 并且用户不会试图将高分辨率图像从系统中欺骗出来。

        为图片提供服务的 index.php 会像这样开始:

        function myHash($sRaw) { // returns 16 chars dual hash
            return hash('adler32', $sRaw) . strrev(hash('crc32', $sRaw));
        } // These 2 hash algos are suggestions, there are more for you to chose.
        
        // s=hi-res-img.jpg,700,500,062c02153d653119
        $aParams = explode(',', $_GET['s']);
        if (count($aParams) != 4) {
            die('Invalid call.');
        }
        
        list($sFileName, $iWidth, $iHeight, $sHash) = $aParams;
        
        $sRaw = session_id() . $sFileName . $iWidth . $iHeight;
        if ($sHash != myHash($sRaw)) {
            die('Invalid hash.');
        }
        

        在此之后,您可以发送图像,因为打开它的用户可以访问有效链接。

        注意使用 session_id 作为原始字符串的一部分,这使得哈希是可选的,但会使用户无法共享有效的 url - 因为它会是会话绑定。 如果您希望 URL 可共享,则只需从该调用中删除 session_id。

        我会以与您已经做的相同的方式包装生成的 url,zip + base64。结果会比您的版本更大,但更难通过混淆查看,从而保护您的图像免遭未经授权的下载。

        如果你只想让它更短,我看不到不重命名文件(或其文件夹)或不使用数据库的方法。

        提出的文件数据库解决方案肯定会产生并发问题 - 除非您始终没有或很少有人同时使用该系统。

        【讨论】:

          【解决方案5】:

          从 cmets 部分的讨论来看,您真正想要的是保护原始高分辨率图像。

          考虑到这一点,我建议首先使用您的 Web 服务器配置(例如 Apache mod_authz_core 或 Nginx ngx_http_access_module)来拒绝从 Web 访问存储原始图像的目录。

          请注意,我们的服务器只会拒绝从网络访问您的图像,但您仍然可以直接从您的 php 脚本访问它们。由于您已经使用一些“调整大小”脚本来显示图像,我建议在那里设置一些硬限制,并拒绝将图像调整为更大的大小(例如 $width = min(1000, $_GET['w']) 之类的东西)。

          我知道这不能回答您最初的问题,但我认为这将是保护您的图像的正确解决方案。如果您仍然想混淆原始名称和调整参数大小,您可以按照您认为合适的方式执行此操作,而不必担心有人可能会找出其背后的原因。

          【讨论】:

            【解决方案6】:

            关于“安全”的简短描述

            如果某处没有存储“秘密密码”,您将无法保护您的链接:只要 URI 包含访问您的资源的所有信息,那么它将是可解码的并且您的“自定义安全性”(顺便说一句,它们是相反的词)很容易被打破。

            你仍然可以在你的 PHP 代码中加盐(比如 $mysalt="....long random string..."),因为我怀疑你想要一个永恒的安全性(这种方法很弱,因为你不能更新 $mysalt 值,但在你的情况下,几年的安全性听起来就足够了,因为无论如何,用户都可以购买一张图片并在其他地方分享,从而破坏您的任何安全机制。

            如果您想拥有一个安全的机制,请使用一个众所周知的(作为框架将携带的),以及身份验证和用户权限管理机制(这样您就可以知道谁在寻找您的图像,以及他们是否被允许到)。

            安全是有代价的,如果您不想负担它的计算和存储需求,那就别管它了。


            通过签署 URL 来确保安全

            如果您想避免用户轻易绕过并获得完整的分辨率图片,那么您可以只对 URI 进行签名(但实际上,为了安全起见,请使用已经存在的东西,而不是下面的快速草稿示例):

            $salt = '....long random stirng...';
            $params = array('img' => '...', 'h' => '...', 'w' => '...');
            $p = http_build_query($params);
            $check = password_hash($p, PASSWORD_BCRYPT, array('salt' => $salt, 'cost' => 1000);
            $uri = http_build_query(array_merge($params, 'sig' => $check));
            

            解码:

            $sig = $_GET['sig'];
            $params = $_GET;
            unset($params['sig']);
            
            // Same as previous
            $salt = '....long random stirng...';
            $p = http_build_query($params);
            $check = password_hash($p, PASSWORD_BCRYPT, array('salt' => $salt, 'cost' => 1000);
            if ($sig !== $check) throw new DomainException('Invalid signature');
            

            http://php.net/manual/fr/function.password-hash.php


            巧妙地缩短

            在这里用通用压缩算法“缩短”是没有用的,因为标头会比 URI 长,所以它几乎不会缩短它。

            如果你想缩短它,聪明点:如果它总是相同的,不要给出相对路径(/dir/dir)(或者只在它不是主要路径时给出)。如果扩展名始终相同,则不要提供扩展名(或者如果几乎所有内容都在png 中,则在不是png 时提供扩展名)。不要给height,因为图像带有aspect ratio:你只需要width。如果您不需要像素精确的宽度,请在 x100px 中提供。

            【讨论】:

              【解决方案7】:

              恐怕您无法比任何已知的更好地缩短查询字符串 压缩算法。如前所述,压缩 版本将比原始版本短几个(大约 4-6 个)字符。 此外,原始字符串可以相对容易地解码(例如,与解码 sha1 或 md5 相对)。

              我建议通过 Web 服务器配置来缩短 URL。你可能 通过用 ID 替换图像路径来进一步缩短它(存储 ID-filename 数据库中的对)。

              例如,以下Nginx 配置接受 像/t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858 这样的网址,其中

              • 第一个数字 (123456) 应该是数据库中的图像 ID;
              • 700500 是图像尺寸;
              • 最后一部分是一个MD5 哈希值,用于防止来自不同请求的 尺寸
              # Adjust maximum image size
              # image_filter_buffer 5M;
              
              server {
                listen          127.0.0.13:80;
                server_name     img-thumb.local;
              
                access_log /var/www/img-thumb/logs/access.log;
                error_log /var/www/img-thumb/logs/error.log info;
              
                set $root "/var/www/img-thumb/public";
              
                # /t/image_id/width/height/md5
                location ~* "(*UTF8)^/t/(\d+)/(\d+)/(\d+)/([a-zA-Z0-9]{32})$" {
                  include        fastcgi_params;
                  fastcgi_pass   unix:/tmp/php-fpm-img-thumb.sock;
                  fastcgi_param  QUERY_STRING image_id=$1&w=$2&h=$3&hash=$4;
                  fastcgi_param  SCRIPT_FILENAME /var/www/img-thumb/public/t/resize.php;
              
                  image_filter resize $2 $3;
                  error_page 415 = /empty;
              
                  break;
                }
              
                location = /empty {
                  empty_gif;
                }
              
                location / { return 404; }
              }
              

              服务器只接受指定模式的 URL,将请求转发到带有修改后的查询字符串的 /public/t/resize.php 脚本,然后使用 image_filter 模块调整 PHP 生成的图像的大小。如果出错,返回一个空的 GIF 图片。

              image_filter 是可选的,仅作为示例提供。调整大小可以完全在 PHP 端执行。顺便说一句,使用 Nginx,可以摆脱 PHP 部分。

              PHP 脚本应该按如下方式验证哈希:

              // Store this in some configuration file.
              $salt = '^sYsdfc_sd&9wa.';
              
              $w = $_GET['w'];
              $h = $_GET['h'];
              
              $true_hash = md5($w . $h . $salt . $image_id);
              if ($true_hash != $_GET['hash']) {
                die('invalid hash');
              }
              
              $filename = fetch_image_from_database((int)$_GET['image_id']);
              $img = imagecreatefrompng($filename);
              header('Content-Type: image/png');
              imagepng($img);
              imagedestroy($img);
              

              【讨论】:

                【解决方案8】:

                我怀疑如果您不希望用户可以解码散列方法,则需要更多地考虑散列方法。 base64 的问题在于 base64 字符串 看起来 像 base64 字符串。很有可能那些精明到可以查看您的页面源代码的人也可能会认出它。

                第一部分:

                一种将字符串编码为尽可能短的方法

                如果您在 URL 词汇/字符方面很灵活,这将是一个很好的起点。由于 gzip 使用反向引用获得了很多好处,因此字符串太短没有什么意义。

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

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

                压缩包: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;
                }
                

                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, '='));
                }
                

                基本上是删除冗余信息,然后将 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);
                

                此脚本的输出如下:

                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"
                

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

                除此之外,我觉得有必要以它只是理论上的事实来总结这一点,这是一个很好的思考挑战。肯定有更好的方法可以达到您想要的结果 - 我将是第一个承认我的解决方案有点荒谬的人!

                【讨论】:

                • 感谢您对这个问题有所了解。它让我更好地理解了整个事情
                【解决方案9】:

                编辑

                从上面和下面的 cmets 中读取,您需要一个解决方案来隐藏图像解析器的真实路径,为其提供固定的图像宽度。

                第 1 步:http://www.example.com/tn/full/animals/images/lion.jpg

                你可以通过.htaccess获利来实现一个基本的“缩略图”

                 RewriteEngine on
                 RewriteBase /
                 RewriteCond %{REQUEST_FILENAME} !-f
                 RewriteRule tn/(full|small)/(.*) index.php?size=$1&img=$2 [QSA,L]
                

                您的 PHP 文件:

                 $basedir="/public/content/";
                 $filename=realpath($basedir.$_GET["img"]);
                
                 ## check that file is in $basedir
                 if ((!strncmp($filename, $basedir, strlen($basedir)) 
                    ||(!file_exists($filename)) die("Bad file path");
                
                 switch ($_GET["size"]) {
                    case "full":
                        $width=700;
                        $height=500;
                        ## you can also use getimagesize() to test if the image is landscape or portrait
                    break;
                    default:
                        $width=350;
                        $height=250;
                    break;
                 }
                 ## here is your old code for resizing images
                 ## Note that the "tn" directory can exist and store the actual reduced images
                

                这使您可以使用 URL www.example.com/tn/full/animals/images/lion.jpg 来查看缩小后的图像。

                这有利于 SEO 保留原始文件名。

                第二步:http://www.example.com/tn/full/lion.jpg

                如果你想要一个更短的 url,如果你拥有的图片数量不是太多,你可以使用文件的基本名称(例如“lion.jpg”)并递归搜索。碰撞时使用索引来识别您想要的(例如“1--lion.jpg”)

                function matching_files($filename, $base) {
                    $directory_iterator = new RecursiveDirectoryIterator($base);
                    $iterator       = new RecursiveIteratorIterator($directory_iterator);
                    $regex_iterator = new RegexIterator($iterator, "#$filename\$#");
                    $regex_iterator->setFlags(RegexIterator::USE_KEY);
                    return array_map(create_function('$a', 'return $a->getpathName();'), iterator_to_array($regex_iterator, false));
                }
                
                function encode_name($filename) {
                    $files=matching_files(basename($filename), realpath('public/content'));
                    $tot=count($files);
                    if (!$tot) return NULL;
                    if ($tot==1) return $filename;
                    return "/tn/full/".array_search(realpath($filename), $files)."--".basename($filename);
                }
                
                function decode_name($filename) {
                    $i=0;
                    if (preg_match("#^([0-9]+)--(.*)#", $filename, $out)) {
                            $i=$out[1];
                            $filename=$out[2];
                    }
                
                    $files=matching_files($filename, realpath('public/content'));
                
                    return $files ? $files[$i] : NULL;
                }
                
                echo $name=encode_name("gallery/animals/images/lion.jp‌​g").PHP_EOL;
                 ## --> returns lion.jpg
                 ## You can use with the above solution the url http://www.example.com/tn/lion.jpg
                
                 echo decode_name(basename($name)).PHP_EOL;
                 ## -> returns the full path opn disk to the image "lion.jpg"
                

                原帖:

                基本上,如果您在示例中添加一些格式,您的缩短网址实际上会更长:

                img=/dir/dir/hi-res-img.jpg&w=700&h=500  // 39 chars
                y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA // 50 chars
                

                使用base64_encode 总是会产生更长的字符串。并且gzcompress 将需要更少的存储不同字符的出现;对于小字符串,这不是一个好的解决方案。

                因此,如果您想缩短之前的结果,那么什么都不做(或简单的str_rot13)显然是第一个考虑的选项。

                您还可以使用您选择的简单字符替换方法:

                 $raw_query_string = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
                 $from="0123456789abcdefghijklmnopqrstuvwxyz&=/ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                 // the following line if the result of str_shuffle($from)
                 $to="0IQFwAKU1JT8BM5npNEdi/DvZmXuflPVYChyrL4R7xc&SoG3Hq6ks=e9jW2abtOzg";
                 echo strtr($raw_query_string, $from, $to)."\n";
                
                 // Result: EDpL4MEu4MEu4NE-u5f-EDp.dmprYLU00rNLA00 // 39 chars
                

                阅读您的评论,您真正想要的是“防止任何人获得高分辨率图像”。

                实现这一目标的最佳方法是使用私钥生成校验和。

                编码:

                $secret="ujoo4Dae";
                $raw_query_string = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
                $encoded_query_string = $raw_query_string."&k=".hash("crc32", $raw_query_string.$secret);
                

                结果:img=/dir/dir/hi-res-img.jpg&amp;w=700&amp;h=500&amp;k=2ae31804

                解码:

                if (preg_match("#(.*)&k=([^=]*)$#", $encoded_query_string, $out)
                    && (hash("crc32", $out[1].$secret) == $out[2])) {
                    $decoded_query_string=$out[1];
                }
                

                这不会隐藏原始路径,但该路径没有理由公开,您的“index.php”可以在检查密钥后从本地目录输出您的图像。

                如果你真的想缩短你的原始网址,你必须考虑限制原始网址中可接受的字符。许多压缩方法都基于这样一个事实,即您可以使用一个完整字节来存储多个字符。

                【讨论】:

                • 该路径在站点上未公开。我已经这样做了,所以网址很好并且对 SEO 友好:www.mysite.com/gallery/animals/lion.jpg。而真正的路径是:/public/content/gallery/animals/images/lion.jpg。它通过以下方式在后端动态加载:index.php?img=/public/content/gallery/animals/images/lion.jpg&amp;w=700&amp;h=500 - 您只能通过打开开发工具或“共享”图像查看此链接。在“分享”的情况下,缩短 url 是必要的,我的意思是例如:“在 facebook 上分享这个图片” 等等。所以我真的不喜欢它被格式化为查询字符串。感谢您的回答
                • 我编辑了我的答案以添加另一种方法:使用 .htaccess 获取更短的 url,然后使用递归搜索获取“更短”的 url。
                【解决方案10】:

                不编码 url,如何输出原始图像的缩略图副本?这是我的想法:

                1) 通过使用随机字符命名您的图片(实际文件名),为 php 创建一个“地图”。 Random_bytes 是一个很好的起点。

                2) 在 #1 的随机 url 字符串中嵌入所需的分辨率。

                3) 使用imagecopyresampled 函数将原始图像复制到您想要输出的分辨率,然后再将其输出到客户端设备。

                例如:

                1 - 文件名示例(来自bin2hex(random_bytes(6))):a1492fdbdcf2.jpg

                2 - 所需分辨率:800x600。我的新链接可能如下所示: http://myserver.com/?800a1492fdbdcf2600http://myserfer.com/?a1492800fdbdc600f2 甚至 http://myserver.com/?800a1492fdbdcf2=600 取决于我选择在链接中嵌入分辨率的位置

                3 - PHP 会知道文件名为 a1492fdbdcf2.jpg,抓取它,使用 imagecopyresampled 复制到你想要的分辨率,然后输出。

                【讨论】:

                • 谢谢。不幸的是,重命名文件不是一种选择
                • 整个路径怎么样?因为您只考虑加密文件名
                【解决方案11】:

                我认为完全不遮掩会更好。您可以非常简单地缓存返回的图像并使用处理程序来提供它们。这需要将图像大小硬编码到 php 脚本中。当您获得新尺寸时,您可以删除缓存中的所有内容,因为它是“延迟加载”的。

                1.从请求中获取图片
                这可能是:/thumbnail.php?image=img.jpg&amp;album=myalbum。甚至可以使用重写将其制成任何东西,并具有如下 URL:/gallery/images/myalbum/img.jpg

                2。检查临时版本是否不存在
                您可以使用is_file() 执行此操作。

                3。如果不存在则创建它
                使用您当前的调整大小逻辑来执行此操作,但不要输出图像。将其保存到临时位置。

                4.将临时文件内容读取到流中
                几乎只是输出它。

                这是一个未经测试的代码示例...

                <?php
                // assuming we have a request /thumbnail.php?image=img.jpg&album=myalbum
                
                // these are temporary filenames places. you need to do this yourself on your system.
                $image = $_GET['image'];           // the file name
                $album = $_GET['album'];           // the album
                $temp_folder = sys_get_temp_dir(); // temp dir to store images 
                                                   // (this should really be a specific cache path)
                $image_gallery = "images";         // root path to the image gallery
                
                $width = 700;
                $height = 500;
                
                $real_path = "$image_gallery/$album/$image";
                $temp_path = "$temp_folder/$album/$image";
                
                if(!is_file($temp_path))
                {
                    // read in the image
                    $contents = file_get_contents($real_path);  
                
                    // resize however you are doing it now.
                    $thumb_contents = resizeImage($contents, $width, $height);
                
                    // write to temp
                    file_put_contents($temp_path, $thumb_contents);
                }
                
                $type = 'image/jpeg';
                header('Content-Type:'.$type);
                header('Content-Length: ' . filesize($temp_path));
                readfile($temp_path);
                ?>
                

                【讨论】:

                • this would be better done by not obscuring at all 是的,你是对的。我之前用 SQL 完成过(整个应用程序基于 DB)。但是现在我需要一切都是即插即用的。支持无法处理简单数据库配置的用户是很痛苦的。我收到的超过 30% 的票是关于 SQL 问题的。我失去了客户,因为他们希望应用程序“开箱即用”,即使他们不知道他们的数据库密码是什么......不再依赖用户的编程知识。但我必须给他们一些他们的图像是安全的东西。不知何故。我会看看你的解决方案,谢谢!
                • 其实这不是一个坏主意。我可以在管理面板中创建一个“触发器”,以便用户可以随时重新缓存所有图像
                • @ArturFilipiak 这就是要点。它还可以节省 CPU 时间,因为图像只需缓存一次。这正是 Wordpress 和其他 CMS 的做法。您还可以添加一些额外的标头以允许客户端缓存图像 - 特别是如果您采用重写路由 - 因为路径看起来像真正的静态图像。
                【解决方案12】:

                在您的问题中,您声明它应该是纯 PHP 并且不使用数据库,并且应该有可能对字符串进行解码。所以稍微改变一下规则:

                • 我解释这个问题的方式是,我们不太关心安全性,但我们确实想要返回图像的最短哈希值。
                • 我们还可以通过使用单向散列算法来获取“解码可能性”。
                • 我们可以将哈希值存储在 JSON 对象中,然后将数据存储在文件中,所以我们最终要做的就是字符串匹配

                ```

                class FooBarHashing {
                
                    private $hashes;
                
                    private $handle;
                
                    /**
                     * In producton this should be outside the web root
                     * to stop pesky users downloading it and geting hold of all the keys.
                     */
                    private $file_name = './my-image-hashes.json';
                
                    public function __construct() {
                        $this->hashes = $this->get_hashes();
                    }
                
                    public function get_hashes() {
                        // Open or create a file.
                        if (! file_exists($this->file_name)) {
                            fopen($this->file_name, "w");
                        }
                        $this->handle = fopen($this->file_name, "r");
                
                
                        $hashes = [];
                        if (filesize($this->file_name) > 0) {
                            $contents = fread($this->handle, filesize($this->file_name));
                            $hashes = get_object_vars(json_decode($contents));
                        }
                
                        return $hashes;
                    }
                
                    public function __destroy() {
                        // Close the file handle
                        fclose($this->handle);
                    }
                
                    private function update() {
                        $handle = fopen($this->file_name, 'w');
                        $res = fwrite($handle, json_encode($this->hashes));
                        if (false === $res) {
                            //throw new Exception('Could not write to file');
                        }
                
                        return true;
                    }
                
                    public function add_hash($image_file_name) {
                        $new_hash = md5($image_file_name, false);
                
                        if (! in_array($new_hash, array_keys($this->hashes) ) ) {
                            $this->hashes[$new_hash] =  $image_file_name;
                            return $this->update();
                        }
                
                        //throw new Exception('File already exists');
                    }
                
                    public function resolve_hash($hash_string='') {
                        if (in_array($hash_string, array_keys($this->hashes))) {
                            return $this->hashes[$hash_string];
                        }
                
                        //throw new Exception('File not found');
                    }
                }
                

                ```

                使用示例:

                <?php
                // Include our class
                require_once('FooBarHashing.php');
                $hashing = new FooBarHashing;
                
                // You will need to add the query string you want to resolve first.
                $hashing->add_hash('img=/dir/dir/hi-res-img.jpg&w=700&h=500');
                
                // Then when the user requests the hash the query string is returned.
                echo $hashing->resolve_hash('65992be720ea3b4d93cf998460737ac6');
                

                所以最终结果是一个只有 32 个字符长的字符串,比我们之前的 52 短得多。

                【讨论】:

                • 是的,我们离解决方案更近了。我使用实际文件将图像数据存储为 JSON 对象,例如:nametitledescription... 我会看看你的解决方案,谢谢
                • 天哪。这个答案是使用 json 文件作为存储(AKA 数据库)的哈希表的极其低效的实现。当您在其中获得几千条记录时,这可能最终会比使用真实数据库慢。想想对每个请求的所有解析。想想 I/O 等待和并发。不是一个好的解决方案。
                • @Phil_1984_,JSON 文件(首次加载时)以及本地存储。
                • @Phil_1984_,我不同意它与“真实”数据库相比效率低下,因为我们只读取文件一次,然后将哈希表缓存在内存中。我希望我的解决方案可以用作单例,并且可以一次完成多个查找。但是你是正确的,在 PHP 中解析 JSON 是很昂贵的,所以 CSV 会是一个更好的选择。
                • 也许我误解了使用场景,但现在纯粹谈谈 PHP...即使您将其用作单例,也会为使用该库的每个请求(例如图像请求)构建 1 个单例)。如果 10 个不同的用户同时请求不同的图像,则每个 url 都需要解码,并且由于没有共享内存(除非您使用 memcache 之类的东西),每个人都必须读取和解析文件。
                【解决方案13】:

                有很多方法可以缩短网址。您可以查看 TinyUrl 等其他服务如何缩短其网址。这是一篇关于哈希和缩短 url 的好文章:http://blog.codinghorror.com/url-shortening-hashes-in-practice/

                您可以使用 php 函数 mhash() 将哈希应用于字符串:http://php.net/manual/en/function.mhash.php

                如果你向下滚动到 mhash 网站上的“可用哈希”,你可以看到你可以在函数中使用哪些哈希(虽然我会检查哪些 php 版本有哪些函数):http://mhash.sourceforge.net/mhash.3.html

                【讨论】:

                • 感谢您的回答。这很有帮助,但不适合我的问题,因为我需要非 DB 解决方案(纯 PHP)。
                猜你喜欢
                • 1970-01-01
                • 2020-08-29
                • 2023-03-03
                • 2010-11-27
                • 2021-04-19
                • 2011-01-23
                • 1970-01-01
                • 2022-12-19
                • 2011-04-28
                相关资源
                最近更新 更多