webentwicklung-frage-antwort-db.com.de

Kürzeste mögliche codierte Zeichenfolge mit Dekodierungsmöglichkeit (URL kürzen) nur mit PHP

Ich bin auf der Suche nach einer Methode, die einen String auf kürzeste mögliche Länge codiert und ihn decodierbar (reines PHP, kein SQL) sein lässt. Ich habe ein funktionierendes Skript, bin aber mit der Länge der codierten Zeichenfolge nicht zufrieden.

SZENARIO:

Link zu einem Bild (abhängig von der Dateiauflösung, die ich dem Benutzer zeigen möchte):

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

Codierter Link (so dass der Benutzer nicht erraten kann, wie er das größere Bild erhält):

  • www.meinesite.com/share/encodedQUERYstring

Grundsätzlich möchte ich nur den Suchabfrageteil der URL codieren:

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

Die Methode, die ich gerade verwende, codiert den obigen Abfrage-String wie folgt:

  • y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA

Die Methode, die ich benutze, ist:

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

Wie kann ich das verschlüsselte Ergebnis kürzen und habe trotzdem die Möglichkeit, es mit only PHP zu entschlüsseln?

12
Artur Filipiak

Ich vermute, dass Sie mehr über Ihre Hash-Methode nachdenken müssen, wenn Sie nicht möchten, dass sie vom Benutzer dekodiert werden kann. Das Problem mit base64 ist, dass eine base64-Zeichenfolge sieht aus einer base64-Zeichenfolge ähnelt. Es besteht eine gute Chance, dass jemand, der es versteht, auf Ihre Seitenquelle zuzugreifen, dies wahrscheinlich auch erkennt.

Teil eins:

eine Methode, die eine Zeichenfolge auf die kürzestmögliche Länge codiert

Wenn Sie in Bezug auf Ihre URL-Vokabeln Zeichen flexibel sind, ist dies ein guter Ausgangspunkt. Da gzip einen großen Teil seiner Gewinne mit Rückverweisen erzielt, macht es wenig Sinn, dass die Zeichenfolge so kurz ist.

Betrachten Sie Ihr Beispiel - Sie haben nur 2 Bytes in der Komprimierung gespeichert, die beim Auffüllen mit base64 wieder verloren gehen:

Non-gzipped: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"

Gzipped: string(52) "y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA=="

Wenn Sie das Vokabular verkleinern, können Sie es natürlich besser komprimieren. Nehmen wir an, wir entfernen redundante Informationen

Schauen Sie sich die Funktionen an:

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

Und

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

Was dort vor sich geht, ist im Grunde genommen das Entfernen redundanter Informationen, gefolgt von der Komprimierung von 4 Bytes zu 3. Dies wird erreicht, indem effektiv eine 6-Bit-Teilmenge der ASCII-Tabelle vorhanden ist. Dieses Fenster wird so verschoben, dass der Versatz bei nützlichen Zeichen beginnt und alle Zeichen enthält, die Sie gerade verwenden.

Mit dem Offset, den ich verwendet habe, können Sie alles von ASCII 38 bis 102 verwenden. Dies gibt Ihnen eine resultierende Zeichenfolge von 30 Byte , das ist eine 9-Byte-Komprimierung (24%) ! Leider müssen Sie es URL-sicher machen (wahrscheinlich mit base64), was es wieder auf 40 Bytes bringt.

Ich denke, zu diesem Zeitpunkt können Sie mit ziemlicher Sicherheit davon ausgehen, dass Sie die Sicherheitsstufe erreicht haben, die erforderlich ist, um 99,9% der Menschen zu stoppen. Fahren wir jedoch mit dem zweiten Teil Ihrer Frage fort

daher kann der Benutzer nicht erraten, wie er das größere Bild erhalten soll

