Chaîne encodée la plus courte possible avec possibilité de décodage (url raccourcie) en utilisant seulement PHP

je cherche une méthode qui code une chaîne à plus brefs longueur possible et lui permet d'être décodable (PHP PUR, Pas de SQL). J'ai le script qui fonctionne mais je ne suis pas satisfait de la longueur de la chaîne encodée.

SCENARIO:

Lien vers une image (dépend de la résolution du fichier que je veux, montrer aux l'utilisateur):

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

lien encodé (de sorte que l'utilisateur ne peut pas deviner comment obtenir l'image plus grande):

  • www.mysite.com/share/encodedQUERYstring

donc, basicaly je voudrais encoder seulement la partie de requête de recherche de la url:

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

La méthode que j'utilise maintenant encoder au-dessus de la chaîne de requête:

  • y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0txjbcwmdtqxbuwmdaa

La méthode que j'utilise est:

 $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)); 

comment raccourcir le résultat encodé et avoir encore la possibilité de le décoder en utilisant Le PHP?

12
demandé sur Artur Filipiak 2015-01-13 23:33:46

13 réponses

je pense que vous aurez besoin de penser plus sur votre méthode de hachage si vous ne voulez pas qu'il soit décodable par l'utilisateur. Le problème avec base64 est une chaîne base64 regarde comme une corde de base64. Il y a de fortes chances que quelqu'un qui est assez futé pour regarder votre page source le reconnaîtra aussi.

première Partie:

une méthode qui code pour une chaîne plus courte possible

si vous êtes flexible sur votre URL vocab/caractères, ce sera un bon point de départ. Depuis gzip fait beaucoup de ses gains en utilisant des références arrières, il y a peu de point car la chaîne est si courte.

réfléchir à votre exemple, vous avez seulement sauvé 2 octets dans la compression, qui sont encore perdu en base64 padding:

Non-format: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"

Format: string(52) "y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA=="

si vous réduisez votre taille de vocab, cela vous permettra naturellement une meilleure compression. Disons que nous supprimons certaines informations redondantes

jetez un coup d'oeil aux fonctions:

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;
}

Et

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

ce qui se passe il y a essentiellement une suppression des informations redondantes, suivie par la compression de 4 octets en 3. Pour ce faire, il faut disposer d'un sous-ensemble de 6 bits de la table ascii. Cette fenêtre est déplacée de sorte que le décalage commence à des caractères utiles et inclut tous les caractères que vous utilisez actuellement.

Avec l'offset que j'ai utilisé, vous pouvez utiliser n'importe quoi de ASCII 38 à 102. Cela vous donne une chaîne résultante de 30 octets, c'est une compression de 9 octets (24%)! Malheureusement, vous aurez besoin de le rendre URL-safe (probablement avec base64), ce qui le ramène à 40 octets.

je pense qu'à ce stade, vous êtes assez sûr de supposer que vous avez atteint le niveau de "sécurité par l'obscurité" requis pour arrêter 99,9% des gens. Continuons cependant, pour la deuxième partie de votre question

si l'utilisateur ne peut pas deviner comment obtenir la plus grande image

il est discutable que ceci est déjà résolu avec ce qui précède, mais ce que vous devez faire est de passer ceci à travers un secret sur le serveur, de préférence avec php openssl. Le code suivant montre la complète utilisation de flux de fonctions ci-dessus et le chiffrement:

$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);

La sortie de ce script est la suivante:

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"

Vous verrez cycle entier: compression > chiffrement > base64 encodage / décodage > déchiffrement > décompression. La sortie de ce serait aussi proche que possible que vous pourriez vraiment obtenir, à proximité de la plus courte durée que vous pourriez obtenir.

tout mis à part, je me sens obligé de conclure avec le fait que c'est purement théorique, et c'était un beau défi à relever. Il y a certainement de meilleures façons d'atteindre le résultat souhaité - je serai le premier à admettre que ma solution est un peu absurde!

