Hàm băm ngắn trong PHP giống như các trang web rút ngắn URL


83

Tôi đang tìm một hàm PHP tạo ra một hàm băm ngắn từ một chuỗi hoặc một tệp, tương tự như các trang web rút ngắn URL đó như tinyurl.com

Hàm băm không được dài hơn 8 ký tự.


2
Tôi biết đây là một câu hỏi cũ nhưng hãy kiểm tra: hashids.org . Hoạt động với hầu hết các ngôn ngữ lập trình
Greg

Kiểm tra thư viện Mã ngắn . Nó làm chính xác những gì bạn muốn. Dựa trên chuyển đổi cơ sở.
Anis

Khác so với sử dụng Adler-32 hoặc CRC32, bạn không thể rút ngắn băm hiện đại (collision-resistant) rằng nhiều (tức là xuống đến 8 ký tự). Không phải với SHA-2, không phải với SHA-1 và thậm chí không với MD5. Với Alphabet::convert($hash, Alphabet::HEX, Alphabet::ALPHANUMERIC), bạn có thể giảm MD5 xuống 22 (từ 32) ký tự. Thay vào đó, những gì bạn muốn là mã hóa ID số nguyên của tệp (ví dụ từ cơ sở dữ liệu của bạn) bằng (new Id())->encode($id).
caw

Câu trả lời:


47

Các dịch vụ rút ngắn URL thay vì sử dụng giá trị số nguyên tăng tự động (như ID cơ sở dữ liệu bổ sung) và mã hóa giá trị đó bằng Base64 hoặc các mã hóa khác để có nhiều thông tin hơn cho mỗi ký tự (64 thay vì chỉ 10 chữ số).


1
Điều này có nghĩa là gì (thêm thông tin cho mỗi nhân vật) chỉ là tò mò !!
ravisoni

2
@ravisoni Nếu bạn sử dụng các chữ số thập phân 0- 9để biểu diễn một số, bạn có 10 giá trị có thể có cho mỗi ký tự được mã hóa (ld (10) ≈ 3,32 bit / ký tự). Tuy nhiên, nếu bạn đại diện cho cùng một số với các ký tự Base64, bạn có 64 giá trị có thể có cho mỗi ký tự được mã hóa (ld (64) = 6 bit / ký tự). Vì vậy, với Base64 có nhiều thông tin hơn được lưu trữ trong mỗi ký tự được mã hóa, tức là, 6 bit thông tin thay vì 3,32 bit.
Gumbo

3
Nếu bạn sử dụng base64, thì không có gì ngăn tập lệnh nói cho ($ i = 0; $ i <999999; $ i ++) {$ pageContent = fread (fopen (' yoururl.com/'.base64_encode($i) );} và bây giờ tôi có quyền truy cập vào từng URL trong cơ sở dữ liệu của bạn.

161

TinyURL không băm bất cứ thứ gì, nó sử dụng số nguyên Cơ sở 36 (hoặc thậm chí là cơ số 62, sử dụng chữ thường và chữ hoa) để cho biết bản ghi nào sẽ truy cập.

Cơ số 36 đến Số nguyên:

intval($str, 36);

Số nguyên đến Cơ số 36:

base_convert($val, 10, 36);

Vì vậy, thay vì chuyển hướng đến một tuyến đường như /url/1234nó đã trở thành /url/axthay vào đó. Điều này mang lại cho bạn nhiều công dụng hơn so với hash will, vì sẽ không có va chạm. Với điều này, bạn có thể dễ dàng kiểm tra xem url có tồn tại hay không và trả về ID thích hợp, hiện có trong cơ sở 36 mà người dùng không biết rằng nó đã có trong cơ sở dữ liệu.

Đừng băm, hãy sử dụng các cơ sở khác cho loại điều này. (Nó nhanh hơn và có thể chống va chạm.)


chào @RobertK, PHP sẽ tìm cách chuyển đổi chuỗi 6 chữ số bao gồm cả số và chữ cái như thế nào?
tim peterson

@timpeterson, chỉ cần gọi intval và chuyển vào cơ sở đã cho (xem khối mã đầu tiên của tôi).
Robert K,

@RobertK, nhưng intval()biến mọi thứ thành một con số. Tôi đoán có thể tôi đang nhầm lẫn về cách intval()kết nối với các bước khác cần thiết để thực hiện chuyển hướng như vai trò của cơ sở dữ liệu.
tim peterson

@timpeterson, đó là vì chuỗi đại diện cho ID của bản ghi cơ sở dữ liệu. Vì vậy, bạn chọn bản ghi dựa trên ID đã chuyển.
Robert K,

@RobertK, một vấn đề mà tôi đang gặp intval()là nếu của bạn $strcó dấu gạch chéo (/) hoặc dấu gạch ngang (-) trong đó. Tôi nhận ra điều đó on/stuff, on-stuffontất cả đều trả lại số 887. Bạn có giải pháp nào để làm việc với các URL có dấu gạch chéo và dấu gạch ngang trong đó không?
tim peterson

83

Tôi đã viết một lib nhỏ để tạo các băm bị xáo trộn từ các số nguyên.

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/7/2015: Thêm mã thực bên dưới, vì nó trở nên khó tìm:

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

}

12
này có một thiết kế rất thông minh = D số nguyên tố vàng = world.rock ()
sova