Es ist fraglich, ob dies mit dem oben genannten Problem bereits gelöst ist. Sie müssen es jedoch über ein Geheimnis auf dem Server weitergeben, vorzugsweise mit/- php openssl . Der folgende Code zeigt den vollständigen Ablauf der oben genannten Funktionen und die Verschlüsselung:

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

Die Ausgabe dieses Skripts lautet wie folgt:

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"

Sie sehen den gesamten Zyklus: Komprimierung> Verschlüsselung> Base64-Codierung/-Decodierung> Entschlüsselung> Dekomprimierung. Die Ausgabe davon wäre so nah wie möglich an der kürzesten möglichen Länge.

Abgesehen von allem fühle ich mich verpflichtet, dies mit der Tatsache abzuschließen, dass es nur theoretisch ist, und dies war eine schöne Herausforderung, über die man nachdenken musste. Es gibt definitiv bessere Möglichkeiten, um das gewünschte Ergebnis zu erzielen - ich gebe als Erster zu, dass meine Lösung etwas absurd ist!

12
calcinai

Wie wäre es mit der Ausgabe einer Miniaturkopie des Originalbilds, anstatt die URL zu kodieren? Hier ist was ich denke:

1) Erstellen Sie eine "Map" für PHP, indem Sie Ihre Bilder (die tatsächlichen Dateinamen) mit zufälligen Zeichen benennen. Random_bytes ist ein großartiger Ort, um zu beginnen.

2) Betten Sie die gewünschte Auflösung in die zufällige URL-Zeichenfolge aus # 1 ein.

3) Verwenden Sie die Funktion imagecopyresampled , um das Originalbild in die Auflösung zu kopieren, die Sie ausgeben möchten, bevor Sie es auf das Clientgerät ausgeben.

Also zum Beispiel:

1 - Beispiel für Dateinamen (von bin2hex(random_bytes(6))): a1492fdbdcf2.jpg

2 - Gewünschte Auflösung: 800x600. Mein neuer Link könnte folgendermaßen aussehen: http://myserver.com/?800a1492fdbdcf2600 oder vielleicht http://myserfer.com/?a1492800fdbdc600f2 oder vielleicht sogar http://myserver.com/?800a1492fdbdcf2=600, je nachdem, wo ich die Auflösung in den Link einbetten möchte

3 - PHP würde wissen, dass der Dateiname a1492fdbdcf2.jpg lautet. Nehmen Sie die Datei zur Hand, kopieren Sie sie mit dem Bildkopierer und geben Sie sie aus.

4
JDW

EDIT

Wenn Sie die obigen und nachfolgenden Kommentare lesen, benötigen Sie eine Lösung, um den tatsächlichen Pfad Ihres Bildparsers auszublenden und ihm eine feste Bildbreite zu geben.

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

Sie können einen einfachen "Thumbnailer" erzielen, indem Sie den Gewinn von .htaccess mitnehmen.

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

Ihre PHP Datei:

 $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

Auf diese Weise können Sie den URL www.example.com/tn/full/animals/images/lion.jpg verwenden, um Ihr verkleinertes Bild anzuzeigen.

Dies hat für SEO den Vorteil, dass der ursprüngliche Dateiname erhalten bleibt.

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

