Random uniek id genereren

Gepost door Enrico Pallazzo op 27-03-2010 12:33.

====[INLEIDING]====
Je herkent dit soort links vast wel: http://www.youtube.com/watch?v=5tDDpNW00iw

Hierin zit het unieke id van de video, namelijk: "5tDDpNW00iw". Maar hoe maak je zulke unieke id's nou eigenlijk?

====[WERKING SCRIPT]====
Dit script maakt het mogelijk een random uniek id te generen op basis van elk getal van 1 t/m 9007199254740992, bijvoorbeeld het unieke id van een rij in je database :-) (Oftewel, je hoeft niet te controleren of het nieuw aangemaakte unieke id daadwerkelijk uniek is.)

Daarnaast kan dit script het "random unieke id" ook weer eenvoudig converteren naar het oorspronkelijke id met de toevoeging: true.

Voorbeeld:

<?php
rand_uniqid(9007199254740989);
// heeft als returnwaarde: 'PpQXn7COf'

rand_uniqid('PpQXn7COf', true);
// heeft als returnwaarde: '9007199254740989'
?>

====[MINIMALE STRINGLENGTE]====
Wil je dat het random unieke id minimaal uit 3 tekens bestaat, gebruik dan: $pad_up = 3

====[WAAROM DIT SCRIPT]====
Omdat je je bezoekers niet wilt laten weten hoeveel rijen je in je database hebt.

Wanneer een bezoeker de volgende pagina ziet:
www.site.nl/user/235

Dan is het niet moeilijk te raden dat er ook een user 234 en misschien wel 236 is. En omdat de administrator vaak als eerste aangemaakt wordt is de kans groot dat die het userid 1 heeft. Niet handig dus.

====[rand_uniqid() VS uniqid()]====
Waarom niet uniqid() gebruiken? Omdat uniqid(), voor zover mij bekend, onmogelijk te koppelen is aan de autoincrement (database).

Met uniqid() loop je tevens de kans dat het nummer toch niet uniek in de database komt te staan (kans is astronomisch klein maar toch aanwezig).

Verder geeft uniqid() een string van 13 of 23 characters.
En "PpQXn7COf" lijkt mij beter dan "4ba67f7b18450" of "4ba67fa3d28c79.92212865"

====[BEVEILIGING]====
Je kan de volgorde van de tekens in $index aanpassen om het lastiger voor hackers te maken om een lookup script te bouwen. Je moet hiervoor een password maken maar dat is optioneel.

====[TODO]====
- Het toevoegen van een prefix en/of suffix, al dan niet random of gedefinieerd.

====[UPDATES]====
1. Tekens verwijderd uit $index: a e o u i s A E O U I S. Hiermee voorkom je dat je bijvoorbeeld je klanten een infaam en abject uniek id geeft. Denk maar eens aan wat het voor je business kan betekenen als één van je nieuwe klanten zich afvraag waarom zijn gebruikersnaam: 'penis' is.

2. Voor de liefhebbers: de OOP versie van het script: http://www.phpfreakz.nl/library.php?sid=27601

3. Voor de liefhebbers: de JS versie van het script: http://www.phpfreakz.nl/library.php?sid=27602

4. Voor de liefhebbers: de PostgreSQL versie van het script: http://www.phpfreakz.nl/library.php?sid=27606 (Met dank aan: Dieter Wijnen)

Bestanden van dit script

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
function rand_uniqid($in, $to_num = false, $pad_up = false, $passKey = null)
{
    $index = "bcdfghjklmnpqrtvwxyz_-0123456789BCDFGHJKLMNPQRTVWXYZ";
    if ($passKey !== null) {
        // Although this function's purpose is to just make the
        // ID short - and not so much secure,
        // you can optionally supply a password to make it harder
        // to calculate the corresponding numeric ID
        
        for ($n = 0; $n<strlen($index); $n++) {
            $i[] = substr( $index,$n ,1);
        }
 
        $passhash = hash('sha256',$passKey);
        $passhash = (strlen($passhash) < strlen($index))
            ? hash('sha512',$passKey)
            : $passhash;
 
        for ($n=0; $n < strlen($index); $n++) {
            $p[] =  substr($passhash, $n ,1);
        }
        
        array_multisort($p,  SORT_DESC, $i);
        $index = implode($i);
    }
 
    $base  = strlen($index);
 
    if ($to_num) {
        // Digital number  <<--  alphabet letter code
        $in  = strrev($in);
        $out = 0;
        $len = strlen($in) - 1;
        for ($t = 0; $t <= $len; $t++) {
            $bcpow = bcpow($base, $len - $t);
            $out   = $out + strpos($index, substr($in, $t, 1)) * $bcpow;
        }
 
        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $out -= pow($base, $pad_up);
            }
        }
        $out = sprintf('%F', $out);
        $out = substr($out, 0, strpos($out, '.'));
    } else { 
        // Digital number  -->>  alphabet letter code
        if (is_numeric($pad_up)) {
            $pad_up--;
            if ($pad_up > 0) {
                $in += pow($base, $pad_up);
            }
        }
 
        $out = "";
        for ($t = floor(log($in, $base)); $t >= 0; $t--) {
            $bcp = bcpow($base, $t);
            $a   = floor($in / $bcp) % $base;
            $out = $out . substr($index, $a, 1);
            $in  = $in - ($a * $bcp);
        }
        $out = strrev($out); // reverse
    }
 
    return $out;
}
?>