10
répondu calcinai 2016-10-10 15:34:07

au lieu d'encoder l'url, que diriez-vous de sortir une copie de la vignette de l'image originale? Voici ce que je pense:

1) créez une" carte " pour php en nommant vos images (les noms de fichiers réels) en utilisant des caractères aléatoires. Random_bytes est un bon point de départ.

2) Intégrer la résolution désirée dans la chaîne d'url aléatoire de #1.

3) Utilisez le imagecopyresampled fonction pour copier l'image originale dans le résolution que vous souhaitez de sortie avant la sortie out de l'appareil.

Donc par exemple:

1 - Exemple de nom de fichier (de bin2hex(random_bytes(6))): a1492fdbdcf2.jpg

2 - résolution désirée: 800x600. Mon nouveau lien pourrait ressembler à: http://myserver.com/?800a1492fdbdcf2600 ou peut-être http://myserfer.com/?a1492800fdbdc600f2 ou peut-être même http://myserver.com/?800a1492fdbdcf2=600 selon l'endroit où je choisir d'intégrer la résolution dans le lien

3-PHP saurait que le nom du fichier est a1492fdbdcf2.jpg, prenez-le, utilisez le imagecopy a été sélectionné pour copier la résolution que vous voulez, et l'afficher.

4
répondu JDW 2016-10-10 11:06:21

EDIT

en lisant les commentaires ci-dessus et ci-dessous, vous avez besoin d'une solution pour cacher le vrai chemin de votre analyseur d'image, lui donnant une largeur d'image fixe.

Étape 1:http://www.example.com/tn/full/animals/images/lion.jpg

vous pouvez réaliser un "thumbnailer" de base en profitant de .htaccess

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

votre fichier 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

Cela vous permet d'utiliser l'url www.example.com/tn/full/animals/images/lion.jpg pour voir votre image de taille réduite.

ceci a le avantage pour SEO de préserver le nom de fichier original.

Etape 2 : http://www.example.com/tn/full/lion.jpg

si vous voulez une url plus courte, si le nombre d'images que vous avez n'est pas trop élevé, vous pouvez utiliser le nom de base du fichier (par ex. "Lion.jpg") et recursively search. En cas de collision, utilisez un index pour identifier celui que vous voulez (p. ex. "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"

message Original:

en gros, si vous ajoutez du formatage dans votre exemple votre url raccourcie est en fait plus:

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

en utilisant base64_encode entraînera toujours des chaînes plus longues. Et gzcompress nécessite au moins pour stocker une occurence des caractères différents; ce n'est pas une bonne solution pour les petites chaînes.

alors ne rien faire (ou un simple str_rot13) est clairement la première option à considérer si vous voulez raccourcir le résultat que vous aviez précédemment.

vous pouvez également utiliser une méthode simple de remplacement de caractère de votre choix:

 $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

en lisant votre commentaire, ce que vous voulez vraiment, c'est "empêcher quiconque d'obtenir une image hi-res".

la meilleure façon d'y parvenir est de générer un checksum avec une clé privée.

Encoder:

$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);

Résultat: img=/dir/dir/hi-res-img.jpg&w=700&h=500&k=2ae31804

Décoder:

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

ceci ne cache pas le chemin original mais ce chemin n'a aucune raison d'être public, votre "index.php " peut afficher votre image à partir le répertoire local une fois que la clé a été vérifiée.

si vous voulez vraiment raccourcir votre URL d'origine, vous devez considérer que les caractères acceptables dans l'url d'origine sont limités. De nombreuses méthodes de compression sont basées sur le fait que vous pouvez utilisez un octet pour stocker plus d'un caractère.

3
répondu Adam 2016-10-10 14:12:40

il y a plusieurs façons de raccourcir les urls. Vous pouvez regarder comment D'autres services, comme TinyUrl, raccourcissent leurs urls. Voici un bon article sur les hashes et le raccourcissement des urls: http://blog.codinghorror.com/url-shortening-hashes-in-practice/