Wenn Sie eine kürzere URL wünschen und die Anzahl der Bilder nicht zu groß ist, können Sie den Basisnamen der Datei (z. B. "lion.jpg") verwenden und rekursiv suchen. Verwenden Sie bei einer Kollision einen Index, um zu identifizieren, welchen Sie möchten (z. B. "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"

Ursprünglicher Beitrag:

Wenn Sie in Ihrem Beispiel eine Formatierung hinzufügen, ist die verkürzte URL in der Tat länger:

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

Die Verwendung von base64_encode führt immer zu längeren Zeichenfolgen. Und gzcompress benötigt weniger, um ein Vorkommen der verschiedenen Zeichen zu speichern. Dies ist keine gute Lösung für kleine Saiten.

Nichts zu tun (oder ein einfacher str_rot13) ist eindeutig die erste Option, die Sie in Betracht ziehen sollten, wenn Sie das zuvor erzielte Ergebnis verkürzen möchten.

Sie können auch eine einfache Methode zum Ersetzen von Zeichen Ihrer Wahl verwenden:

 $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

Lesen Sie aus Ihrem Kommentar, was Sie wirklich wollen, ist "um zu verhindern, dass jemand ein hochauflösendes Bild bekommt".

Der beste Weg, dies zu erreichen, ist die Generierung einer Prüfsumme mit einem privaten Schlüssel.

Kodieren:

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

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

Dekodieren:

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

Dadurch wird der ursprüngliche Pfad nicht ausgeblendet, aber dieser Pfad hat keinen Grund, öffentlich zu sein. Ihre "index.php" kann Ihr Bild aus dem lokalen Verzeichnis ausgeben, sobald der Schlüssel überprüft wurde.

Wenn Sie Ihre ursprüngliche URL wirklich verkürzen möchten, müssen Sie die zulässigen Zeichen in der ursprünglichen URL als eingeschränkt betrachten. Viele Komprimierungsmethoden basieren auf der Tatsache, dass Sie ein vollständiges Byte verwenden können, um mehr als ein Zeichen zu speichern.

3
Adam

Ich denke, das wäre besser, wenn man es überhaupt nicht verdeckt. Sie können zurückgegebene Bilder ganz einfach zwischenspeichern und mit einem Handler bereitstellen. Dies setzt voraus, dass die Bildgrößen im PHP-Skript fest codiert sind. Wenn Sie neue Größen erhalten, können Sie einfach alles im Cache löschen, da es "faul geladen" ist.

1. Holen Sie sich das Bild aus der Anfrage
Dies könnte sein: /thumbnail.php?image=img.jpg&album=myalbum. Es könnte sogar alles mit Rewrite gemacht werden und eine URL haben wie: /gallery/images/myalbum/img.jpg.

2. Überprüfen Sie, ob eine temporäre Version nicht vorhanden ist
Sie können dies mit is_file() tun.

3. Erstellen Sie es, wenn es nicht existiert
Verwenden Sie dazu Ihre aktuelle Größenänderungslogik, geben Sie das Bild jedoch nicht aus. Speichern Sie es an dem temporären Speicherort.

4. Lesen Sie den Inhalt der temporären Datei in den Stream
Ziemlich einfach ausgeben.

Hier ist ein ungetestetes Codebeispiel ...

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

Kurze Worte zu "Sicherheit"

Sie können Ihren Link einfach nicht sichern, wenn an keiner Stelle ein "geheimes Passwort" gespeichert ist: Solange die URI alle Informationen für den Zugriff auf Ihre Ressource enthält, ist sie decodierbar und Ihre "benutzerdefinierte Sicherheit" (das Gegenteil ist der Fall) Wörter btw) werden leicht gebrochen.

Sie können immer noch ein Salt in Ihren PHP Code setzen (wie $mysalt="....long random string..."), da ich bezweifle, dass Sie eine ewige Sicherheit wollen (ein solcher Ansatz ist schwach, weil Sie den $mysalt Wert nicht erneuern können, aber in Ihrem Fall klingen einige Jahre Sicherheit ausreichend, da ein Benutzer ohnehin ein Bild kaufen und es an einer anderen Stelle freigeben kann, wodurch einer Ihrer Sicherheitsmechanismen verletzt wird).

Wenn Sie über einen sicheren Mechanismus verfügen möchten, verwenden Sie einen bekannten Mechanismus (wie ihn ein Framework bieten würde) sowie einen Mechanismus für die Authentifizierung und die Verwaltung von Benutzerrechten (damit Sie wissen, wer nach Ihrem Image sucht und ob dies zulässig ist).

Sicherheit hat ihre Kosten. Wenn Sie sich die Anforderungen an Computer und Speicher nicht leisten möchten, vergessen Sie diese.


