PHP Short Hash wie URL-verkürzende Websites

83

Ich suche nach einer PHP-Funktion, die aus einem String oder einer Datei einen kurzen Hash erstellt, ähnlich wie Websites mit URL-Verkürzung wie tinyurl.com

Der Hash sollte nicht länger als 8 Zeichen sein.

Klemme
quelle
2
Ich weiß, dass dies eine alte Frage ist, aber check out: hashids.org . Funktioniert mit den meisten Programmiersprachen
Greg
Überprüfen Sie die ShortCode- Bibliothek. Es macht genau das, was Sie wollen. Basierend auf Basisumwandlung.
Anis
Andere als die Verwendung von Adler-32 oder CRC32, können Sie nicht modern verkürzen (kollisionsresistent) Hashes , dass viel (dh 8 Zeichen nach unten). Nicht mit SHA-2, nicht mit SHA-1 und nicht einmal mit MD5. Mit Alphabet::convert($hash, Alphabet::HEX, Alphabet::ALPHANUMERIC)können Sie MD5 auf 22 (von 32) Zeichen reduzieren. Sie möchten stattdessen die Ganzzahl-IDs der Dateien (z. B. aus Ihrer Datenbank) mit codieren (new Id())->encode($id).
Caw

Antworten:

47

URL-Verkürzungsdienste verwenden eher einen automatisch inkrementierten ganzzahligen Wert (wie eine zusätzliche Datenbank-ID) und codieren diesen mit Base64 oder anderen Codierungen, um mehr Informationen pro Zeichen zu erhalten (64 statt nur 10 wie Ziffern).

