web-dev-qa-db-fra.com

PHP hachage court comme les sites Web raccourcissant

Je recherche une fonction PHP qui crée un hachage court dans une chaîne ou un fichier, similaire aux sites Web à raccourcissement d’URL tels que tinyurl.com

Le hachage ne doit pas dépasser 8 caractères.

77
clamp

Les services de raccourcissement d'URL utilisent plutôt une valeur entière incrémentée automatiquement (comme un identifiant de base de données supplémentaire) et l'encodent avec Base64 ou d'autres codages pour avoir plus d'informations par caractère (64 au lieu de 10).

44
Gumbo

TinyURL ne fait rien de hachage, il utilise des entiers en base 36 (ou même en base 62, en lettres minuscules et majuscules) pour indiquer quel enregistrement visiter.

Base 36 à Integer:

intval($str, 36);

Entier à la base 36:

base_convert($val, 10, 36);

Ainsi, au lieu de rediriger vers une route comme /url/1234, il devient plutôt /url/ax. Cela vous donne beaucoup plus d’utilisation qu’un hasch, car il n’y aura pas de collision. Avec cela, vous pouvez facilement vérifier si une URL existe et renvoyer le bon ID existant en base 36 sans que l'utilisateur sache qu'il était déjà dans la base de données.

Ne pas hacher, utilisez d'autres bases pour ce genre de chose. (C'est plus rapide et peut être protégé contre les collisions.)

147
Robert K

J'ai écrit une petite bibliothèque pour générer des hachages obfusqués à partir d'entiers.

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 

14/07/2015: Ajout du code réel ci-dessous, car il est devenu difficile à trouver:

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

}
75
KevBurnsJr

Le hachage le plus court est de 32 caractères, mais vous pouvez toujours utiliser les 8 premiers caractères du hachage md5

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

Update: voici une autre classe trouvée ici écrite par Travell Perkins qui prend un numéro d'enregistrement et crée un hachage court pour celui-ci. Le nombre de 14 chiffres produit une chaîne de 8 chiffres. À la date où vous atteignez ce nombre, vous devenez plus populaire que 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)));
    }
}

voici un exemple d'utilisation:

BaseIntEncoder::encode('1122344523');//result:3IcjVE
BaseIntEncoder::decode('3IcjVE');//result:1122344523
17
Nazariy

Meilleure réponse à ce jour: Chaîne la plus petite "Chaîne de hachages unique" avec un ID de base de données unique - Solution PHP, aucune bibliothèque tierce requise.

Voici le 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;

}

?>
1
John Erck

Moyen facile avec la vérification des doublons dans la base de données:

$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; 
    }
}
0
Gediminas

Je faisais un shortner d'URL. Dans mon cas, j'ai utilisé le "id" de la base de données pour créer à chaque fois une URL courte unique.

Ce que j'ai fait, c'est d'abord -

Insérer des données telles que "URL originale" et "date de création" dans la base de données en laissant la "URL courte" vide dans la base de données. Ensuite, obtenez le "id" à partir de là et passez la fonction ci-dessous.

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

?>

Et puis UPDATE valeur de Short Url Code in Database.

Ici, j'utilise "id" pour créer un code court. Depuis ID ne peut pas être identique pour plusieurs entrées. C'est unique, donc le code unique ou l'URL sera unique.

0
lazycipher

En fait, la meilleure solution pour avoir un hachage "aléatoire" est de générer une liste de hachage aléatoire, mettez-la sur Mysql avec un INDEX unique (vous pouvez écrire un simple UDF pour insérer 100 000 lignes en 1 seconde).

Je pense une structure comme celle-ci ID | HASH | STATUS | URL | VIEWS | ......

Où status indique si ce hachage est libre ou non.

0
Thomas Decaux