Sicher durch Signieren der URL

Wenn Sie vermeiden möchten, dass Benutzer einfach umgangen werden, und ein Bild in voller Auflösung erhalten möchten, können Sie einfach den URI signieren (verwenden Sie jedoch aus Sicherheitsgründen etwas, das bereits vorhanden ist, anstelle des folgenden Beispiels für einen Schnellentwurf):

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

Dekodierung:

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

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


Klug kürzen

"Verkürzen" mit einem generischen Komprimierungsalgorithmus ist hier nutzlos, da die Header länger als der URI sind und ihn daher so gut wie nie verkürzen.

Wenn Sie es verkürzen möchten, seien Sie schlau: Geben Sie den relativen Pfad (/dir/dir) nicht an, wenn er immer gleich ist (oder geben Sie ihn nur an, wenn er nicht der Hauptpfad ist). Geben Sie die Erweiterung nicht an, wenn sie immer gleich ist (oder geben Sie sie an, wenn sie nicht png ist, wenn sich fast alles in png befindet). Geben Sie die height nicht an, da das Bild den aspect ratio enthält: Sie benötigen nur die width. Geben Sie x100px ein, wenn Sie keine pixelgenaue Breite benötigen.

2
Xenos

Es gibt viele Möglichkeiten, um URLs zu kürzen. Sie können nachsehen, wie andere Dienste wie TinyUrl ihre URLs verkürzen. Hier ist ein guter Artikel über Hashes und das Verkürzen von URLs: http://blog.codinghorror.com/url-shortening-hashes-in-practice/

Sie können die PHP-Funktion mhash () verwenden, um Hashes auf Strings anzuwenden: http://php.net/manual/en/function.mhash.php

Und wenn Sie auf der mhash-Website nach unten zu "Available Hashes" scrollen, können Sie sehen, welche Hashes Sie in der Funktion verwenden können (obwohl ich prüfen würde, welche PHP-Versionen welche Funktionen haben): http://mhash.sourceforge.net /mhash.3.html

2
GreeKatrina

In Ihrer Frage geben Sie an, dass es sich um reines PHP handeln und keine Datenbank verwenden soll. Außerdem sollte die Möglichkeit bestehen, die Zeichenfolgen zu dekodieren. Also die Regeln ein bisschen biegen:

  • Die Art und Weise, wie ich diese Frage interpretiere, ist, dass uns die Sicherheit nicht so sehr am Herzen liegt, aber wir wollen die kürzesten Hashes, die zurück zu Bildern führen.
  • Wir können auch eine "Dekodierungsmöglichkeit" mit einer Prise Salz einnehmen, indem wir einen Einweg-Hashing-Algorithmus verwenden.
  • Wir können die Hashes in einem JSON-Objekt speichern und dann die Daten in einer Datei speichern. Alles, was wir am Ende des Tages tun müssen, ist der String-Abgleich

`` `

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

`` `

Anwendungsbeispiel:

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

Das Endresultat ist also eine Zeichenkette, die nur 32 Zeichen lang ist, was viel kürzer ist als die 52 , die wir vorher hatten.

1
Aron

Leider können Sie die Abfragezeichenfolge nicht besser verkürzen als mit einem bekannten Komprimierungsalgorithmus. Wie bereits erwähnt, ist eine komprimierte Version um einige (ca. 4-6) Zeichen kürzer als das Original. Darüber hinaus kann der ursprüngliche String relativ einfach dekodiert werden (im Gegensatz zum Dekodieren von sha1 oder md5).

Ich schlage vor, URLs mit Hilfe der Webserverkonfiguration zu kürzen. Sie können es weiter verkürzen, indem Sie den Bildpfad durch eine ID ersetzen (speichern Sie ID-Dateiname Paare in einer Datenbank).

