Ever go to a website and think to yourself, “Wow, those are some nice photos. I wonder if I can download them and [insert personal use line here]“?
The issue of protecting images online is quite a challenge, and borderline impossible. Using Flash is a good way to do it, but now you’ve just limited your audience. Even with Flash, it’s not 100% secure. In safari, users can go to the activity window and see what resources are being downloaded and from where. Other more advanced users often use Firebug or something of the like to grab the actual URL of the image and subsequently access that file directly.
Ultimately, users can always just take a screen shot of the page, but this will also include any of the text that is above the image (unless they use Firebug to hide it). You can’t stop that without the user agreeing to download a 3rd party plugin, so might as well deal with that fact.
In lieu of all this, I still wanted to use Pablo Yanez’s images as wallpapers for my new site design. He wanted them protected. I completely agree… and out of that need, came this project.
Theory behind the process: (I’m not going to go into too much detail for, hopefully, obvious reasons)
- First thing you need to do is create a directory that users will never see or know existed, and name it something funky like, “sdflkjnVFC3424″. Chances are, they won’t guess it. This is where you will keep hi-res versions of your images. Keep track of where this directory is located relative to the image generating PHP script.
- The second thing is to create a PHP script that renders out the image dynamically, based on some parameters that are sent in via an AJAX request. Call it image.php or something.
- Now you need something that generates that AJAX request URL. The reason for this is because you want that URL to be generated within an arbitrary amount of time before the image generating PHP script gets called. You can call this encoder.php or something like that.
Here’s the trick: The encoder.php file gets the initial “Build me the URL of where the image is” request. This URL has a time stamp generated and encoded in it. Make sure the time stamp that is being generated adds or subtracts a large number of time, in this example I will say 12304 seconds get added. This part is key because if users figure out that the encoded parameter is a base_64 encoded time stamp, they can write a script to generate one too, totally defeating the purpose of the time stamp. That’s the first part of the encoder. The second part is to read the directory where the images are and, in my case, put the entire list into an array and pick a random number that is no less than 0 and no larger than the length of the array -1. This is important because in image.php, we will be doing the same lookup in the directory, except this time, we are using that random number to select the file name of the file we want to display. Also, image.php also creates a time stamp (important) and adds 12304 seconds to it. If image.php’s time stamp is within, say, 5 seconds of the encoded.php timestamp, the script continues. Otherwise, the script dies and in my case, renders out “clear.gif” to confuse people.
*Important note at this point: Using the time stamp technique disables caching of the images, but because we don’t want users to retain a copy at all, that’s fine.
This is my encoder.php file (secure parts omitted):
function getRan()
{
$dir_array = array();
if ($handle = opendir('Secure images directory, relative to site root')) {
while (false !== ($file = readdir($handle))) {
if($file!="." && $file!=".."){
$dir_array[] = $file;
}
}
closedir($handle);
}
return rand(0,count($dir_array)-1);
}
function getRanAJAX()
{
$dir_array = array();
if ($handle = opendir('Secure images directory, relative this')) {
while (false !== ($file = readdir($handle))) {
if($file!="." && $file!=".."){
$dir_array[] = $file;
}
}
closedir($handle);
}
return rand(0,count($dir_array)-1);
}
function getBGParams()
{
$r = getRan();
$t = time();//use math to change time - add or subtract in seconds
$tenc = base64_encode($t);
$tdir = bloginfo('template_directory'); //wordpress
$style= $tdir."/image.php?u=".$tenc."&p=".$r;
echo $style;
}
function getU()
{
$ref = $_SERVER['HTTP_REFERER'];
//make sure requests are coming from me
$pattern = '/^(http:\/\/www.gmtaz.com)|(http:\/\/gmtaz.com)/';
if(preg_match($pattern,$ref)){
$r = getRanAJAX();
$t = time();//use math to change time - add or subtract in seconds
$tenc = base64_encode($t);
echo '{"u":"'.$tenc.'", "d" : "' . $tdir . '/image.php", "r" : "'.$r.'", "ref" : "'.$ref.'"}';
}
}
function getR()
{
$r = rand(0, 1);
echo '{"r":"'.$r.'"}';
}
$op = $_GET['o'];
if($op=='u')
getU();
if($op == 'r')
{
getR();
}
Hopefully that all makes sense. The first AJAX request using jQuery looks like this:
< ?php include 'encoder.php' ?>
$(function(){
gmtaz.setBG('< ?php getBGParams() ?>');
setInterval(function(){
var u = '';
var url = '';
var r = '';
var dt = new Date();
var time = dt.getTime();
//for wordpress
var td = "< ?php bloginfo('template_directory'); ?>/encoder.php";
$.getJSON(td,
{o:'u', t: time},
function(d){
u = d.u;
url = d.d;
r = d.r;
var dd = '' + url + '?u='+ u +'&p=' + r;
//alert(dd +''+ '');
gmtaz.setBG(dd);
});
}, 12000);
});
setBG(dd) is actually this function:
setBG : function(url){
var url = url + gmtaz.getResolution();
$('< img />')
.attr('src', url)
.load(function(){
var src = $(this).attr('src');
$('#bg').css({
'backgroundImage':'url('+src+')',
'backgroundRepeat':'no-repeat',
'backgroundAttachment':'fixed'})
.fadeIn('slow',function(){
$('body').css({
'backgroundImage':'url('+src+')',
'backgroundRepeat':'no-repeat',
'backgroundAttachment':'fixed'
});
$(this).fadeOut('fast');
});
});
},
getResolution : function(){
return '&w='+screen.width+"&h="+screen.height;
},
All this is doing is using some jQuery to grab that image URL which is being passed in and set it as the background. I actually have two bg layers to allow for the fade effect.
Here’s image.php:
< ?php
$dir_array = array();
if ($handle = opendir('Secure Images Directory')) {
while (false !== ($file = readdir($handle))) {
if($file!="." && $file!=".."){
$dir_array[] = $file;
}
}
closedir($handle);
}
$images = $dir_array;
$t = time();//same mathematical algorithm as in encoder.php
$tdec = base64_decode($_GET['u']);
$diffT = $t-$tdec;
if($diffT > 5 || $diffT < 0)//if older than 5 seconds - die
{
die('clear.gif');
}
//Disable caching
header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Content-type: image/jpeg");
while (@ob_end_clean());
$PATH = urldecode($_GET['p']);
$File = 'secret directoy'.$images[$PATH];
if (($FileInfos= stat($File)) or die("File not found!"));
$FileExt= substr($File, -3);
$FileTypeMIME= array("jpg" => "image/jpeg",
"png" => "image/png",
"gif" => "image/gif",
"ico" => "image/x-icon");
$ContentType= $FileTypeMIME[$FileExt];
if (empty($ContentType)) die("You are not allowed to access this file!");
$h = $_GET['h'];
$w = $_GET['w'];
$im = @imagecreatefromjpeg($File);
$width = 1920;
$height = 1200;
$NewThumb= ImageCreateTrueColor($w,$h);
// check if ratios match
$_ratio=array($width/$height,$w/$h);
if ($_ratio[0] != $_ratio[1]) { // crop image
// find the right scale to use
$_scale=min((float)($width/$w),(float)($height/$h));
// coords to crop
$cropX=(float)($width-($_scale*$w));
$cropY=(float)($height-($_scale*$h));
// cropped image size
$cropW=(float)($width-$cropX);
$cropH=(float)($height-$cropY);
$crop = ImageCreateTrueColor($cropW,$cropH);
// crop the middle part of the image to fit proportions
ImageCopy(
$crop,
$im,
0,
0,
(int)($cropX/2),
(int)($cropY/2),
$cropW,
$cropH
);
}
// do the thumbnail
if (isset($crop)) { // been cropped
ImageCopyResampled(
$NewThumb,
$crop,
0,
0,
0,
0,
$w,
$h,
$cropW,
$cropH
);
ImageDestroy($crop);
} else { // ratio match, regular resize
ImageCopyResampled(
$NewThumb,
$im,
0,
0,
0,
0,
$w,
$h,
$width,
$height
);
}
$q=60;//final output quality
ImageJpeg($NewThumb,null,$q);
ImageDestroy($NewThumb);
ImageDestroy($im);
?>
You may notice that I’m doing some cropping here. Actually what I’m doing is sizing the image to the full resolution of the visitor. I crop it to fit the dimension ratio first, then I resize it. This should make the image always look the same no matter what computer you’re on. Handy for dynamically creating wallpapers as well.. I might expand on this and include that function in my wallpaper gallery.
Let me know if anyone knows a way around this. I’m interested in tightening this up!
[...] original here: .intheMeanTime » Blog Archive » Protect Images Online using PHP … If you enjoyed this article please consider sharing [...]
[...] See original here: .intheMeanTime » Blog Archive » Protect Images Online using PHP … [...]
Hi!
I would like to aks if it’s possible to get the download files..(some example) beacause I can’t get to work this.
Thank you.