Gumbo
quelle
1
Was das bedeutet (mehr Informationen pro Charakter) nur neugierig !!
Ravisoni
2
@ravisoni Wenn Sie die Dezimalstellen verwenden 0- 9eine Zahl darzustellen, Sie haben 10 mögliche Werte pro codierten Zeichen (ld (10) ≈ 3,32 Bits / Zeichen). Wenn Sie jedoch dieselbe Zahl mit Base64-Zeichen darstellen, haben Sie 64 mögliche Werte pro codiertem Zeichen (ld (64) = 6 Bit / Zeichen). Bei Base64 sind also mehr Informationen in jedem codierten Zeichen gespeichert, dh 6 Informationsbits anstelle von 3,32 Bit.
Gumbo
3
Wenn Sie base64 verwenden, hindert nichts ein Skript daran, für ($ i = 0; $ i <999999; $ i ++) {$ pageContent = fread (fopen (' yoururl.com/'.base64_encode($i) );} zu sagen und jetzt habe ich Zugriff auf jede einzelne URL in Ihrer Datenbank.
161

TinyURL hasht nichts, sondern verwendet Ganzzahlen der Basis 36 (oder sogar Basis 62 mit Klein- und Großbuchstaben), um anzugeben, welcher Datensatz besucht werden soll.

Basis 36 bis Ganzzahl:

intval($str, 36);

Ganzzahl zur Basis 36:

base_convert($val, 10, 36);

Also, anstatt zu einer Route umzuleiten, wie /url/1234es /url/axstattdessen wird. Dies gibt Ihnen viel mehr Nutzen als ein Hash, da es keine Kollisionen gibt. Auf diese Weise können Sie leicht überprüfen, ob eine URL vorhanden ist, und die richtige, vorhandene ID in Basis 36 zurückgeben, ohne dass der Benutzer weiß, dass sie bereits in der Datenbank vorhanden ist.

Hasch nicht, benutze andere Basen für solche Dinge. (Es ist schneller und kann kollisionssicher gemacht werden.)

Robert K.
quelle
hi @RobertK, wie würde das PHP aussehen, um 6-stellige Zeichenfolgen zu konvertieren, die sowohl Zahlen als auch Buchstaben enthalten?
Tim Peterson
@timpeterson, rufe einfach intval auf und übergebe die angegebene Basis (siehe meinen ersten Codeblock).
Robert K
@RobertK, intval()verwandelt aber alles in eine Zahl. Ich bin vielleicht verwirrt darüber, wie die intval()Verbindung zu den anderen Schritten hergestellt wird, die für die Umleitung erforderlich sind, wie z. B. die Rolle der Datenbank.
Tim Peterson
@timpeterson, das liegt daran, dass die Zeichenfolge die ID des Datenbankeintrags darstellt. Sie wählen den Datensatz also anhand der übergebenen ID aus.
Robert K
@RobertK, das einzige Problem, mit dem ich sehe, intval()ist, wenn $stres Schrägstriche (/) oder Bindestriche (-) enthält. Ich erkannte , dass on/stuff, on-stuffund die onganze Zahl zurückgegeben 887. Haben Sie eine Lösung für die Arbeit mit URLs mit Schrägstrichen und Bindestrichen?
Tim Peterson
83

Ich habe eine winzige Bibliothek geschrieben, um verschleierte Hashes aus ganzen Zahlen zu generieren.

http://web.archive.org/web/20130727034425/http://blog.kevburnsjr.com/php-unique-hash

$ids = range(1,10);
foreach($ids as $id) {
  echo PseudoCrypt::unhash($id) . "\n";
}
m8z2p
8hy5e
uqx83
gzwas
38vdh
phug6
bqtiv
xzslk
k8ro9
6hqqy

14.07.2015: Hinzufügen des aktuellen Codes unten, da es schwierig geworden ist, Folgendes zu finden:

<?php
/**
 * PseudoCrypt by KevBurns (http://blog.kevburnsjr.com/php-unique-hash)
 * Reference/source: http://stackoverflow.com/a/1464155/933782
 * 
 * I want a short alphanumeric hash that’s unique and who’s sequence is difficult to deduce. 
 * I could run it out to md5 and trim the first n chars but that’s not going to be very unique. 
 * Storing a truncated checksum in a unique field means that the frequency of collisions will increase 
 * geometrically as the number of unique keys for a base 62 encoded integer approaches 62^n. 
 * I’d rather do it right than code myself a timebomb. So I came up with this.
 * 
 * Sample Code:
 * 
 * echo "<pre>";
 * foreach(range(1, 10) as $n) {
 *     echo $n." - ";
 *     $hash = PseudoCrypt::hash($n, 6);
 *     echo $hash." - ";
 *     echo PseudoCrypt::unhash($hash)."<br/>";
 * }
 * 
 * Sample Results:
 * 1 - cJinsP - 1
 * 2 - EdRbko - 2
 * 3 - qxAPdD - 3
 * 4 - TGtDVc - 4
 * 5 - 5ac1O1 - 5
 * 6 - huKpGQ - 6
 * 7 - KE3d8p - 7
 * 8 - wXmR1E - 8
 * 9 - YrVEtd - 9
 * 10 - BBE2m2 - 10
 */

class PseudoCrypt {

    /* Key: Next prime greater than 62 ^ n / 1.618033988749894848 */
    /* Value: modular multiplicative inverse */
    private static $golden_primes = array(
        '1'                  => '1',
        '41'                 => '59',
        '2377'               => '1677',
        '147299'             => '187507',
        '9132313'            => '5952585',
        '566201239'          => '643566407',
        '35104476161'        => '22071637057',
        '2176477521929'      => '294289236153',
        '134941606358731'    => '88879354792675',
        '8366379594239857'   => '7275288500431249',
        '518715534842869223' => '280042546585394647'
    );

    /* Ascii :                    0  9,         A  Z,         a  z     */
    /* $chars = array_merge(range(48,57), range(65,90), range(97,122)) */
    private static $chars62 = array(
        0=>48,1=>49,2=>50,3=>51,4=>52,5=>53,6=>54,7=>55,8=>56,9=>57,10=>65,
        11=>66,12=>67,13=>68,14=>69,15=>70,16=>71,17=>72,18=>73,19=>74,20=>75,
        21=>76,22=>77,23=>78,24=>79,25=>80,26=>81,27=>82,28=>83,29=>84,30=>85,
        31=>86,32=>87,33=>88,34=>89,35=>90,36=>97,37=>98,38=>99,39=>100,40=>101,
        41=>102,42=>103,43=>104,44=>105,45=>106,46=>107,47=>108,48=>109,49=>110,
        50=>111,51=>112,52=>113,53=>114,54=>115,55=>116,56=>117,57=>118,58=>119,
        59=>120,60=>121,61=>122
    );

    public static function base62($int) {
        $key = "";
        while(bccomp($int, 0) > 0) {
            $mod = bcmod($int, 62);
            $key .= chr(self::$chars62[$mod]);
            $int = bcdiv($int, 62);
        }
        return strrev($key);
    }

    public static function hash($num, $len = 5) {
        $ceil = bcpow(62, $len);
        $primes = array_keys(self::$golden_primes);
        $prime = $primes[$len];
        $dec = bcmod(bcmul($num, $prime), $ceil);
        $hash = self::base62($dec);
        return str_pad($hash, $len, "0", STR_PAD_LEFT);
    }

    public static function unbase62($key) {
        $int = 0;
        foreach(str_split(strrev($key)) as $i => $char) {
            $dec = array_search(ord($char), self::$chars62);
            $int = bcadd(bcmul($dec, bcpow(62, $i)), $int);
        }
        return $int;
    }

    public static function unhash($hash) {
        $len = strlen($hash);
        $ceil = bcpow(62, $len);
        $mmiprimes = array_values(self::$golden_primes);
        $mmi = $mmiprimes[$len];
        $num = self::unbase62($hash);
        $dec = bcmod(bcmul($num, $mmi), $ceil);
        return $dec;
    }

}
KevBurnsJr
quelle
12
Dies hat ein sehr intelligentes Design = D goldene Primzahlen = world.rock ()
sova
3
Ich weiß, dass ich einen älteren Beitrag kommentiere. Ich dachte, ich würde erwähnen, dass KevBurnsJr-Code gut funktioniert. Ich habe jedoch kürzlich von einem Windows 2003 32-Bit-Server zu einem Windows 2008 R2 x64-Server gewechselt und stelle fest, dass ich die eindeutigen Hashs dupliziere. Ich muss jetzt eine alternative Methode zum Erstellen von Bestätigungscodes finden.
DanielJay
2
Der Beitrag wurde aktualisiert, um bcmath mit Hilfe einiger Kommentatoren zu verwenden, sodass er jetzt solide sein sollte. Jemand hat auch einen Weg gefunden, es reversibel zu machen, was völlig doof ist.
KevBurnsJr
2
web.archive.org/web/20130727034425/http://blog.kevburnsjr.com/… es scheint, dass die Website nicht verfügbar ist, also hier ist die Kopie dieses Links;)
Harinder
4
Sie sollten Ihre Website wiederherstellen oder die PHP-Version davon unter github.com/KevBurnsJr/pseudocrypt veröffentlichen - was für eine großartige kleine Bibliothek! Ich wollte kein riesiges "System" wie YOURLS oder PHURL verwenden, nur eine nette Bibliothek, um Shortlinks zu erstellen, und das ist es. Danke
anorganik
21

Der kürzeste Hash hat eine Länge von 32 Zeichen. Sie können jedoch die ersten 8 Zeichen des MD5-Hash verwenden

echo substr(md5('http://www.google.com'), 0, 8);

Update : hier ist eine andere Klasse gefunden hier durch schriftliche Travell Perkins , die Rekordzahl nimmt und erstellen kurze Hash dafür. Die 14-stellige Nummer erzeugt eine 8-stellige Zeichenfolge. Ab dem Datum, an dem Sie diese Zahl erreichen, werden Sie beliebter als tinyurl;)