Commentaar

22-03-2010 21:08

Geniaal, bedankt!

1
2
3
Post hier de source-code van je script. Alle informatie tussen <? ... ?> en <?php ... ?> zal automatisch worden getoond in color-coding. 

Let op! Het is niet de bedoeling om hier een link naar je website te plaatsen. Post hier gewoon de code, veel simpeler, sneller en meer kans dat het blijft staan.
24-03-2010 22:27

Voor de liefhebbers: Een dergelijk script maar dan in OOP :-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
class Uniqid {
    
    const index = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
    public static function encode($number){
        
        if(!isset($number)){
            return null;
        }
 
        $base = strlen(self::index);
        $out = "";
        
        for ($t = floor(log($number, $base)); $t >= 0; $t--) {
        
            $bcp = bcpow($base, $t);
            $a   = floor($number / $bcp) % $base;
            $out = $out . substr(self::index, $a, 1);
            $number  = $number - ($a * $bcp);
        }
      
        $out = strrev($out);
        
        return $out;
    }
 
    public static function decode($string){

        $base = strlen(self::index);
        $string  = strrev($string);
        $out = 0;
        $len = strlen($string) - 1;
 
        for ($t = 0; $t <= $len; $t++){
            
            $bcpow = bcpow($base, $len - $t);
            $out = $out + strpos(self::index, substr($string, $t, 1)) * $bcpow;
        }
 
        $out = sprintf('%F', $out);
        $out = substr($out, 0, strpos($out, '.'));
      
        return $out;
    }
}
?>
24-03-2010 22:32

Voor de liefhebbers: Een dergelijk script maar dan in JS :-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php
/**
 *  Javascript randUniqID class
 *  Description: Translates a numeric identifier into a short string and backwards.
 *
 *  Usage:
 *    var str = randUniqID.encode(9007199254740989); // str = 'fE2XnNGpF'
 *    var id = randUniqID.decode('fE2XnNGpF'); // id = 9007199254740989;
 **/

var randUniqID = {
  index:'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
 
  /**
   *  @function randUniqID.encode
   *  @description Encode a number into short string
   *  @param integer
   *  @return string
   **/
  encode:function(_number){
    if('undefined' == typeof _number){
      return null;
    }
    else if('number' != typeof(_number)){
      throw new Error('Wrong parameter type');
    }
 
    var ret = '';
 
    for(var i=Math.floor(Math.log(parseInt(_number))/Math.log(randUniqID.index.length));i>=0;i--){
      ret = ret + randUniqID.index.substr((Math.floor(parseInt(_number) / randUniqID.bcpow(randUniqID.index.length, i)) % randUniqID.index.length),1);
    }
 
    return ret.reverse();
  },
 
  /**
   *  @function randUniqID.decode
   *  @description Decode a short string and return number
   *  @param string
   *  @return integer
   **/
  decode:function(_string){
    if('undefined' == typeof _string){
      return null;
    }
    else if('string' != typeof _string){
      throw new Error('Wrong parameter type');
    }
 
    var str = _string.reverse();
    var ret = 0;
 
    for(var i=0;i<=(str.length - 1);i++){
      ret = ret + randUniqID.index.indexOf(str.substr(i,1)) * (randUniqID.bcpow(randUniqID.index.length, (str.length - 1) - i));
    }
 
    return ret;
  },
 
  /**
   *  @function randUniqID.bcpow
   *  @description Raise _a to the power _b
   *  @param float _a
   *  @param integer _b
   *  @return string
   **/
  bcpow:function(_a, _b){
    return Math.floor(Math.pow(parseFloat(_a), parseInt(_b)));
  }
};
 
/**
 *  @function String.reverse
 *  @description Reverse a string
 *  @return string
 **/
String.prototype.reverse = function(){
  return this.split('').reverse().join('');
};
?>
26-03-2010 09:21

Kun je ook geen PL/pgSQL versie maken? Met als input een sequence. Zodat je hem als DEFAULT waarde kan toevoegen aan een database kolom.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?sql
CREATE FUNCTION rand_uniqid(IN sequence_name text, OUT uniek_id text) AS $$
BEGIN
/* functie  */
END
$$ LANGUAGE plpgsql; 

CREATE SEQUENCE test START 101;

CREATE TABLE test (
 id text DEFAULT rand_uniqid('test') PRIMARY KEY,
 kolom2 text,
 kolom3 text
);
26-03-2010 10:56

Is dit niet wat je zoekt?
http://www.phpfreakz.nl/forum.php?forum=2&iid=1304715