Zum Beispiel akzeptiert die folgende Nginx Konfiguration URLs wie /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858, wobei

  • die erste Zahl (123456) soll eine Bild-ID aus der Datenbank sein.
  • 700 und 500 sind Bildabmessungen;
  • der letzte Teil ist ein MD5-Hash, der vor Anfragen mit unterschiedlichen Dimensionen schützt .
# 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; }
}

Der Server akzeptiert nur URLs mit dem angegebenen Muster, leitet die Anforderung an das /public/t/resize.php-Skript mit der geänderten Abfragezeichenfolge weiter und ändert dann die Größe des von PHP mit image_filter module generierten Abbilds. Im Fehlerfall wird ein leeres GIF-Bild zurückgegeben.

Der image_filter ist optional und dient nur als Beispiel. Die Größenänderung kann vollständig auf der Seite PHP durchgeführt werden. Mit Nginx ist es übrigens möglich, den Teil PHP loszuwerden.

Das Skript PHP soll den Hash wie folgt validieren:

// 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

Aus der Diskussion im Kommentarbereich geht hervor, dass Sie Ihre ursprünglichen hochauflösenden Bilder wirklich schützen möchten.

Vor diesem Hintergrund würde ich vorschlagen, dies zuerst mit Ihrer Webserverkonfiguration (z. B. Apache mod_authz_core oder Nginx ngx_http_access_module ) zu tun, um den Zugriff vom Web auf das Verzeichnis zu verweigern, in dem Ihre Originalbilder gespeichert sind.

Beachten Sie, dass der Server nur den Zugriff auf Ihre Bilder aus dem Web verweigert, Sie jedoch weiterhin direkt über Ihre PHP-Skripte darauf zugreifen können. Da Sie bereits Bilder mit einem "Resizer" -Skript anzeigen, würde ich vorschlagen, eine feste Grenze zu setzen und sich zu weigern, die Größe von Bildern auf etwas Größeres zu ändern (z. B. so etwas wie $width = min(1000, $_GET['w'])).

Ich weiß, dass dies Ihre ursprüngliche Frage nicht beantwortet, aber ich denke, dass dies die richtige Lösung wäre, um Ihre Bilder zu schützen. Und wenn Sie den ursprünglichen Namen und die Größenänderungsparameter trotzdem verschleiern möchten, können Sie dies nach Belieben tun, ohne sich Sorgen machen zu müssen, dass jemand herausfindet, was dahintersteckt.

0
Giedrius D

Sie sagen, dass Sie die Größe dort haben möchten, damit Sie, wenn Sie eines Tages entscheiden, dass die Vorschaubilder zu klein sind, die Größe erhöhen möchten. Hier besteht die Lösung darin, die Bildgröße fest in das PHP-Skript zu codieren und sie aus der URL zu entfernen . Wenn Sie die Größe in Zukunft ändern möchten, ändern Sie die fest codierten Werte im PHP-Skript (oder in einer config.php, die Sie in das Skript aufnehmen).

Sie haben auch angegeben, dass Sie bereits Dateien zum Speichern von Bilddaten als JSON-Objekt verwenden, z. B .: name, title, description. Wenn Sie dies ausnutzen, benötigen Sie keine Datenbank und können den JSON-Dateinamen als Schlüssel zum Nachschlagen der Bilddaten verwenden.

Wenn der Benutzer eine URL wie diese besucht:

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

Sie laden ax9v.json von dem Speicherort, an dem Sie die JSON-Dateien bereits gespeichert haben, und in dieser JSON-Datei wird der tatsächliche Pfad des Bildes gespeichert. Laden Sie dann das Bild, passen Sie die Größe an die fest codierte Größe in Ihrem Skript an und senden Sie es an den Benutzer.

Zeichnen Sie aus den Schlussfolgerungen in https://blog.codinghorror.com/url-shortening-hashes-in-practice/ , um den kleinsten Teil der URL zu erhalten, müssen Sie gültige Zeichenkombinationen als neu wiederholen Dateien werden hochgeladen (z. B. die erste ist "AAA", dann "AAB", "AAC" usw.), anstatt einen Hashing-Algorithmus zu verwenden. Ihre Lösung hätte dann nur 3 Zeichen in der Zeichenfolge für die ersten 238.328 Fotos, die Sie hochladen.