class BaseIntEncoder {

    //const $codeset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    //readable character set excluded (0,O,1,l)
    const codeset = "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ";

    static function encode($n){
        $base = strlen(self::codeset);
        $converted = '';

        while ($n > 0) {
            $converted = substr(self::codeset, bcmod($n,$base), 1) . $converted;
            $n = self::bcFloor(bcdiv($n, $base));
        }

        return $converted ;
    }

    static function decode($code){
        $base = strlen(self::codeset);
        $c = '0';
        for ($i = strlen($code); $i; $i--) {
            $c = bcadd($c,bcmul(strpos(self::codeset, substr($code, (-1 * ( $i - strlen($code) )),1))
                    ,bcpow($base,$i-1)));
        }

        return bcmul($c, 1, 0);
    }

    static private function bcFloor($x)
    {
        return bcmul($x, '1', 0);
    }

    static private function bcCeil($x)
    {
        $floor = bcFloor($x);
        return bcadd($floor, ceil(bcsub($x, $floor)));
    }

    static private function bcRound($x)
    {
        $floor = bcFloor($x);
        return bcadd($floor, round(bcsub($x, $floor)));
    }
}

Hier ist ein Beispiel für die Verwendung:

BaseIntEncoder::encode('1122344523');//result:3IcjVE
BaseIntEncoder::decode('3IcjVE');//result:1122344523
Nazariy
quelle
32
Bei Verwendung der ersten 8 Zeichen von md5 besteht wahrscheinlich eine vernünftige Wahrscheinlichkeit, dass zwei URLs denselben Hash haben
Tom Haigh
2
Ja, eine solche Kollision kann auftreten, aber die Wahrscheinlichkeit für zufällige Zeichenfolgen ist sehr gering. Sie beträgt etwa 1 bis 4 Milliarden. Wenn Sie jedoch einen 100% eindeutigen Hash haben möchten, den Sie als Referenz für die inkludierte Datenbankdatensatzklasse verwenden können.
Nazariy
2
const codeset
Ich
3