vous pouvez utiliser la fonction php mhash() pour appliquer des hachures aux chaînes:http://php.net/manual/en/function.mhash.php

et si vous faites défiler vers le bas jusqu'à "Hashes disponibles" sur le site Web de mhash, vous pouvez voir ce que hachages vous pouvez utiliser dans la fonction (bien que je voudrais vérifier que les versions de php antérieures ont qui fonctionne):http://mhash.sourceforge.net/mhash.3.html

2
répondu GreeKatrina 2015-01-15 18:18:12

je pense que ce serait mieux de ne pas l'obscurcir du tout. Vous pouvez tout simplement mettre en cache les images retournées et utiliser un gestionnaire pour les fournir. Cela nécessite que les tailles d'image soient codées en dur dans le script php. Lorsque vous obtenez de nouvelles tailles, vous pouvez simplement supprimer tout le cache car il est "paresseux chargé".

1. Obtenez de l'image à partir de la demande

Cela pourrait être ceci: /thumbnail.php?image=img.jpg&album=myalbum. Il peut même être fait pour être n'importe quoi en utilisant rewrite et avoir une URL comme: /gallery/images/myalbum/img.jpg.

2. Vérifiez si une version temporaire n'existe pas

Vous pouvez le faire à l'aide de is_file().

3. Créer si elle n'existe pas

Utilisez votre logique de redimensionnement actuelle pour le faire, mais ne produisez pas l'image. Enregistrez-le à l'emplacement temporaire.

4. Lire le contenu du fichier temp dans le flux

À peu près juste de sortie.

Voici un non testé exemple de code...

<?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);
?>
2
répondu Michael Coxon 2016-10-10 10:44:52

quelques mots sur la "sécurité"

vous ne serez tout simplement pas en mesure de sécuriser votre lien s'il n'y a pas de "mot de passe secret" stocké quelque part: aussi longtemps que L'URI contient toutes les informations pour accéder à votre ressource, alors il sera décodable et votre "sécurité personnalisée" (ce sont des mots opposés btw) sera cassé facilement.

vous pouvez toujours mettre un sel dans votre code PHP (comme $mysalt="....long random string...") puisque je doute que vous vouliez une sécurité éternelle (une telle approche est faible parce que vous ne pouvez pas renouveler $mysalt valeur, mais dans votre cas, quelques années de sécurité semble suffisant, car de toute façon, un utilisateur peut acheter une image et la partager ailleurs, en brisant l'un de vos mécanismes de sécurité).

si vous voulez avoir un mécanisme sûr, utilisez un mécanisme bien connu (comme le ferait un framework), ainsi qu'un mécanisme d'authentification et de gestion des droits de l'utilisateur (pour que vous puissiez savoir qui cherche votre image et s'il est autorisé à le faire).

la Sécurité a un coût, si vous ne voulez pas se permettre ses besoins d'informatique et de stockage, puis l'oublier.


Sécurisé par la signature de l'URL

si vous voulez éviter les utilisateurs by-pass facile et obtenir l'image pleine résolution, alors vous pouvez simplement signer L'URI (mais vraiment, pour la sécurité, utiliser quelque chose qui existe déjà à la place de cet exemple de projet rapide ci-dessous):

$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));

Décodage:

$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');

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


Raccourcir intelligemment

"raccourcir" avec un algorithme de compression générique est inutile ici parce que les en-têtes seront plus longs que L'URI, donc il ne sera presque jamais raccourcir.

si vous voulez la raccourcir, soyez intelligent: Ne donnez pas le chemin relatif (/dir/dir) si c'est toujours la même (ou de lui donner seulement si ce n'est pas le principal). Ne donnez pas la prolongation si c'est toujours la même (ou le donner quand il n'est pas png si presque tout est dans png). Ne donnez pas l' height parce que l'image porte le aspect ratio: vous avez seulement besoin de l' width. Il donne en x100px si vous n'avez pas besoin d'une largeur de pixel précise.