Ich hatte mit dem Prototypen einer PHP-Lösung auf Phpfiddle begonnen, aber der Code verschwand (verwende Phpfiddle nicht).

0
Luke

Theorie

Theoretisch benötigen wir einen kurzen Eingabezeichensatz und einen großen Ausgabezeichensatz. Ich werde es anhand des folgenden Beispiels demonstrieren. Wir haben die Nummer 2468 als Ganzzahl mit 10 Zeichen (0-9) als Zeichensatz. Wir können es mit der Basis 2 (binäres Zahlensystem) in dieselbe Zahl umwandeln. Dann haben wir einen kürzeren Zeichensatz (0 und 1) und das Ergebnis ist länger: 100110100100

Aber wenn wir in eine hexadezimale Zahl (Basis 16) mit einem Zeichensatz von 16 (0-9 und A-F) konvertieren. Dann erhalten wir ein kürzeres Ergebnis: 9A4

Trainieren

In Ihrem Fall haben wir also folgenden Zeichensatz für die Eingabe:

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

Insgesamt 41 Zeichen: Zahlen, Kleinbuchstaben und Sonderzeichen =/-. &

Der Zeichensatz für die Ausgabe ist etwas knifflig. Wir möchten nur URL-Speicherzeichen verwenden. Ich habe sie hier geholt: Zeichen im GET-Parameter erlaubt

Unser Ausgabezeichensatz ist also (73 Zeichen):

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

Zahlen, Groß- und Kleinschreibung und einige Sonderzeichen.

Wir haben mehr Zeichen in unserem Set für die Ausgabe als für die Eingabe. Die Theorie besagt, dass wir unsere Eingabezeichenfolge verkürzen können. PRÜFEN!

Codierung

Jetzt brauchen wir eine Codierungsfunktion von Basis 41 bis Basis 73. In diesem Fall kenne ich keine PHP -Funktion. Zum Glück können wir die Funktion 'convBase' hier herunterladen: http://php.net/manual/de/function.base-convert.php#106546 (falls jemand eine intelligentere Funktion kennt, lass es mich wissen)

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

Jetzt können wir die URL verkürzen. Der endgültige Code lautet:

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

Die codierte Zeichenfolge hat nur 34 Zeichen.

Optimierungen

Sie können die Anzahl der Zeichen optimieren, indem Sie

  • reduzieren Sie die Länge der Eingabezeichenfolge. Benötigen Sie wirklich den Overhead der URL-Parametersyntax? Vielleicht können Sie Ihre Zeichenfolge wie folgt formatieren:

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

    Dies reduziert die Eingabe selbst UND den Eingabezeichensatz. Ihr reduzierter Eingabezeichensatz ist dann:

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

    Endgültige Ausgabe:

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

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

  • den eingegebenen Zeichensatz verkleinern ;-). Vielleicht können Sie noch weitere Zeichen ausschließen? Sie können die Zahlen zuerst in Zeichen kodieren. Dann kann Ihr eingegebener Zeichensatz um 10 reduziert werden!

  • erhöhen Sie Ihren Ausgabezeichensatz. So wird das von mir vorgegebene Set innerhalb von 2 Minuten gegoogelt. Möglicherweise können Sie mehr URL-Speicherzeichen verwenden. Keine Ahnung ... Vielleicht hat jemand eine Liste

Sicherheit

Heads up: Der Code enthält keine kryptografische Logik. Wenn also jemand die Zeichensätze errät, kann er die Zeichenfolge leicht entschlüsseln. Sie können die Zeichensätze jedoch (einmal) mischen. Dann ist es für den Angreifer etwas schwieriger, aber nicht wirklich sicher. Vielleicht reicht es für Ihren Anwendungsfall trotzdem.