Für einen kurzen Hash , url freundlich , im Hinblick auf mögliche doppelte Inhalte nicht anzuerkennen, können wir verwenden , hash()und vor allem des CRC - Hash - Typ, da es genau gemacht hat dafür:

Zyklische Redundanzprüfung

Eine zyklische Redundanzprüfung (CRC) ist ein Fehlererkennungscode, der üblicherweise in digitalen Netzwerken und Speichergeräten verwendet wird, um versehentliche Änderungen an Rohdaten zu erkennen. Datenblöcke, die in diese Systeme eingegeben werden, erhalten einen kurzen Prüfwert, der auf dem Rest einer Polynomaufteilung ihres Inhalts basiert. Beim Abrufen wird die Berechnung wiederholt, und falls die Prüfwerte nicht übereinstimmen, können Korrekturmaßnahmen ergriffen werden

https://en.wikipedia.org/wiki/Cyclic_redundancy_check

echo hash("crc32", "Content of article...");
// Output fd3e7c6e
NVRM
quelle
2

Beste Antwort bisher: Kleinste eindeutige "Hash-ähnliche" Zeichenfolge bei eindeutiger Datenbank-ID - PHP-Lösung, keine Bibliotheken von Drittanbietern erforderlich.

Hier ist der Code:

<?php
/*
THE FOLLOWING CODE WILL PRINT:
A database_id value of 200 maps to 5K
A database_id value of 1 maps to 1
A database_id value of 1987645 maps to 16LOD
*/
$database_id = 200;
$base36value = dec2string($database_id, 36);
echo "A database_id value of $database_id maps to $base36value\n";
$database_id = 1;
$base36value = dec2string($database_id, 36);
echo "A database_id value of $database_id maps to $base36value\n";
$database_id = 1987645;
$base36value = dec2string($database_id, 36);
echo "A database_id value of $database_id maps to $base36value\n";