3
Tôi biết tôi đang bình luận về một bài viết cũ hơn. Tôi nghĩ rằng tôi sẽ đề cập rằng mã KevBurnsJr hoạt động tốt. Tuy nhiên, gần đây tôi vừa chuyển từ máy chủ Windows 2003 32 bit sang máy chủ Windows 2008 R2 x64 và tôi nhận thấy rằng mình đang sao chép hàm băm duy nhất. Bây giờ tôi đang phải tìm một phương pháp thay thế để tạo mã xác nhận.
DanielJay

2
Bài đã được cập nhật để sử dụng bcmath với sự giúp đỡ của một số người bình luận nên bây giờ nó chắc chắn. Ai đó cũng đã tìm ra một cách để làm cho nó có thể đảo ngược hoàn toàn là dope.
KevBurnsJr

2
web.archive.org/web/20130727034425/http://blog.kevburnsjr.com/… có vẻ như trang web không hoạt động nên đây là bản sao của liên kết đó;)
Harinder

4
Bạn nên khôi phục trang web của mình hoặc đăng phiên bản php của cái này dưới github.com/KevBurnsJr/pseudocrypt - thật tuyệt vời! Không muốn sử dụng một "hệ thống" khổng lồ như YOURLS hoặc PHURL, chỉ cần một lib tốt để tạo các liên kết ngắn và đây là nó. Cảm ơn
inorganik

21

Hàm băm ngắn nhất có độ dài 32 ký tự, bao giờ bạn có thể sử dụng 8 ký tự đầu tiên của mã băm md5

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

Cập nhật : đây là một lớp khác được tìm thấy ở đây được viết bởi Travell Perkins mà mất con số kỷ lục và tạo ra băm viết tắt của nó. 14 chữ số tạo ra chuỗi 8 chữ số. Đến ngày bạn đạt đến con số này, bạn trở nên nổi tiếng hơn 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)));
    }
}

đây là ví dụ về cách sử dụng nó:

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

32
Sử dụng 8 charactors đầu tiên của md5 có lẽ là một cơ hội hợp lý trong hai URL có cùng bảng băm
Tom Haigh

2
Có, xung đột như vậy có thể xảy ra, nhưng cơ hội là rất ít đối với chuỗi ngẫu nhiên, nó là khoảng một đến 4 tỷ, bao giờ bạn muốn có 100% hàm băm duy nhất mà bạn có thể sử dụng làm tham chiếu đến bản ghi cơ sở dữ liệu sử dụng lớp bao gồm.
Nazariy

2
muốn đề cập đến rằng const codesetcó thể trong bất kỳ thứ tự tùy ý, chỉ để xáo trộn ++
Luis Siquot

3

Đối với một mã băm ngắn , thân thiện với url , trong quan điểm không cho phép nội dung trùng lặp có thể xảy ra, chúng tôi có thể sử dụng hash()và đặc biệt là loại băm CRC , vì nó được tạo chính xác cho điều đó:

Kiểm tra dự phòng theo chu kỳ

Kiểm tra dự phòng theo chu kỳ (CRC) là một mã phát hiện lỗi thường được sử dụng trong các mạng kỹ thuật số và thiết bị lưu trữ để phát hiện những thay đổi ngẫu nhiên đối với dữ liệu thô. Các khối dữ liệu đi vào các hệ thống này được đính kèm một giá trị kiểm tra ngắn, dựa trên phần còn lại của một phép chia đa thức cho nội dung của chúng. Khi truy xuất, phép tính được lặp lại và trong trường hợp các giá trị kiểm tra không khớp, có thể thực hiện hành động khắc phục

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

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

2

Câu trả lời hay nhất chưa từng có: Chuỗi "Hash Like" nhỏ nhất duy nhất được cung cấp ID cơ sở dữ liệu duy nhất - Giải pháp PHP, Không cần thư viện bên thứ ba.

Đây là mã:

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

Trên thực tế, giải pháp tốt nhất để có băm "ngẫu nhiên" là tạo danh sách băm ngẫu nhiên, đặt nó trên Mysql với một INDEX duy nhất (bạn có thể viết một UDF đơn giản để chèn 100 000 hàng trong 1 giây).

Tôi nghĩ cấu trúc như thế này là ID | HASH | STATUS | URL | VIEWS | ......

Trạng thái ở đâu cho biết Hash này là miễn phí hay không.


0

Cách dễ dàng với kiểm tra trùng lặp trong Cơ sở dữ liệu:

$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

Tôi đang tạo một công cụ rút gọn url. Trong trường hợp của tôi, tôi đã sử dụng "id" của cơ sở dữ liệu để tạo mỗi lần một url ngắn duy nhất.

Những gì tôi đã làm là, đầu tiên -

Chèn Dữ liệu như "Url gốc" và "ngày tạo" trong db để trống "url ngắn" trong db. Sau đó lấy "id" từ đó và chuyển vào hàm bên dưới.

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

?>

Và sau đó CẬP NHẬT giá trị của Mã url ngắn trong Cơ sở dữ liệu.

Ở đây tôi đang sử dụng "id" để tạo mã ngắn. Vì ID không thể giống nhau cho nhiều mục nhập. Nó là duy nhất do đó Mã hoặc Url duy nhất sẽ là duy nhất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.