仅使用 PHP 具有解码可能性(缩短 url)的最短编码字符串
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27931003/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Shortest possible encoded string with decode possibility (shorten url) using only PHP
提问by Artur Filipiak
I'm looking for a method that encodes an string to shortestpossible length and lets it be decodable(pure PHP, no SQL). I have working script but I'm unsatisfied with length of the encoded string.
我正在寻找一种将字符串编码为尽可能短的长度并使其可解码的方法(纯 PHP,无 SQL)。我有工作脚本,但我对编码字符串的长度不满意。
SCENARIO:
设想:
Link to an image (depends on the file resolution I want to show to the user):
链接到图像(取决于我想向用户显示的文件分辨率):
- www.mysite.com/share/index.php?img=/dir/dir/hi-res-img.jpg&w=700&h=500
- www.mysite.com/share/index.php?img=/dir/dir/hi-res-img.jpg&w=700&h=500
Encoded link (so the user can't guess how to get the larger image):
编码链接(因此用户无法猜测如何获得更大的图像):
- www.mysite.com/share/encodedQUERYstring
- www.mysite.com/share/encodedQUERYstring
So, basicaly I'd like to encode only the search query part of the url:
所以,基本上我只想对 url 的搜索查询部分进行编码:
- img=/dir/dir/hi-res-img.jpg&w=700&h=500
- img=/dir/dir/hi-res-img.jpg&w=700&h=500
The method I use right now will encode the above query string to:
我现在使用的方法会将上述查询字符串编码为:
- y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA
- y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA
The method I use is:
我使用的方法是:
$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));
How do I shorten the encoded result and still have the possibility to decode it using onlyPHP?
如何缩短编码结果并仍然可以仅使用PHP对其进行解码?
采纳答案by calcinai
I suspect that you will need to think more about your method of hashing if you don't want it to be decodable by the user. The issue with base64
is that a base64 string lookslike a base64 string. There's a good chance that someone that's savvy enough to be looking at your page source will probably recognise it too.
我怀疑如果您不希望用户对其进行解码,您将需要更多地考虑您的散列方法。问题base64
在于 base64 字符串看起来像 base64 字符串。很有可能那些足够精明地查看您的页面源代码的人也可能会认出它。
Part one:
第一部分:
a method that encodes an string to shortest possible length
一种将字符串编码为尽可能短的长度的方法
If you're flexible on your URL vocab/characters, this will be a good starting place. Since gzip makes a lot of its gains using back references, there is little point as the string is so short.
如果您对 URL 词汇/字符很灵活,这将是一个很好的起点。由于 gzip 使用反向引用获得了很多收益,因此字符串太短没有什么意义。
Consider your example - you've only saved 2 bytes in the compression, which are lost again in base64 padding:
考虑您的示例 - 您在压缩中只保存了 2 个字节,这些字节在 base64 填充中再次丢失:
Non-gzipped: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"
非压缩: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"
Gzipped: string(52) "y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA=="
压缩包: string(52) "y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA=="
If you reduce your vocab size, this will naturally allow you better compression. Let's say we remove some redundant information
如果你减少你的词汇量,这自然会让你更好地压缩。假设我们删除了一些冗余信息
Take a look at the functions:
看一下函数:
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;
}
And
和
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, '='));
}
What's going on there is basically a removal of redundant information, followed by the compression of 4 bytes into 3. This is achieved by effectively having a 6-bit subset of the ascii table. This window is moved so that the offset starts at useful characters and includes all the characters you're currently using.
发生的事情基本上是去除冗余信息,然后将 4 个字节压缩为 3 个。这是通过有效地拥有 6 位 ASCII 表子集来实现的。移动此窗口,以便偏移量从有用字符开始,并包括您当前使用的所有字符。
With the offset I've used, you can use anything from ASCII 38 to 102. This gives you a resulting string of 30 bytes, that's a 9-byte (24%) compression! Unfortunately, you'll need to make it URL-safe (probably with base64), which brings it back up to 40 bytes.
使用我使用的偏移量,您可以使用从 ASCII 38 到 102 的任何内容。这会为您提供30 字节的结果字符串,即 9 字节 (24%) 压缩!不幸的是,您需要使其 URL 安全(可能使用 base64),这会将其恢复到 40 字节。
I think at this point, you're pretty safe to assume that you've reached the "security through obscurity" level required to stop 99.9% of people. Let's continue though, to the second part of your question
我认为在这一点上,您可以非常安全地假设您已经达到阻止 99.9% 人所需的“通过默默无闻的安全”级别。让我们继续,到你问题的第二部分
so the user can't guess how to get the larger image
所以用户无法猜测如何获得更大的图像
It's arguable that this is already solved with the above, but what you need to do is pass this through a secret on the server, preferably with php openssl. The following code shows the complete usage flow of functions above and the encryption:
可以说这已经通过上面的方法解决了,但是你需要做的是通过服务器上的一个秘密来传递这个,最好是使用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);
The output of this script is the following:
此脚本的输出如下:
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"
You'll see the whole cycle: compression > encryption > base64 encode/decode > decryption > decompression. The output of this would be as close as possible as you could really get, at near the shortest length you could get.
您将看到整个循环:压缩 > 加密 > base64 编码/解码 > 解密 > 解压。其输出将尽可能接近您真正可以获得的长度,接近您可以获得的最短长度。
Everything aside, I feel obliged to conclude this with the fact that it is theoretical only, and this was a nice challenge to think about. There are definitely better ways to achieve your desired result - I'll be the first to admit that my solution is a little bit absurd!
抛开一切不谈,我觉得有必要以它只是理论上的事实来总结这一点,这是一个很好的思考挑战。肯定有更好的方法来达到你想要的结果 - 我会第一个承认我的解决方案有点荒谬!
回答by JDW
Instead of encoding the url, how about outputting a thumbnail copy of the original image? Here's what I'm thinking:
不是对 url 进行编码,而是输出原始图像的缩略图副本如何?这是我的想法:
1) Create a "map" for php by naming your pictures (the actual file names) using random characters. Random_bytesis a great place to start.
1)通过使用随机字符命名您的图片(实际文件名)来为php创建一个“地图”。Random_bytes是一个很好的起点。
2) Embed the desired resolution within the randomized url string from #1.
2) 将所需的分辨率嵌入 #1 的随机 url 字符串中。
3) Use the imagecopyresampledfunction to copy the original image into the resolution you would like to output before outputting it out to the client's device.
3) 使用imagecopyresampled函数将原始图像复制到您想要输出的分辨率,然后再将其输出到客户端设备。
So for example:
例如:
1 - Filename example (from bin2hex(random_bytes(6))
): a1492fdbdcf2.jpg
1 - 文件名示例(来自bin2hex(random_bytes(6))
):a1492fdbdcf2.jpg
2 - Resolution desired: 800x600. My new link could look like:
http://myserver.com/?800a1492fdbdcf2600
or maybe http://myserfer.com/?a1492800fdbdc600f2
or maybe even http://myserver.com/?800a1492fdbdcf2=600
depending on where I choose to embed the resolution within the link
2 - 所需的分辨率:800x600。我的新链接可能看起来像:
http://myserver.com/?800a1492fdbdcf2600
或者http://myserfer.com/?a1492800fdbdc600f2
甚至可能http://myserver.com/?800a1492fdbdcf2=600
取决于我选择在链接中嵌入分辨率的位置
3 - PHP would know that the file name is a1492fdbdcf2.jpg, grab it, use the imagecopyresampled to copy to the resolution you want, and output it.
3 - PHP 会知道文件名是 a1492fdbdcf2.jpg,抓取它,使用 imagecopyresampled 复制到你想要的分辨率,然后输出。
回答by Adam
EDIT
编辑
Reading from the above and below comments, you need a solution to hide the real path of your image parser, giving it a fixed image width.
从上面和下面的评论中阅读,您需要一个解决方案来隐藏图像解析器的真实路径,使其具有固定的图像宽度。
Step 1 : http://www.example.com/tn/full/animals/images/lion.jpg
第1步 : http://www.example.com/tn/full/animals/images/lion.jpg
You can achieve a basic "thumbnailer" by taking profit of .htaccess
您可以通过获利来实现基本的“缩略图” .htaccess
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule tn/(full|small)/(.*) index.php?size=&img= [QSA,L]
Your PHP file:
您的 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
This lets you using the url www.example.com/tn/full/animals/images/lion.jpg
to view your reduced in size image.
这使您可以使用 urlwww.example.com/tn/full/animals/images/lion.jpg
来查看缩小后的图像。
This has the advantage for SEO to preserve the original file name.
这有利于 SEO 保留原始文件名。
Step 2 : http://www.example.com/tn/full/lion.jpg
第2步 : http://www.example.com/tn/full/lion.jpg
If you want a shorter url, if the number of images you have is not too much, you can use the basename of the file (eg. "lion.jpg") and recursively search. When collision use an index to identify which one you want (eg. "1--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"
Original post:
原帖:
Basically, if you add some formatting in your example your shorten url is in fact longer:
基本上,如果您在示例中添加一些格式,则缩短的 url 实际上更长:
img=/dir/dir/hi-res-img.jpg&w=700&h=500 // 39 chars
y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA // 50 chars
Using base64_encode
will always result in longer strings. And gzcompress
will require at less to store one occurence of the different chars; this is not a good solution for small strings.
使用base64_encode
总是会导致更长的字符串。并且gzcompress
至少需要存储一次出现的不同字符;对于小字符串,这不是一个好的解决方案。
So doing nothing (or a simple str_rot13
) is clearly the first option to consider if you want to shorten the result you had previously.
因此str_rot13
,如果您想缩短之前的结果,什么都不做(或简单的)显然是第一个要考虑的选项。
You can also use a simple character replacement method of your choice:
您还可以使用您选择的简单字符替换方法:
$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
Reading from your comment, what you really want is "to prevent anyone to gets a hi-res image".
从您的评论中阅读,您真正想要的是“防止任何人获得高分辨率图像”。
The best way to achieve that is to generate a checksum with a private key.
实现这一目标的最佳方法是使用私钥生成校验和。
Encode:
编码:
$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);
Result: img=/dir/dir/hi-res-img.jpg&w=700&h=500&k=2ae31804
结果: img=/dir/dir/hi-res-img.jpg&w=700&h=500&k=2ae31804
Decode:
解码:
if (preg_match("#(.*)&k=([^=]*)$#", $encoded_query_string, $out)
&& (hash("crc32", $out[1].$secret) == $out[2])) {
$decoded_query_string=$out[1];
}
This does not hide the original path but this path has no reason to be public, your "index.php" can output your image from the local directory once the key has been checked.
这不会隐藏原始路径,但此路径没有理由公开,一旦检查了密钥,您的“index.php”就可以从本地目录输出您的图像。
If you really want to shorten your original URL, you have to consider the acceptable characters in the original url to be restricted. Many compression methods are based on the fact that you can use a full byte to store more than a character.
如果你真的想缩短你的原始网址,你必须考虑限制原始网址中可接受的字符。许多压缩方法都基于这样一个事实,即您可以使用一个完整的字节来存储多个字符。
回答by Michael Coxon
I think this would be better done by not obscuring at all. You could quite simply cache returned images and use a handler to provide them. This requires the image sizes to be hardcoded into the php script. When you get new sizes you can just delete everything in the cache as it is 'lazy loaded'.
我认为完全不遮挡会更好地做到这一点。您可以非常简单地缓存返回的图像并使用处理程序来提供它们。这需要将图像大小硬编码到 php 脚本中。当您获得新尺寸时,您可以删除缓存中的所有内容,因为它是“延迟加载”的。
1. Get the image from the request
This could be this: /thumbnail.php?image=img.jpg&album=myalbum
. It could even be made to be anything using rewrite and have a URL like: /gallery/images/myalbum/img.jpg
.
1.从请求中获取图像
这可能是这样的:/thumbnail.php?image=img.jpg&album=myalbum
。甚至可以使用 rewrite 将它变成任何东西,并具有如下 URL:/gallery/images/myalbum/img.jpg
。
2. Check to see if a temp version does not exist
You can do this using is_file()
.
2. 检查临时版本是否不存在
您可以使用is_file()
.
3. Create it if it does not exist
Use your current resizing logic to do it, but don't output the image. Save it to the temp location.
3.如果不存在则创建它
使用您当前的调整大小逻辑来执行它,但不要输出图像。将其保存到临时位置。
4. Read the temp file contents to the stream
Pretty much just output it.
4. 将临时文件内容读取到流中
几乎只是输出它。
Here is an untestedcode example...
这是一个未经测试的代码示例...
<?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);
?>
回答by GreeKatrina
There are many ways to shorten urls. You can look up how other services, like TinyUrl, shorten their urls. Here is a good article on hashes and shortening urls: http://blog.codinghorror.com/url-shortening-hashes-in-practice/
有很多方法可以缩短网址。您可以查看其他服务(如 TinyUrl)如何缩短其网址。这是一篇关于哈希和缩短网址的好文章:http: //blog.codinghorror.com/url-shortening-hashes-in-practice/
You can use the php function mhash() to apply hashes to strings: http://php.net/manual/en/function.mhash.php
您可以使用 php 函数 mhash() 将哈希应用于字符串:http: //php.net/manual/en/function.mhash.php
And if you scroll down to "Available Hashes" on the mhash website, you can see what hashes you can use in the function (although I would check what php versions have which functions):http://mhash.sourceforge.net/mhash.3.html
如果你向下滚动到 mhash 网站上的“可用哈希”,你可以看到你可以在函数中使用哪些哈希(尽管我会检查哪些 php 版本具有哪些函数):http: //mhash.sourceforge.net/mhash .3.html
回答by Xenos
Short words about "security"
关于“安全”的简短词
You simply won't be able to secure your link if there is no "secret password" stored somewhere: as long as the URI carries all information to access your resource, then it will be decodable and your "custom security" (they are opposite words btw) will be broken easily.
如果没有“秘密密码”存储在某处,您将无法保护您的链接:只要 URI 携带所有信息来访问您的资源,那么它将是可解码的,并且您的“自定义安全性”(它们是相反的)顺便说一句)会很容易被打破。
You can still put a salt in your PHP code (like $mysalt="....long random string..."
) since I doubt you want an eternal security (such approach is weak because you cannot renew the $mysalt
value, but in your case, few years security sounds sufficient, since anyway, a user can buy one picture and share it elsewhere, breaking any of your security mechanism).
你还可以把盐在你的PHP代码(像$mysalt="....long random string..."
),因为我怀疑你想要一个永恒的安全性(这样的方法是弱,因为你不能更新$mysalt
值,但在你的情况下,几年来的安全听起来足够了,因为无论如何,用户可以购买一张图片并在其他地方分享,破坏了您的任何安全机制)。
If you want to have a safe mechanism, use a well-known one (as a framework would carry), along with authentication and user rights management mechanism (so you can know who's looking for your image, and whether they are allowed to).
如果您想拥有一种安全机制,请使用众所周知的机制(作为框架将携带),以及身份验证和用户权限管理机制(这样您就可以知道谁在寻找您的图像,以及他们是否被允许)。
Security has a cost, if you don't want to afford its computing & storing requirements, then forget about it.
安全是有代价的,如果你不想负担它的计算和存储要求,那就忘掉它吧。
Secure by signing the URL
通过对 URL 签名来确保安全
If you want to avoid users easy by-passing and get full res picture, then you may just sign the URI (but really, for safety, use something that already exist instead of that quick draft example below):
如果你想避免用户轻易绕过并获得完整的分辨率图片,那么你可以只签署 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));
Decoding:
解码:
$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');
See http://php.net/manual/fr/function.password-hash.php
见http://php.net/manual/fr/function.password-hash.php
Shorten smartly
巧妙地缩短
"Shortening" with a generic compression algorithm is useless here because the headers will be longer than the URI, so it will almost never shorten it.
使用通用压缩算法“缩短”在这里是无用的,因为标头将比 URI 长,因此它几乎永远不会缩短它。
If you want to shorten it, be smart: don't give the relative path (/dir/dir
) if it's always the same (or give it only if it's not the main one). Don't give the extension if it's always the same (or give it when it's not png
if almost everything is in png
). Don't give the height
because the image carries the aspect ratio
: you only need the width
. Give it in x100px
if you do not need a pixel-accurate width.
如果你想缩短它,聪明点:/dir/dir
如果相对路径 ( ) 总是相同的,不要给出它(或者只有当它不是主要路径时才给出)。如果扩展名始终相同,则不要给出扩展名(或者png
如果几乎所有内容都在 中,则不要给出扩展名png
)。不要给出 ,height
因为图像带有aspect ratio
:您只需要width
。x100px
如果您不需要像素精确的宽度,请放弃。
回答by Aron
In your question you state that it should be pure PHP and not use a database, and there should be a possibility to decode the strings. So bending the rules a bit:
在您的问题中,您声明它应该是纯 PHP 并且不使用数据库,并且应该有可能对字符串进行解码。所以稍微改变一下规则:
- The way I am interpreting this question is that we don't care about security that much but, we do want the shortest hashes that lead back to images.
- We can also take "decode possibility" with a pinch of salt by using a one way hashing algorithm.
- We can store the hashes inside a JSON object, then store the data in a file, so all we have to do at the end of the day is string matching
- 我解释这个问题的方式是,我们不太关心安全性,但是,我们确实想要返回图像的最短哈希值。
- 我们还可以通过使用单向散列算法来获得“解码可能性”。
- 我们可以将哈希值存储在一个 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');
}
}
```
``
Usage example:
用法示例:
<?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');
So the end result is a string that is only 32chars long, which is way shorter than the 52we had before.
所以最终结果是一个只有32 个字符长的字符串,这比我们之前的52个字符短得多。
回答by Timo
Theory
理论
In theory we need a short input character set and a large output character set. I will demonstrate it by the following example. We have the number 2468 as integer with 10 characters (0-9) as character set. We can convert it to the same number with base 2 (binary number system). Then we have a shorter character set (0 and 1) and the result is longer: 100110100100
理论上我们需要一个短的输入字符集和一个大的输出字符集。我将通过以下示例进行演示。我们将数字 2468 作为整数,以 10 个字符 (0-9) 作为字符集。我们可以将其转换为基数为 2(二进制数字系统)的相同数字。然后我们有一个更短的字符集(0和1),结果更长:100110100100
But if we convert to hexadecimal number (base 16) with a character set of 16 (0-9 and A-F). Then we get a shorter result: 9A4
但是如果我们转换为字符集为 16(0-9 和 AF)的十六进制数(基数为 16)。然后我们得到一个较短的结果:9A4
Practice
实践
So in your case we have the following character set for the input:
因此,在您的情况下,我们为输入设置了以下字符集:
$inputCharacterSet = "0123456789abcdefghijklmnopqrstuvwxyz=/-.&";
In total 41 characters: Numbers, lower cases and the special chars = / - . &
总共 41 个字符:数字、小写和特殊字符 = / - 。&
The character set for output is a bit tricky. We want use URL save characters only. I've grabbed them from here: Characters allowed in GET parameter
输出的字符集有点棘手。我们只想使用 URL 保存字符。我从这里获取了它们:GET 参数中允许的字符
So our output character set is (73 characters):
所以我们的输出字符集是(73 个字符):
$outputCharacterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-_.!*'(),$";
Numbers, lower AND upper cases and some special chars.
数字、小写和大写以及一些特殊字符。
We have more characters in our set for the output than for the intput. Theory says we can short our input string. CHECK!
我们的输出集中字符多于输入字符。理论说我们可以缩短输入字符串。查看!
Coding
编码
Now we need an encode function from base 41 to base 73. For that case I don't know a PHP function. Luckily we can grab the function 'convBase' from here: http://php.net/manual/de/function.base-convert.php#106546(if someone knows a smarter function let me know)
现在我们需要一个从基数 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;
}
Now we can short the url. The final code is:
现在我们可以缩短网址。最后的代码是:
$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"
The encoded string has only 34 characters.
编码后的字符串只有 34 个字符。
Optimizations
优化
You can optimize the count of characters by
您可以通过以下方式优化字符数
reduce the length of input string. Do you really need the overhead of url parameter syntax? Maybe you can format your string as follows:
$input = '/dir/dir/hi-res-img.jpg,700,500';
This reduces the input itself AND the input character set. Your reduced input character set is then:
$inputCharacterSet = "0123456789abcdefghijklmnopqrstuvwxyz/-.,";
Final output:
string(27) "E$AO.Y_JVIWMQ9BB_Xb3!Th*-Ut"
string(31) "/dir/dir/hi-res-img.jpg,700,500"
reducing the input character set ;-). Maybe you can exclude some more characters? You can encode the numbers to characters first. Then your input character set can be reduced by 10!
increase your output character set. So the given set by me is googled within 2 minutes. Maybe you can use more url save characters. No idea... Maybe someone has a list
减少输入字符串的长度。你真的需要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 保存字符。不知道......也许有人有一个清单
Security
安全
Heads up: There is no cryptographically logic in the code. So if somebody guesses the character sets, he can decode the string easily. But you can shuffle the character sets (once). Then it is a bit harder for the attacker, but not really safe. Maybe its enough for your use case anyway.
注意:代码中没有加密逻辑。因此,如果有人猜出字符集,他就可以轻松地对字符串进行解码。但是你可以洗牌字符集(一次)。然后对攻击者来说有点困难,但并不真正安全。无论如何,对于您的用例来说可能已经足够了。
回答by Ruslan Osmanov
I'm afraid, you won't be able to shorten the query string better than any known compression algorithm. As already mentioned, a compressed version will be shorter by a few (around 4-6) characters than the original. Moreover, the original string can be decoded relatively easy (opposed to decoding sha1 or md5, for instance).
恐怕,您将无法比任何已知的压缩算法更好地缩短查询字符串。如前所述,压缩版本将比原始版本短几个(大约 4-6 个)字符。此外,原始字符串可以相对容易地解码(例如,与解码 sha1 或 md5 相对)。
I suggest shortening URLs by means of Web server configuration. You might shorten it further by replacing image path with an ID (store ID-filenamepairs in a database).
我建议通过 Web 服务器配置来缩短 URL。您可以通过用 ID 替换图像路径来进一步缩短它( 在数据库中存储ID-文件名对)。
For example, the following Nginxconfiguration accepts
URLs like /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858
, where
例如,下面的Nginx配置接受像这样的 URL /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858
,其中
- the first number (
123456
) is supposed to be an image ID from database; 700
and500
are image dimentions;- the last part is an MD5 hash protecting from requests with different dimentions.
- 第一个数字 (
123456
) 应该是数据库中的图像 ID; 700
和500
是图像尺寸;- 最后一部分是一个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=&w=&h=&hash=;
fastcgi_param SCRIPT_FILENAME /var/www/img-thumb/public/t/resize.php;
image_filter resize ;
error_page 415 = /empty;
break;
}
location = /empty {
empty_gif;
}
location / { return 404; }
}
The server accepts only URLs of specified pattern, forwards request to /public/t/resize.php
script with modified query string, then resizes the image generated by PHP with image_filter
module. In case of error, returns an empty GIF image.
服务器只接受指定模式的 URL,将请求转发给/public/t/resize.php
带有修改后的查询字符串的脚本,然后使用image_filter
模块调整 PHP 生成的图像的大小。如果出现错误,则返回一个空的 GIF 图像。
The image_filter
is optional, it is included only as an example. Resizing can be performed fully on PHP side. With Nginx, it is possible to get rid of PHP part, by the way.
的image_filter
是可选的,它被包括仅作为示例。调整大小可以完全在 PHP 端执行。顺便说一下,使用 Nginx 可以摆脱 PHP 部分。
The PHP script is supposed to validate the hash as follows:
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);
回答by Giedrius D
From the discussion in the comments section it looks like what you really want is to protect your original hi-res images.
从评论部分的讨论看来,您真正想要的是保护您的原始高分辨率图像。
Having that in mind I'd suggest to actually do that first using your web server configuration (e.g. Apache mod_authz_coreor Nginx ngx_http_access_module) to deny access from the web to the directory where your original images are stored.
考虑到这一点,我建议首先使用您的 Web 服务器配置(例如 Apache mod_authz_core或 Nginx ngx_http_access_module)来拒绝从 Web 访问存储原始图像的目录。
Note that we server will only deny access to your images from the web but you will still be able to access them directly from your php scripts. Since you already are displaying images using some "resizer" script I'd suggest putting some hard limit there and refuse to resize images to anything bigger then that (e.g. something like this $width = min(1000, $_GET['w'])
).
请注意,我们的服务器只会拒绝从 Web 访问您的图像,但您仍然可以直接从您的 php 脚本访问它们。由于您已经使用一些“调整器”脚本显示图像,我建议在那里设置一些硬限制并拒绝将图像调整为更大的图像(例如像这样的东西$width = min(1000, $_GET['w'])
)。
I know this does not answer your original question but I think this would the right solution to protect your images. And if you still want to obfuscate the original name and resizing parameters you can do that however you see fit without worrying that someone might figure out whats behind it.
我知道这不能回答您最初的问题,但我认为这是保护您的图像的正确解决方案。如果您仍然想混淆原始名称并调整参数大小,您可以按照自己认为合适的方式进行操作,而不必担心有人可能会弄清楚其背后的内容。