web-dev-qa-db-fra.com

Chaîne codée la plus courte possible avec possibilité de décodage (raccourcir l'URL) en utilisant uniquement PHP

Je cherche une méthode qui code une chaîne à la longueur la plus courte} possible et la laisse être décodable (PHP pur, pas de SQL). J'ai un script de travail 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 à l'utilisateur):

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

Lien codé (l'utilisateur ne peut donc pas deviner comment obtenir une image plus grande):

  • www.mysite.com/share/encodedQUERYstring

Donc, fondamentalement, je voudrais encoder uniquement la partie requête de recherche de l'URL:

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

La méthode que j'utilise actuellement va coder la chaîne de requête ci-dessus pour:

  • 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 puis-je raccourcir le résultat encodé tout en ayant la possibilité de le décoder à l'aide de uniquement PHP?

12
Artur Filipiak

Je suppose que vous devrez réfléchir davantage à votre méthode de hachage si vous ne souhaitez pas que celle-ci soit décodable par l'utilisateur. Le problème avec base64 est qu’une chaîne en base64 ressemble ressemble à une chaîne en base64. Il y a de fortes chances que quelqu'un qui est assez averti pour regarder votre source de page le reconnaisse aussi.

Partie un:

une méthode qui code une chaîne à la longueur la plus courte possible

Si vous êtes flexible sur votre vocabulaire/caractères URL, ce sera un bon point de départ. Étant donné que gzip tire beaucoup de ses gains en utilisant des références en arrière, il n’ya aucun intérêt à ce que la chaîne soit si courte.

Considérez votre exemple - vous n’avez enregistré que 2 octets dans la compression, qui sont à nouveau perdus lors du remplissage en base64:

Non gzippé: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"

Gzipped: 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'œil 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, c’est essentiellement la suppression des informations redondantes, suivie de la compression de 4 octets en 3. C’est obtenu grâce à un sous-ensemble de 6 bits de la table ascii. Cette fenêtre est déplacée de sorte que le décalage commence par les caractères utiles et inclut tous les caractères que vous utilisez actuellement.

Avec le décalage 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 devrez le rendre sûr pour les URL (probablement avec base64), ce qui le ramènera à 40 octets.

Je pense que pour le moment, vous pouvez supposer que vous avez atteint le niveau de "sécurité par l'obscurité" requis pour arrêter 99,9% des personnes. Continuons cependant à la deuxième partie de votre question

de sorte que l'utilisateur ne puisse pas deviner comment obtenir l'image plus grande

On peut soutenir que cela est déjà résolu avec ce qui précède, mais vous devez le transmettre à un secret sur le serveur, de préférence avec php openssl . Le code suivant montre le flux d'utilisation complet des 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 tout le cycle: compression> chiffrement> codage/décodage base64> déchiffrement> décompression. La sortie de ceci serait aussi proche que possible de ce que vous pourriez réellement obtenir, au plus court terme possible.

Tout mis à part, je me sens obligé de conclure cela avec le fait que ce n’est que théorique, et c’était un beau défi à relever. Il existe certainement de meilleurs moyens d’atteindre le résultat souhaité. Je serai le premier à admettre que ma solution est un peu absurde!

12
calcinai

Au lieu d'encoder l'URL, que diriez-vous de générer une copie miniature de l'image d'origine? Voici ce que je pense:

1) Créez une "carte" pour php en nommant vos images (les noms de fichiers actuels) en utilisant des caractères aléatoires. Random_bytes est un bon endroit pour commencer.

2) Intégrez la résolution souhaitée dans la chaîne d'URL randomisée à partir de # 1.

3) Utilisez la fonction imagecopyresampled pour copier l’image originale dans la résolution souhaitée, avant de l’afficher sur le périphérique du client.

Donc par exemple:

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

2 - Résolution souhaité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 choisirais d'intégrer la résolution dans le lien.

3 - PHP devrait savoir que le nom du fichier est a1492fdbdcf2.jpg, récupérez-le, utilisez l'imagecopyresampled pour le copier dans la résolution de votre choix et imprimez-le.

4
JDW

MODIFIER

En lisant les commentaires ci-dessus et ci-dessous, vous avez besoin d'une solution pour masquer le chemin réel de votre analyseur d'image en lui attribuant une largeur d'image fixe.

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