2
répondu Xenos 2016-10-11 09:28:01

dans votre question vous déclarez qu'il devrait être PHP pur et ne pas utiliser une base de données, et il devrait y avoir une possibilité de décoder les chaînes. Ainsi, plier les règles un peu:

  • la façon dont j'interprète cette question Est que nous ne nous soucions pas tant de la sécurité que de la sécurité, mais que nous voulons des hachures les plus courtes qui nous ramènent aux images.
  • On peut aussi prendre "décoder possibilité" avec une pincée de sel en utilisant un algorithme de hachage.
  • nous pouvons stocker les hachures à l'intérieur D'un objet JSON, puis stocker les données dans un fichier, de sorte que tout ce que nous avons à faire à la fin de la journée est la correspondance de chaîne de caractères
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');
    }
}

exemple d'Utilisation:

<?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');

donc le résultat final est une chaîne qui n'est que 32 caractères, qui est plus courte que la 52 nous avions avant.

1
répondu Aron 2016-10-08 23:44:22

j'ai bien peur que vous ne soyez pas capable de raccourcir la chaîne de requête mieux que n'importe quelle chaîne connue l'algorithme de compression. Comme déjà mentionné, un comprimé la version sera plus courte de quelques caractères (environ 4-6) que l'original. De plus, la chaîne originale peut être décodée relativement facilement (par opposition au décodage sha1 ou md5, par exemple).

je suggère de raccourcir les URLs au moyen de la configuration du serveur Web. Vous pourriez raccourcissez - le davantage en remplaçant le chemin d'image par un ID (store ID-nom de fichier paires dans une base de données).

Par exemple, le suivant Nginx la configuration accepte Url de la forme /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858, où

  • le premier nombre (123456) est supposé être un ID d'image de la base de données;
  • 700 et 500 sont des dimensions d'image;
  • la dernière partie est un hachage MD5 protégeant des requêtes avec différents dimentions.
# 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; }
}

Le serveur accepte uniquement les URLs du motif spécifié, transmet la requête à /public/t/resize.php script avec chaîne de requête modifiée, puis redimensionne L'image générée par PHP avec image_filter module. En cas d'Erreur, renvoie une image gif vide.

image_filter est facultatif, il est fourni uniquement à titre d'exemple. Le redimensionnement peut être effectué entièrement du côté de PHP. Avec Nginx, il est possible de se débarrasser de la partie PHP, soit dit en passant.

le script PHP est supposé valider le hash comme suit:

// 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);
0
répondu Ruslan Osmanov 2016-10-11 07:56:32

D'après la discussion dans la section des commentaires, il semble que ce que vous voulez vraiment est de protéger vos images hi-res originales.

ayant cela à l'esprit, je suggérerais de faire cela en utilisant d'abord votre configuration de serveur web (par exemple Apache mod_authz_core ou Nginx ngx_http_access_module) pour refuser l'accès du web au répertoire où vos images originales sont stockées.

notez que le serveur ne refuse l'accès à vos images le web mais vous pourrez toujours y accéder directement à partir de vos scripts php. Puisque vous affichez déjà des images en utilisant un script de "redimensionnement", je vous suggère d'y mettre une limite dure et de refuser de redimensionner les images à quelque chose de plus grand que cela (par exemple quelque chose comme ceci $width = min(1000, $_GET['w'])).

je sais que cela ne répond pas à votre question initiale mais je pense que ce serait la bonne solution pour protéger vos images. Et si vous voulez encore obscurcir le nom original et les paramètres de redimensionnement vous pouvez le faire comme bon vous semble sans vous inquiéter que quelqu'un puisse découvrir ce qui se cache derrière.

0
répondu Giedrius D 2016-10-11 21:44:06

je ne pense pas que l'url peut être raccourci beaucoup plus que sur votre propre exemple. Mais je suggère quelques étapes pour mieux obscurcir vos images.

tout d'abord, je supprimerais tout ce que vous pouvez de l'url de base que vous fermez et base64encoding, donc au lieu de

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