Overigens is het volgens mij niet zo moeilijk om dit te porteren naar pl/pgsql.

1
N.V.T.
26-03-2010 11:16

Van PL/pgSQL heb ik helaas nog weinig kaas gegeten maar ga mij hier zeker eens in verdiepen. Wellicht is er iemand anders hier die hier wat meer in thuis is en een PL/pgSQL versie kan maken?

1
2
3
<?php
echo 'Hello world!';
?>
26-03-2010 17:40

Hier een postgresql versie. Het maakt wel niet een random unieke string aan. Maar een getal word omgezet naar een string.

Het is een aangepaste versie van base64 encoding, maar dan een url safe versie, aangezien de base64 encoding tekens kan bevatten zoals / =

Meer uitleg over base64 encoding http://en.wikipedia.org/wiki/Base64

Hierdoor is er ook geen enkel gevaar dat er ooit een dubbele waarden zou toegevoegd kunnen worden. Ook al zou je zeggen dat een veld alleen unieke waardes zou mogen bevatten zou je toch nog indien er een pak waarden in de tabel zouden zitten query's kunnen krijgen die zeer traag zijn. Omdat bij iedere fail terug een random id aangemaakt moet worden en dan controleren of die al bestaad.

En als je de volgorde van de index veranderd voor je eigen site komt niemand ooit achter een decode versie :-).

(Ik ga wel nog wat verbeteringen aanbrengen. Deze post ik dan natuurlijk ook hier.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php 

CREATE OR REPLACE FUNCTION string_to_bits(input_text TEXT) 
RETURNS TEXT AS $$
DECLARE
    output_text TEXT;
    i INTEGER;
BEGIN
    output_text := '';


    FOR i IN 1..char_length(input_text) LOOP
        output_text := output_text || ascii(substring(input_text FROM i FOR 1))::bit(8);
    END LOOP;


    return output_text;
END;
$$ LANGUAGE plpgsql; 


CREATE OR REPLACE FUNCTION id_to_sid(id INTEGER) 
RETURNS TEXT AS $$
DECLARE
    output_text TEXT;
    i INTEGER;
    index TEXT[];
    bits TEXT;
    bit_array TEXT[];
    input_text TEXT;
BEGIN
    input_text := id::TEXT;
    output_text := '';
    index := string_to_array('0,d,A,3,E,z,W,m,D,S,Q,l,K,s,P,b,N,c,f,j,5,I,t,C,i,y,o,G,2,r,x,h,V,J,k,-,T,w,H,L,9,e,u,X,p,U,a,O,v,4,R,B,q,M,n,g,1,F,6,Y,_,8,7,Z', ',');
    
    bits := string_to_bits(input_text);
    
    IF length(bits) % 6 <> 0 THEN
        bits := rpad(bits, length(bits) + 6 - (length(bits) % 6), '0');
    END IF;

    FOR i IN 1..((length(bits) / 6)) LOOP
        IF i = 1 THEN
            bit_array[i] := substring(bits FROM 1 FOR 6);
        ELSE
            bit_array[i] := substring(bits FROM 1 + (i - 1) * 6 FOR 6);
        END IF;

        output_text := output_text || index[bit_array[i]::bit(6)::integer + 1];
    END LOOP;


    return output_text;
END;
$$ LANGUAGE plpgsql; 

 ?>
20-08-2010 12:05

Ik heb er maar een fatsoenlijke JavaScript-versie van gemaakt die zichzelf in het Crypto-object stopt. Zo heb je wat meer zekerheid en veiligheid.

Wel leuk gedaan. ;-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<script>
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, newcap: true, immed: true */

/*global Crypto:true */

if (typeof Crypto === 'undefined') {
    Crypto = {};
}

Crypto.random = (function () {
    var index = [
        'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm',
        'n', 'p', 'q', 'r', 't', 'v', 'w', 'x', 'y', 'z',
        '_', '-', '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K',
        'L', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X',
        'Y', 'Z'
    ], base = index.length;

    return {
        encode: function (i) {
            var out = [],
                t = Math.floor(Math.log(i) / Math.log(base)),
                bcp,
                a;

            while (t >= 0) {
                bcp = Math.pow(base, t);
                a = Math.floor(i / bcp) % base;
                out[out.length] = index[a];
                i -= a * bcp;

                t -= 1;
            }

            return out.reverse().join('');
        },
        decode: function (i) {
            var chars = i.split(''),
                out = 0,
                el;

            while (typeof (el = chars.pop()) !== 'undefined') {
                out += index.indexOf(el) * Math.pow(base, chars.length);
            }

            return out;
        }
    };
}());
</script>

Voorbeeld:

<script>
alert(Crypto.random.encode(101010101));
alert(Crypto.random.decode('XMzNr'));
</script>
24-09-2010 01:25

Geweldig script!

11-12-2010 19:08

Ik ben precies naar dit script op zoek. Ik heb het script geknipt en geplakt alleen geeft hij geen uitkomst...?

Inloggen wachtwoord vergeten? Aanmelden