0
Timo

Es wurde viel darüber gesprochen, wie Codierung nicht zur Sicherheit beiträgt. Deshalb konzentriere ich mich nur auf die Verkürzung und Ästhetik.

Anstatt es als eine Zeichenfolge zu betrachten, könnten Sie es als 3 einzelne Komponenten betrachten. Wenn Sie dann den Code-Speicherplatz für jede Komponente begrenzen, können Sie die Dinge viel kleiner zusammenpacken.

Z.B.

  • Pfad - Besteht nur aus den 26 Zeichen (a-z) und/-. (Variable Länge)
  • width - Integer (0 - 65k) (feste Länge, 16 Bit)
  • height - Ganzzahl (0 - 65 KB) (feste Länge, 16 Bit)

Ich beschränke den Pfad auf maximal 31 Zeichen, damit wir 5-Bit-Gruppierungen verwenden können.

Packen Sie zuerst Ihre festen Längenmaße und hängen Sie jedes Pfadzeichen als 5 Bit an. Möglicherweise muss auch ein spezielles Nullzeichen hinzugefügt werden, um das Endbyte auszufüllen. Offensichtlich müssen Sie dieselbe Wörterbuchzeichenfolge für die Codierung und Decodierung verwenden.

Siehe Code unten.

Dies zeigt, dass Sie eine kürzere Zeichenfolge erhalten können, indem Sie einschränken, was Sie codieren und wie viel Sie codieren können. Sie können es sogar noch kürzer machen, indem Sie nur 12-Bit-Ganzzahlen verwenden (max. 2048) oder Teile des Pfads entfernen, wenn sie bekannt sind, wie z. B. Basispfad oder Dateierweiterung (siehe letztes Beispiel).

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

Ich denke nicht, dass die resultierende URL viel mehr verkürzt werden kann als in Ihrem eigenen Beispiel. Aber ich schlage ein paar Schritte vor, um Ihre Bilder besser zu verschleiern.

Zuerst würde ich alles, was Sie können, aus der Basis-URL entfernen, die Sie zippen und base64encoding, also statt

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

Ich würde ... benutzen

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

Waren diese letzten 16 Zeichen ein Hash, um zu überprüfen, ob die geöffnete URL mit der in Ihrem Code angegebenen übereinstimmt, und versucht der Benutzer nicht, das hochauflösende Image aus dem System herauszutricksen.

Ihre index.php, die die Bilder liefert, würde folgendermaßen beginnen:

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

Nach diesem Zeitpunkt können Sie das Bild senden, während der Benutzer, der es öffnet, Zugriff auf einen gültigen Link hatte.

Beachten Sie, dass die Verwendung von session_id als Teil der Rohzeichenfolge, mit der der Hash erstellt wird, optional ist, es den Benutzern jedoch unmöglich machen würde, eine gültige URL freizugeben, da dies eine Sitzungsbindung wäre. Wenn Sie möchten, dass die URLs gemeinsam genutzt werden können, entfernen Sie einfach session_id aus diesem Aufruf.

Ich würde die resultierende URL genauso verpacken, wie Sie es bereits getan haben, Zip + base64. Das Ergebnis wäre sogar noch größer als Ihre Version, aber schwieriger durch die Verschleierung zu sehen und Ihre Bilder daher vor nicht autorisierten Downloads zu schützen.

Wenn Sie es nur kürzer machen möchten, sehe ich keine Möglichkeit, dies ohne Umbenennen der Dateien (oder ihrer Ordner) oder ohne die Verwendung einer Datenbank zu tun.

Die vorgeschlagene Lösung für die Dateidatenbank führt mit Sicherheit zu Problemen bei der gleichzeitigen Verwendung - es sei denn, Sie haben immer keine oder nur sehr wenige Personen, die das System gleichzeitig verwenden.

0
Edelmar Ziegler