je voudrais utiliser

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

étaient ces derniers 16 les caractères sont un hachage pour valider l'url ouverte est la même que vous avez offert dans votre code - et l'utilisateur n'est pas en train d'essayer de piéger l'image haute résolution hors du système.

votre index.php qui sert les images de commencer comme ceci:

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

après ce point, vous pouvez envoyer l'image car l'utilisateur qui l'ouvre a accès à un lien valide.

notez l'utilisation de session_id comme partie de la chaîne brute qui rend le hash optionnel, mais le rendrait impossible pour les utilisateurs à partager une url valide-comme ce serait session bind. si vous voulez que les urls soient partageables, supprimez session_id de cet appel.

j'envelopperais l'url résultante de la même façon que vous le faites déjà, zip + base64. Le résultat serait encore plus grand que votre version, mais plus difficile à voir à travers l'obscurcissement, et donc protéger vos images contre les téléchargements non autorisés.

Si vous voulez seulement faire plus court, je ne vois pas un moyen de le faire sans renommer les fichiers (ou de leurs dossiers), ou sans l'utilisation d'une base de données.

la solution de base de données de fichiers proposée va sûrement créer des problèmes de concurrence - à moins que vous n'ayez toujours pas ou très peu de personnes utilisant le système simultanément.

0
répondu Edelmar Ziegler 2016-10-12 04:56:24

vous dites que vous voulez la taille il de sorte que si vous décidez un jour que les images de prévisualisation sont trop petites, vous voulez augmenter la taille - la solution ici est de code dur la taille de l'image dans le script php et l'éliminer de l'url. Si vous voulez changer la taille dans le futur, changez les valeurs hardcoded dans le script php (ou dans une config.php que vous incluez dans le script).

vous avez aussi dit que vous utilisez déjà des fichiers pour stocker des données d'image en tant que JSON l'objet, de la forme: name,title, description. En exploitant cela, vous n'avez pas besoin d'une base de données et pouvez utiliser le nom de fichier JSON comme clé pour rechercher les données d'image.

Lorsque l'utilisateur visite une url comme ceci:

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

Vous chargez ax9v.json à partir de l'emplacement où vous stockez déjà les fichiers json, et dans ce fichier json le chemin réel de l'image est stocké. Puis chargez l'image, redimensionnez-la selon la taille hardcoded dans votre script et envoyez-la à la utilisateur.

Drawing from the conclusions in https://blog.codinghorror.com/url-shortening-hashes-in-practice/, pour obtenir la plus petite partie de la chaîne de recherche de l'url, vous devez itérer des combinaisons de caractères valides au fur et à mesure que de nouveaux fichiers sont téléchargés (p. ex. le premier est "AAA" puis "AAB", "AAC", etc. au lieu d'utiliser un algorithme de hachage. Votre solution aurait alors seulement 3 caractères dans la chaîne de caractères pour les 238,328 premières photos que vous téléchargez.