// HERE'S THE FUNCTION THAT DOES THE HEAVY LIFTING...
function dec2string ($decimal, $base)
// convert a decimal number into a string using $base
{
    //DebugBreak();
   global $error;
   $string = null;

   $base = (int)$base;
   if ($base < 2 | $base > 36 | $base == 10) {
      echo 'BASE must be in the range 2-9 or 11-36';
      exit;
   } // if

   // maximum character string is 36 characters
   $charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

   // strip off excess characters (anything beyond $base)
   $charset = substr($charset, 0, $base);

   if (!ereg('(^[0-9]{1,50}$)', trim($decimal))) {
      $error['dec_input'] = 'Value must be a positive integer with < 50 digits';
      return false;
   } // if

   do {
      // get remainder after dividing by BASE
      $remainder = bcmod($decimal, $base);

      $char      = substr($charset, $remainder, 1);   // get CHAR from array
      $string    = "$char$string";                    // prepend to output

      //$decimal   = ($decimal - $remainder) / $base;
      $decimal   = bcdiv(bcsub($decimal, $remainder), $base);

   } while ($decimal > 0);

   return $string;

}

?>
John Erck
quelle
1

Die beste Lösung für "zufälligen" Hash besteht darin, eine Liste mit zufälligen Hashs zu erstellen und diese mit einem eindeutigen INDEX auf MySQL zu stellen (Sie können eine einfache UDF schreiben, um 100 000 Zeilen in 1 Sekunde einzufügen).

Ich denke eine Struktur wie diese ID | HASH | STATUS | URL | VIEWS | ......

Wobei der Status angibt, ob dieser Hash frei ist oder nicht.

Thomas Decaux
quelle
0

Einfacher Weg mit doppelter Prüfung in der Datenbank:

$unique = false;

// While will be repeated until we get unique hash
while($unique == false) {

    // Getting full hash based on random numbers
    $full_hash = base64_encode( rand(9999,999999) ); 

    // Taking only first 8 symbols
    $hash = substr($full_hash, 0, 8); 

    // Checking for duplicate in Database - Laravel SQL syntax
    $duplicate = \App\Item::where('url', $hash)->count(); 

    // If no Duplicate, setting Hash as unique
    if ($duplicate==0) {

        // For stoping while
        $unique=true;

        // New Hash is confirmed as unique
        $input['url']=$hash; 
    }
}
Gediminas
quelle
0

Ich habe eine URL kürzer gemacht. In meinem Fall habe ich die "ID" der Datenbank verwendet, um jedes Mal eine eindeutige kurze URL zu erstellen.

Was ich getan habe ist, zuerst -

Fügen Sie Daten wie "Original-URL" und "Erstellungsdatum" in die Datenbank ein und lassen Sie die "kurze URL" in der Datenbank leer. Holen Sie sich dann die "ID" von dort und übergeben Sie die unten stehende Funktion.

<?php
    function genUniqueCode($id){
    $id = $id + 100000000000;
    return base_convert($id, 10, 36);
}

//Get Unique Code using ID
/*
id Below is retrived from Database after Inserting Original URL.
*/



$data['id'] =10;
$uniqueCode = genUniqueCode($data['id']);

   // Generating the URL
$protocol = strtolower(substr($_SERVER["SERVER_PROTOCOL"],0,5))=='https'?'https':'http';
echo "<a href='{$protocol}://{$_SERVER['HTTP_HOST']}/{$uniqueCode}'>{$protocol}://{$_SERVER['HTTP_HOST']}/{$uniqueCode}</a>";

?>

Und dann UPDATE-Wert des Kurz-URL-Codes in der Datenbank.

Hier verwende ich "id", um einen Kurzcode zu erstellen. Da ID für Mehrfacheinträge nicht identisch sein kann. Es ist einzigartig, daher ist der eindeutige Code oder die eindeutige URL eindeutig.

Lazycipher
quelle