Vous pouvez réaliser un "vignette" de base en tirant profit de .htaccess

 RewriteEngine on
 RewriteBase /
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteRule tn/(full|small)/(.*) index.php?size=$1&img=$2 [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 afficher votre image de taille réduite.

Cela a l'avantage pour le référencement de conserver le nom de fichier d'origine.

Étape 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, vous pouvez utiliser le nom de base du fichier (par exemple "lion.jpg") et effectuer une recherche récursive. En cas de collision, utilisez un index pour identifier lequel vous voulez (par exemple, "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:

Fondamentalement, si vous ajoutez du formatage dans votre exemple, votre URL de raccourcissement est en fait plus longue:

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

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

Donc, ne rien faire (ou un simple str_rot13) est clairement la première option à envisager si vous souhaitez 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 "d'empêcher quiconque d'obtenir une image haute résolution".

Le meilleur moyen d'y parvenir est de générer une somme de contrôle 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];
}

Cela ne masque pas le chemin d'origine mais ce chemin n'a aucune raison d'être public, votre "index.php" peut sortir votre image du répertoire local une fois la clé vérifiée.

Si vous voulez vraiment raccourcir votre URL d'origine, vous devez considérer les caractères acceptables dans l'URL d'origine comme étant restreints. De nombreuses méthodes de compression sont basées sur le fait que vous pouvez utiliser un octet complet pour stocker plus qu'un caractère.

3
Adam

Je pense que cela serait mieux fait en ne dissimulant pas 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 contenu du cache car il est "chargé paresseux".

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

2. Vérifier si une version temporaire n'existe pas
Vous pouvez le faire en utilisant is_file().

3. Créez-le s'il n'existe pas
Utilisez votre logique de redimensionnement actuelle pour le faire, mais ne sortez pas l'image. Enregistrez-le à l'emplacement temporaire.

4. Lire le contenu du fichier temporaire dans le flux
En gros, il suffit de le sortir.

Voici un exemple de code non testé ...

<?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
Michael Coxon

Quelques mots sur la "sécurité"

Vous ne pourrez tout simplement pas sécuriser votre lien s'il n'y a pas de "mot de passe secret" stocké quelque part: tant que l'URI contient toutes les informations permettant d'accéder à votre ressource, il sera décodable et votre "sécurité personnalisée" (elles sont en face mots btw) sera cassé facilement.

Vous pouvez toujours mettre un sel dans votre code PHP (comme $mysalt="....long random string...") car je doute que vous souhaitiez une sécurité éternelle (une telle approche est faible car vous ne pouvez pas renouveler la valeur $mysalt, mais dans votre cas, quelques années de sécurité sonne suffisant, puisqu’un utilisateur peut acheter une image et la partager ailleurs, en cassant l’un de ses mécanismes de sécurité).

Si vous souhaitez disposer d'un mécanisme sécurisé, utilisez-en un bien connu (comme un framework), ainsi qu'un mécanisme d'authentification et de gestion des droits des utilisateurs (afin de savoir qui recherche votre image et si elle est autorisée à le faire).

La sécurité a un coût, si vous ne voulez pas vous permettre de vous en passer pour vos besoins en informatique, alors oubliez-le.


Sécurisé en signant l'URL

Si vous voulez éviter aux utilisateurs de contourner facilement et d'obtenir une image complète de la résolution, vous pouvez simplement signer l'URI (mais réellement, pour des raisons de sécurité, utilisez quelque chose qui existe déjà à la place de l'exemple de brouillon 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 car les en-têtes seront plus longs que l'URI, donc il ne le raccourcira presque jamais.

Si vous voulez le raccourcir, soyez malin: ne donnez pas le chemin relatif (/dir/dir) s'il est toujours identique (ou donnez-le uniquement s'il ne s'agit pas du chemin principal). Ne donnez pas l'extension si c'est toujours la même chose (ou donnez-la quand ce n'est pas png si presque tout est dans png). Ne donnez pas la height car l'image porte le aspect ratio: vous n'avez besoin que de la width. Donnez-le dans x100px si vous n'avez pas besoin d'une largeur au pixel près.

2
Xenos

Il y a plusieurs façons de raccourcir les URL. Vous pouvez rechercher comment d'autres services, tels que TinyUrl, raccourcissent leurs URL. Voici un bon article sur les hachages et les raccourcis d’URL: http://blog.codinghorror.com/url-shortening-hashes-in-practice/

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

Et si vous faites défiler jusqu'à "Available Hashes" sur le site Web de mhash, vous pouvez voir quels hachages vous pouvez utiliser dans la fonction (bien que je vérifierais quelles versions php ont quelles fonctions): http://mhash.sourceforge.net /mhash.3.html

2
GreeKatrina

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

  • Selon mon interprétation de cette question, nous ne nous soucions pas beaucoup de la sécurité, mais nous voulons les hachages les plus courts qui ramènent aux images.
  • Nous pouvons également prendre "possibilité de décodage" avec une pincée de sel en utilisant un algorithme de hachage à sens unique.
  • Nous pouvons stocker les hachages dans un objet JSON, puis stocker les données dans un fichier. Ainsi, tout ce que nous avons à faire à la fin de la journée est la correspondance des chaînes.

`` `

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

Ainsi, le résultat final est une chaîne ne contenant que 32 caractères, ce qui est beaucoup plus court que le 52 que nous avions auparavant.

1
Aron

Je crains que vous ne puissiez raccourcir la chaîne de requête mieux qu’un algorithme de compression connu. Comme mentionné précédemment, une version compressée sera plus courte de quelques caractères (environ 4 à 6) par rapport à la version originale. 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 URL au moyen de la configuration du serveur Web. Vous pouvez le raccourcir davantage en remplaçant le chemin de l'image par un ID (store ID-nom_fichier paires dans une base de données).

Par exemple, le Nginx configuration suivant accepte les URL telles que /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858, où

  • le premier numéro (123456) est supposé être un identifiant 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 de différentes dimensions .
# 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; }
}

Le serveur accepte uniquement les URL du modèle spécifié, transmet la demande au script /public/t/resize.php avec la 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, retourne une image GIF vide.

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

Le script PHP est censé valider le hachage 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
Ruslan Osmanov

D'après la discussion dans la section des commentaires, il semble que vous souhaitiez protéger vos images haute résolution d'origine.

Gardant cela à l'esprit, je suggérerais de commencer par utiliser la configuration de votre serveur Web (par exemple, Apache mod_authz_core ou Nginx ngx_http_access_module ) pour refuser l'accès depuis le Web au répertoire où vos images d'origine sont stockées.

Notez que notre serveur refusera uniquement l'accès à vos images à partir du 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 conseillerais de fixer une limite stricte et de refuser de redimensionner les images à une taille supérieure à celle-ci (par exemple, quelque chose comme cette $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 souhaitez toujours masquer le nom d'origine et les paramètres de redimensionnement, vous pouvez le faire comme bon vous semble, sans craindre que quelqu'un ne découvre ce qui se cache derrière.

0
Giedrius D

Vous dites que vous voulez que la taille soit là pour que si vous décidiez un jour que les images d'aperçu soient trop petites, vous souhaitiez augmenter la taille - la solution ici est de coder de manière rigide la taille de l'image dans le script php et de l'éliminer de l'URL . Si vous souhaitez modifier la taille ultérieurement, modifiez les valeurs codées en dur dans le script php (ou dans un fichier config.php que vous avez inclus dans le script).

Vous avez également indiqué que vous utilisiez déjà des fichiers pour stocker des données d'image sous forme d'objet JSON, par exemple: 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 le chemin réel de l'image est stocké dans ce fichier JSON. Chargez ensuite l'image, redimensionnez-la en fonction de la taille codée en dur dans votre script et envoyez-la à l'utilisateur.

S'appuyant sur les conclusions de https://blog.codinghorror.com/url-shortening-hashes-in-practice/ , pour obtenir la plus petite partie de l'URL correspondant à la chaîne de recherche dont vous auriez besoin pour itérer des combinaisons de caractères valides comme nouvelles. les fichiers sont téléchargés (par exemple, le premier est "AAA", puis "AAB", "AAC", etc.) au lieu d'utiliser un algorithme de hachage. Votre solution n'aurait alors que 3 caractères dans la chaîne pour les 238 328 premières photos que vous avez téléchargées.

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

0
Luke

Théorie

En théorie, nous avons besoin d’un court jeu de caractères et d’un grand jeu de caractères. Je vais le démontrer par l'exemple suivant. Nous avons le nombre 2468 sous forme d'entier avec 10 caractères (0-9) sous forme de jeu de caractères. Nous pouvons le convertir en même nombre en base 2 (système de numération binaire). Ensuite, 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). On obtient alors un résultat plus court: 9A4

Entraine toi

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

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

Au total 41 caractères: Nombres, minuscules et les caractères spéciaux =/-. Et

Le jeu de caractères pour la sortie est un peu délicat. Nous voulons utiliser uniquement des caractères de sauvegarde d'URL. Je les ai saisis à partir d'ici: Caractères autorisés dans le paramètre GET } _

Notre jeu de caractères de sortie est donc (73 caractères):

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

Chiffres, majuscules ET majuscules et quelques caractères spéciaux.

Nous avons plus de caractères dans notre ensemble pour la sortie que pour l'entrée. La théorie dit que nous pouvons court-circuiter notre chaîne d'entrée. VÉRIFIER!

Codage

Nous avons maintenant 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 récupérer la fonction 'convBase' à partir d'ici: http://php.net/manual/de/function.base-convert.php#106546 (si quelqu'un connaît une fonction plus intelligente, 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 court-circuiter 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 codée n'a que 34 caractères.

Optimisations

Vous pouvez optimiser le nombre de caractères en

  • réduire la longueur de la chaîne d'entrée. Avez-vous vraiment besoin de la surcharge de la syntaxe de paramètre url? Peut-être que vous pouvez formater votre chaîne 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 réduit est alors:

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

    Sortie finale:

    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 saisi ;-). Peut-être pouvez-vous exclure d'autres personnages? Vous pouvez d'abord encoder les nombres en caractères. Ensuite, votre jeu de caractères 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. Peut-être que vous pouvez utiliser plus de caractères de sauvegarde 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 les jeux de caractères, il peut facilement décoder la chaîne. Mais vous pouvez mélanger les jeux de caractères (une fois). Ensuite, c'est un peu plus difficile pour l'attaquant, mais pas vraiment en sécurité. Peut-être que cela suffit pour votre cas d'utilisation de toute façon.

0
Timo

On a beaucoup parlé de la façon dont l'encodage ne contribue pas à la sécurité, aussi je me concentre sur le raccourcissement et l'esthétique.

Plutôt que de le considérer comme une chaîne, vous pouvez le considérer comme 3 composants individuels. Ensuite, si vous limitez votre espace de code pour chaque composant, vous pouvez regrouper des éléments beaucoup plus petits.

Par exemple.

  • path - Uniquement composé des 26 caractères (a-z) et/-. (Longueur variable)
  • 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 afin que nous puissions utiliser des groupements de 5 bits.

Emballez d'abord vos dimensions de longueur fixe et ajoutez chaque caractère de chemin d'accès sous forme de 5 bits. Il peut également être nécessaire d'ajouter un caractère nul spécial pour remplir l'octet de fin. Évidemment, vous devez utiliser la même chaîne de dictionnaire pour l’encodage et le décodage.

Voir le code ci-dessous.

Cela montre qu'en limitant ce que vous encodez et combien vous pouvez encoder, vous pouvez obtenir une chaîne plus courte. Vous pourriez le rendre encore plus court en utilisant uniquement des entiers de dimension 12 bits (2048 maximum), ou même en supprimant des parties du chemin si elles sont connues, telles que le chemin de base ou l’extension de fichier (voir le 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
Phil

Je ne pense pas que l'URL résultante puisse être raccourcie beaucoup plus que sur votre propre exemple. Mais je suggère quelques étapes pour mieux brouiller vos images.

D'abord, je supprimerais tout ce que vous pouvez de l'URL de base que vous zippez et encodez en base64, donc au lieu de

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

J'utiliserais

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

Si ces 16 derniers caractères sont un hachage pour valider l'URL ouverte est identique à celle que vous avez proposée dans votre code - et l'utilisateur n'essaie pas de tromper l'image haute résolution du système.

Votre index.php qui sert les images devrait 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 moment, vous pouvez envoyer l'image car l'utilisateur qui l'ouvrait avait accès à un lien valide.

Notez que l'utilisation de session_id en tant que partie de la chaîne brute qui rend le hachage est facultative, mais empêcherait les utilisateurs de partager une adresse URL valide, car il s'agirait d'une liaison de session. Si vous souhaitez que les URL puissent être partagées, supprimez simplement session_id de cet appel.

Je voudrais envelopper l'URL résultant de la même manière que vous faites déjà, Zip + base64. Le résultat serait encore plus volumineux que votre version, mais plus difficile à voir à travers l'obscurcissement, protégeant ainsi vos images des téléchargements non autorisés.

Si vous voulez seulement le raccourcir, je ne vois pas comment le faire sans renommer les fichiers (ou leurs dossiers), ni utiliser une base de données.

La solution de base de données de fichiers proposée créera sûrement des problèmes de simultanéité - à moins que très peu de personnes utilisent le système simultanément.

0
Edelmar Ziegler