j'avais commencé à prototype d'une solution php sur phpfiddle mais le code a disparu (n'utilisez pas phpfiddle).

0
répondu Luke 2016-10-12 19:15:01

beaucoup de choses ont été dites sur le fait que l'encodage n'aide pas la sécurité, donc je me concentre sur le raccourcissement et l'esthétique.

plutôt que de le considérer comme une chaîne, vous pourriez le considérer comme 3 composants individuels. Alors si vous limitez votre code espace pour chaque composant, vous pouvez emballer les choses ensemble beaucoup plus petit.

E. G.

  • chemin - ne comprenant que les 26 caractères (A-z) et/ -. (Variable la longueur)
  • largeur - entier (0-65k) (longueur fixe, 16 bits)
  • hauteur - entier (0-65k) (longueur fixe, 16 bits)

Je limite le chemin à un maximum de 31 caractères pour que nous puissions utiliser des groupes de 5 bits.

Emballez vos dimensions de longueur fixes en premier, et ajoutez chaque caractère de chemin comme 5 bits. Il pourrait également être nécessaire d'ajouter un caractère null pour remplir jusqu'à la fin octet. Évidemment, vous devez utiliser la même chaîne de dictionnaires pour encoder et décoder.

voir code ci-dessous.

Cela montre que, en limitant ce que vous encodez et combien vous pouvez encoder, vous pouvez obtenir une chaîne plus courte. Vous pouvez le rendre encore plus court en n'utilisant que des entiers de dimension de 12 bits (max 2048), ou même en supprimant des parties du chemin si elles sont connues comme chemin de base ou extension de fichier (voir dernier exemple).

<?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" }
0
répondu Phil 2016-10-12 23:28:10

Théorie

en théorie, nous avons besoin d'un jeu de caractères d'entrée court et d'un grand jeu de caractères de sortie. Je vais le démontrer par l'exemple suivant. Nous avons le nombre 2468 comme entier avec 10 caractères (0-9) comme jeu de caractères. Nous pouvons convertir le même nombre avec la base 2 (binaire). Alors nous avons un jeu de caractères plus court (0 et 1) et le résultat est plus long: 100110100100

Mais si nous convertissons en nombre hexadécimal (base 16) avec un jeu de caractères de 16 (0-9 et a-f). Alors nous obtenons un résultat plus courte: 9A4

Pratique

Donc, dans votre cas, nous avons le jeu de caractères suivantes pour l'entrée:

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

au total 41 caractères: nombres, minuscules et caractères spéciaux=/ -. &

Le jeu de caractères de sortie est un peu délicat. Nous voulons utiliser les caractères de sauvegarde D'URL SEULEMENT. J'ai attrapé des de ici: caractères autorisés dans le paramètre GET

donc notre caractère de sortie (73 caractères):

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

nombres, cas inférieurs et supérieurs et quelques caractères spéciaux.

Nous avons plus de caractères dans notre jeu pour la sortie que pour les entrées. La théorie dit que nous pouvons raccourcir notre chaîne de caractères. CHECK!

Codage

maintenant nous avons besoin d'une fonction d'encodage de la base 41 à la base 73. Dans ce cas, je ne connais pas de fonction PHP. Heureusement, nous pouvons saisir la fonction "convBase" d'ici.: http://php.net/manual/de/function.base-convert.php#106546 (si quelqu'un en sait plus intelligent de fonction faites le moi savoir)

<?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;
}

maintenant nous pouvons raccourcir l'url. Le code final est:

$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"

la chaîne encodée n'a que 34 caractères.

Optimisations

Vous pouvez optimiser le nombre de caractères par

  • réduire la longueur de la chaîne d'entrée. Avez-vous vraiment besoin de la surcharge de l'url syntaxe des paramètres? Peut-être Pouvez-vous formater votre chaîne de caractères comme suit:

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

    cela réduit l'entrée elle-même et le jeu de caractères d'entrée. Votre jeu de caractères en entrée réduite est alors:

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

    résultat Final:

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

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

  • réduire le jeu de caractères d'entrée ;-). Peut-être Pouvez-vous exclure d'autres personnages? Vous pouvez d'abord encoder les nombres en caractères. Alors votre jeu de caractères d'entrée peut être réduit de 10!

  • augmentez votre jeu de caractères de sortie. Donc l'ensemble donné par moi est googlé dans les 2 minutes. Vous pouvez peut-être utiliser plus de caractères de sauvegarde d'url. Aucune idée... Peut-être que quelqu'un a une liste

Sécurité

attention: il n'y a pas de logique cryptographique dans le code. Donc, si quelqu'un devine le jeux de caractères, il peut décoder la chaîne facilement. Mais vous pouvez mélanger les jeux de caractères (lorsque.) Alors c'est un peu plus difficile pour l'attaquant, mais pas vraiment à l'abri. Peut-être que c'est assez pour ta valise de toute façon.

0
répondu Timo 2017-05-23 11:46:13