427

How would it be possible to generate a random, unique string using numbers and letters for use in a verify link? Like when you create an account on a website, and it sends you an email with a link, and you have to click that link in order to verify your account

How can I generate one of those using PHP?

miken32
  • 35,483
  • 13
  • 81
  • 108
Andrew
  • 196,883
  • 184
  • 487
  • 673
  • 1
    All you need are strings and uniformly distributed random numbers. – Artelius Dec 04 '09 at 10:52
  • 11
    Hey Andrew, you should choose `Scott` as the correct answer. Scott is using OpenSSL's cryptographically secure psudo-random number generator (CSPRNG) which will choose the most secure source of entropy based on your platform. – rook Sep 18 '13 at 19:50
  • 3
    Anyone reading this after 2015, please, please look here: https://paragonie.com/blog/2015/07/how-safely-generate-random-strings-and-integers-in-php Most top answers are flawed more or less... – rugk Oct 08 '16 at 20:30
  • OpenSSL and `uniqid` are insecure. Use something like [`Random::alphanumericString($length)`](https://github.com/delight-im/PHP-Random) or `Random::alphanumericHumanString($length)`. – caw Nov 20 '19 at 16:39

28 Answers28

587

PHP 7 standard library provides the random_bytes($length) function that generate cryptographically secure pseudo-random bytes.

Example:

$bytes = random_bytes(20);
var_dump(bin2hex($bytes));

The above example will output something similar to:

string(40) "5fe69c95ed70a9869d9f9af7d8400a6673bb9ce9"

More info: http://php.net/manual/en/function.random-bytes.php

PHP 5 (outdated)

I was just looking into how to solve this same problem, but I also want my function to create a token that can be used for password retrieval as well. This means that I need to limit the ability of the token to be guessed. Because uniqid is based on the time, and according to php.net "the return value is little different from microtime()", uniqid does not meet the criteria. PHP recommends using openssl_random_pseudo_bytes() instead to generate cryptographically secure tokens.

A quick, short and to the point answer is:

bin2hex(openssl_random_pseudo_bytes($bytes))

which will generate a random string of alphanumeric characters of length = $bytes * 2. Unfortunately this only has an alphabet of [a-f][0-9], but it works.


Below is the strongest function I could make that satisfies the criteria (This is an implemented version of Erik's answer).
function crypto_rand_secure($min, $max)
{
    $range = $max - $min;
    if ($range < 1) return $min; // not so random...
    $log = ceil(log($range, 2));
    $bytes = (int) ($log / 8) + 1; // length in bytes
    $bits = (int) $log + 1; // length in bits
    $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
    do {
        $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
        $rnd = $rnd & $filter; // discard irrelevant bits
    } while ($rnd > $range);
    return $min + $rnd;
}

function getToken($length)
{
    $token = "";
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    $max = strlen($codeAlphabet); // edited

    for ($i=0; $i < $length; $i++) {
        $token .= $codeAlphabet[crypto_rand_secure(0, $max-1)];
    }

    return $token;
}

crypto_rand_secure($min, $max) works as a drop in replacement for rand() or mt_rand. It uses openssl_random_pseudo_bytes to help create a random number between $min and $max.

getToken($length) creates an alphabet to use within the token and then creates a string of length $length.

Source: http://us1.php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322

gagarine
  • 3,840
  • 25
  • 34
Scott
  • 10,906
  • 4
  • 24
  • 46
  • I hope that by "password retrieval" you don't mean you're storing the passwords in plaintext. If that's the case, your security here seems pointless. – alanaktion May 07 '13 at 20:43
  • 2
    Agreed. Plaintext password storage is awful! (I use blowfish.) But after the token is generated it allows the holder of the token to reset the password, so the token is password equivalent while it is valid (and is treated as such). – Scott May 08 '13 at 01:44
  • 26
    Hey, I combined your excellent code with the convenient Kohana Text::random() method functionality and created this gist: https://gist.github.com/raveren/5555297 – raveren May 10 '13 at 15:51
  • Thx to Scott! I've used md5(mt_rand(1, 122)), but even when creating about 78 keys, there were duplicates... – toesslab Jan 31 '14 at 07:44
  • 10
    Perfect! I tested it with 10 chars as length. 100000 tokens and still no duplicates! – Sharpless512 Feb 26 '14 at 08:13
  • 5
    I used this process and found it super slow. With a length of 36 it timed out on the dev server. With a length of 24 it still took around 30 seconds. Not sure what I did wrong but it was too slow for me and I opted for another solution. – Shane Apr 17 '14 at 01:25
  • @Scott Amazing function! Is it good enough to use for CSRF and email confirmation keys? – Kid Diamond Apr 22 '14 at 22:09
  • 6
    You can also use `base64_encode` instead of `bin2hex` to get a shorter string with larger alphabet. – erjiang May 30 '14 at 13:57
  • Noob here - so for example I would run `echo getToken(crypto_rand_secure(5,20));` to get the token? And then password_hash() it? – bagofmilk Aug 19 '14 at 18:52
  • Can I ask why you can't add the line: $codeAlphabet .= "!@#$%^&*?"; to the getToken function? – Jordan Young Jan 26 '15 at 02:15
  • @erjiang i replaced `$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));` with `$rnd = hexdec(base64_encode(openssl_random_pseudo_bytes($bytes)));` and got this `aAAAAIKAAJAAOAAAAKA6KMAALMAAAAAAAAAqAGAA`. Seems better to use `bin2hex` – Andris Mar 02 '15 at 05:12
  • @user2118559 I meant that you can replace the one-liner `bin2hex(openssl_random_pseudo_bytes($bytes));` with `base64_encode(openssl_random_pseudo_bytes($bytes));` Either way you're not changing the input random data. I think the complex function in the second half of this answer is just silly. – erjiang Mar 02 '15 at 15:59
  • @Jordan Because it was stated in the issue: "random unique alphanumeric string". – TheOneWhoSighs Mar 27 '15 at 19:33
  • getToken(10) returns nothing ! I think openssl_random_pseudo_bytes is very slow ! – S.M_Emamian Aug 10 '15 at 05:02
  • As far as condensing this as much as possible, how about: `str_replace(array('+','/','='), array('','',''), base64_encode(openssl_random_pseudo_bytes($bytes)));` – PaulSkinner Sep 28 '15 at 16:20
  • 2
    I may miss something but I don't get how this ensures the generated token is unique. – Nitseg Jan 20 '16 at 08:57
  • @Nitseg I think Its not, you have to register every ones and check for duplicates. However with a large length (>10chars), you get a very low risk – Reign.85 Mar 29 '16 at 09:14
  • What's wrong with just, function getToken($length) { return bin2hex(openssl_random_pseudo_bytes($length); } – Mark Jun 01 '16 at 03:46
  • @Yusuf - There is nothing wrong with your getToken function, but it does serve a different purpose. Your bin2hex(openssl_random_pseudo_bytes()) function is just a strict wrapper for openssl. The solution above provides two functions, one that can be used to obtain a random int between min and max and a second that utilizes that function to create a string of random characters that are pulled from a user defined set of characters instead of just 0-9,A-F. – Scott Jun 01 '16 at 18:24
  • @Scott all I was pointing at is loops are unnecessary in both of your functions. It doesn't give any raise to cryptography entropy. Pls refer to my post below. – Mark Jun 02 '16 at 05:42
  • 1
    Be aware that the `getToken` function never generates the last character in the `codeAlphabet` variable. In this case `9` is never generated. `$max = strlen($codeAlphabet) - 1;` should be `$max = strlen($codeAlphabet);` –  Aug 23 '16 at 08:16
  • 1
    The short version would be easier to understand if `$bits` was replaced by `$bytes`. `bin2hex(openssl_random_pseudo_bytes($bytes))` "generate a random string of alphanumeric characters of length = **$bytes** \* 2" – CJ Dennis Sep 03 '16 at 03:46
  • 1
    As far as I can tell, `crypto_rand_secure` will never return `$max`. That's not what I'd expect, and it differs from PHP's `rand` and `random_int`, which both state that the range is inclusive. It seems to me that `while ($rnd >= $range)` should be `while ($rnd > $range)` and the previous edit to `getToken` should be changed back. – robotspacer Oct 02 '16 at 11:28
  • good idea to generate random unique string, I've edited the answer with the php7 random-bytes and random-int suggestion, correct me if I'm wrong or any better solution – jihchuan Jun 28 '17 at 10:40
  • @jihchuan - Good example, but I removed one line from your edit as its a bit strange to append random hex to the code alphabet. If `random_bytes` were used in an example, it would be in place of `openssl_random_pseudo_bytes($bytes)` in the helper function. – Scott Jun 28 '17 at 17:33
  • scratch the earlier question. Figured it out – Agbogidi Michael Jul 28 '17 at 19:01
  • Is there any reason for doing `hexdec(bin2hex(` and not just `bindec(`? – 99 Problems - Syntax ain't one Mar 13 '18 at 17:14
  • Fantastic mate, thanks a bunch! Using it on on php 7 – Mustafa Erdogan Jan 28 '19 at 23:21
  • This doesn't guarantee uniqueness, though. How about concatenating the string with an auto_increment kind-of value coming from the DBMS? – Eugenio Sep 03 '19 at 11:14
  • The random_int solution with PHP7 should be highlighted as best and easiest solution. The Openssl extension is usually not installed. – RubbelDeCatc Nov 15 '19 at 09:48
328

Security Notice: This solution should not be used in situations where the quality of your randomness can affect the security of an application. In particular, rand() and uniqid() are not cryptographically secure random number generators. See Scott's answer for a secure alternative.

If you do not need it to be absolutely unique over time:

md5(uniqid(rand(), true))

Otherwise (given you have already determined a unique login for your user):

md5(uniqid($your_user_login, true))
Scott Arciszewski
  • 30,409
  • 16
  • 85
  • 198
loletech
  • 3,504
  • 1
  • 13
  • 3
  • 97
    Both methods do not *guarantee* uniqueness - length of the input of the md5 function is greater than length of its output and according to the http://en.wikipedia.org/wiki/Pigeonhole_principle the collision is guaranteed. On the other hand, the greater the population relying on hashes to achieve the "unique" id, the greater the probability of occurring at least one collision (see http://en.wikipedia.org/wiki/Birthday_problem). The probability may be tiny for most of solutions but it still exists. – Dariusz Walczak Jul 14 '11 at 12:30
  • 14
    This is not a secure method of generating random values. See Scott's answer. – rook Sep 18 '13 at 19:51
  • 5
    For email confirmations that expire relatively soon, I think the tiny possibility that one person might one day confirm someone else's email is a negligible risk. But you could always check if the token already exists in the DB after creating it and just pick a new one if that is the case. However, the time you spend writing that snippet of code is likely wasted as it will most likely never be run. – Andrew May 26 '14 at 21:10
  • 1
    Do note that md5 returns hexadecimal values, meaning the character set is limited to [0-9] and [a-f]. – Thijs Riezebeek May 15 '15 at 16:59
  • 2
    md5 can have collisions, you have just ruined uniqid advantage – user151496 Sep 28 '16 at 12:42
  • use it for more unique result str_shuffle(md5(uniqid(rand(), true))) – Davinder Kumar Apr 18 '17 at 06:20
97

Object-oriented version of the most up-voted solution

I've created an object-oriented solution based on Scott's answer:

<?php

namespace Utils;

/**
 * Class RandomStringGenerator
 * @package Utils
 *
 * Solution taken from here:
 * http://stackoverflow.com/a/13733588/1056679
 */
class RandomStringGenerator
{
    /** @var string */
    protected $alphabet;

    /** @var int */
    protected $alphabetLength;


    /**
     * @param string $alphabet
     */
    public function __construct($alphabet = '')
    {
        if ('' !== $alphabet) {
            $this->setAlphabet($alphabet);
        } else {
            $this->setAlphabet(
                  implode(range('a', 'z'))
                . implode(range('A', 'Z'))
                . implode(range(0, 9))
            );
        }
    }

    /**
     * @param string $alphabet
     */
    public function setAlphabet($alphabet)
    {
        $this->alphabet = $alphabet;
        $this->alphabetLength = strlen($alphabet);
    }

    /**
     * @param int $length
     * @return string
     */
    public function generate($length)
    {
        $token = '';

        for ($i = 0; $i < $length; $i++) {
            $randomKey = $this->getRandomInteger(0, $this->alphabetLength);
            $token .= $this->alphabet[$randomKey];
        }

        return $token;
    }

    /**
     * @param int $min
     * @param int $max
     * @return int
     */
    protected function getRandomInteger($min, $max)
    {
        $range = ($max - $min);

        if ($range < 0) {
            // Not so random...
            return $min;
        }

        $log = log($range, 2);

        // Length in bytes.
        $bytes = (int) ($log / 8) + 1;

        // Length in bits.
        $bits = (int) $log + 1;

        // Set all lower bits to 1.
        $filter = (int) (1 << $bits) - 1;

        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));

            // Discard irrelevant bits.
            $rnd = $rnd & $filter;

        } while ($rnd >= $range);

        return ($min + $rnd);
    }
}

Usage

<?php

use Utils\RandomStringGenerator;

// Create new instance of generator class.
$generator = new RandomStringGenerator;

// Set token length.
$tokenLength = 32;

// Call method to generate random string.
$token = $generator->generate($tokenLength);

Custom alphabet

You can use custom alphabet if required. Just pass a string with supported chars to the constructor or setter:

<?php

$customAlphabet = '0123456789ABCDEF';

// Set initial alphabet.
$generator = new RandomStringGenerator($customAlphabet);

// Change alphabet whenever needed.
$generator->setAlphabet($customAlphabet);

Here's the output samples

SRniGU2sRQb2K1ylXKnWwZr4HrtdRgrM
q1sRUjNq1K9rG905aneFzyD5IcqD4dlC
I0euIWffrURLKCCJZ5PQFcNUCto6cQfD
AKwPJMEM5ytgJyJyGqoD5FQwxv82YvMr
duoRF6gAawNOEQRICnOUNYmStWmOpEgS
sdHUkEn4565AJoTtkc8EqJ6cC4MLEHUx
eVywMdYXczuZmHaJ50nIVQjOidEVkVna
baJGt7cdLDbIxMctLsEBWgAw5BByP5V0
iqT0B2obq3oerbeXkDVLjZrrLheW4d8f
OUQYCny6tj2TYDlTuu1KsnUyaLkeObwa

I hope it will help someone. Cheers!

Slava Fomin II
  • 21,036
  • 19
  • 98
  • 176
  • I'm sorry but how do I run this thing? I did `$obj = new RandomStringGenerator;` but which method should I called? Thank you. – sg552 Oct 01 '14 at 16:41
  • @sg552, you should call `generate` method as in example above. Just pass an integer to it to specify length of the resulting string. – Slava Fomin II Dec 02 '14 at 08:38
  • Thanks it works for me but `Custom Alphabet` didn't. I got empty output when I echo. Anyway I'm not even sure what's the use of 2nd example `Custom Alphabet` so I think I just stay with the first example. Thanks. – sg552 Dec 03 '14 at 21:24
  • 1
    Nice, but pretty overkill in most scenarios, unless your project heavily relies on randomly generated codes – dspacejs May 08 '15 at 11:47
  • 1
    yes nice but overkill, especially considering it is pretty much deprecated now for php 5.7 – Andrew Mar 21 '16 at 17:30
54

I'm late but I'm here with some good research data based on the functions provided by Scott's answer. So I set up a Digital Ocean droplet just for this 5-day long automated test and stored the generated unique strings in a MySQL database.

During this test period, I used 5 different lengths (5, 10, 15, 20, 50) and +/-0.5 million records were inserted for each length. During my test, only the length 5 generated +/-3K duplicates out of 0.5 million and the remaining lengths didn't generate any duplicates. So we can say that if we use a length of 15 or above with Scott's functions, then we can generate highly reliable unique strings. Here is the table showing my research data:

enter image description here

Update: I created a simple Heroku app using these functions that returns the token as a JSON response. The app can be accessed at https://uniquestrings.herokuapp.com/api/token?length=15

I hope this helps.

Community
  • 1
  • 1
Rehmat
  • 3,732
  • 1
  • 16
  • 32
39

You can use UUID(Universally Unique Identifier), it can be used for any purpose, from user authentication string to payment transaction id.

A UUID is a 16-octet (128-bit) number. In its canonical form, a UUID is represented by 32 hexadecimal digits, displayed in five groups separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric characters and four hyphens).

function generate_uuid() {
    return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
        mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
        mt_rand( 0, 0xffff ),
        mt_rand( 0, 0x0C2f ) | 0x4000,
        mt_rand( 0, 0x3fff ) | 0x8000,
        mt_rand( 0, 0x2Aff ), mt_rand( 0, 0xffD3 ), mt_rand( 0, 0xff4B )
    );

}

//calling funtion

$transationID = generate_uuid();

some example outputs will be like:

E302D66D-87E3-4450-8CB6-17531895BF14
22D288BC-7289-442B-BEEA-286777D559F2
51B4DE29-3B71-4FD2-9E6C-071703E1FF31
3777C8C6-9FF5-4C78-AAA2-08A47F555E81
54B91C72-2CF4-4501-A6E9-02A60DCBAE4C
60F75C7C-1AE3-417B-82C8-14D456542CD7
8DE0168D-01D3-4502-9E59-10D665CEBCB2

hope it helps someone in future :)

Developer
  • 3,307
  • 4
  • 35
  • 42
34

This function will generate a random key using numbers and letters:

function random_string($length) {
    $key = '';
    $keys = array_merge(range(0, 9), range('a', 'z'));

    for ($i = 0; $i < $length; $i++) {
        $key .= $keys[array_rand($keys)];
    }

    return $key;
}

echo random_string(50);

Example output:

zsd16xzv3jsytnp87tk7ygv73k8zmr0ekh6ly7mxaeyeh46oe8
Ilmari Karonen
  • 44,762
  • 9
  • 83
  • 142
18

I use this one-liner:

base64_encode(openssl_random_pseudo_bytes(3 * ($length >> 2)));

where length is the length of the desired string (divisible by 4, otherwise it gets rounded down to the nearest number divisible by 4)

DudeOnRock
  • 3,066
  • 3
  • 23
  • 51
  • 6
    It doesn't always return alphanumeric characters only. It can contain characters like `==` – user3459110 Oct 26 '14 at 08:15
  • 1
    If used in an URL, `base64url_encode` can be used instead, see here: http://php.net/manual/en/function.base64-encode.php – Paul Mar 06 '17 at 16:49
12

Use the code below to generate the random number of 11 characters or change the number as per your requirement.

$randomNum=substr(str_shuffle("0123456789abcdefghijklmnopqrstvwxyz"), 0, 11);

or we can use custom function to generate the random number

 function randomNumber($length){
     $numbers = range(0,9);
     shuffle($numbers);
     for($i = 0;$i < $length;$i++)
        $digits .= $numbers[$i];
     return $digits;
 }

 //generate random number
 $randomNum=randomNumber(11);
Sahil
  • 1,123
  • 10
  • 27
Ramesh Kotkar
  • 629
  • 9
  • 10
  • 4
    This can never generate the string "aaaaaaaaaaa" or "aaaaabbbbbb". It just picks a substring of a random permutation of the alphabet. The 11 characters long string only has V(11,35) = 288 possible values. Do not use this if you expect the strings to be unique! – kdojeteri Sep 05 '17 at 21:55
  • 1
    @Peping then do `substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstvwxyz', 36)), 0, 11);` – Tim Hallman Mar 18 '19 at 22:43
  • 1
    thanks for nice function! '0123456789abcdefghijklmnopqrstvwxyzABCDEFGHIJKLMNOPQRSTVWXYZ~!@#$%^&*()_+-=<>,.?/' - ext. str_shuffle range. – vitali_y Nov 10 '20 at 17:09
7
  1. Generate a random number using your favourite random-number generator
  2. Multiply and divide it to get a number matching the number of characters in your code alphabet
  3. Get the item at that index in your code alphabet.
  4. Repeat from 1) until you have the length you want

e.g (in pseudo code)

int myInt = random(0, numcharacters)
char[] codealphabet = 'ABCDEF12345'
char random = codealphabet[i]
repeat until long enough
Erik A. Brandstadmoen
  • 9,864
  • 2
  • 34
  • 52
  • Theoretically speaking, couldn't this potentially generate the same string more than once? – frezq Jan 29 '19 at 15:06
7

For really random strings, you can use

<?php

echo md5(microtime(true).mt_Rand());

outputs :

40a29479ec808ad4bcff288a48a25d5c

so even if you try to generate string multiple times at exact same time, you will get different output.

6

This is a simple function that allows you to generate random strings containing Letters and Numbers (alphanumeric). You can also limit the string length. These random strings can be used for various purposes, including: Referral Code, Promotional Code, Coupon Code. Function relies on following PHP functions: base_convert, sha1, uniqid, mt_rand

function random_code($length)
{
  return substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, $length);
}

echo random_code(6);

/*sample output
* a7d9e8
* 3klo93
*/
Arpit J.
  • 882
  • 10
  • 17
5

When trying to generate a random password you are trying to :

  • First generate a cryptographically secure set of random bytes

  • Second is turning those random bytes into a printable string

Now, there are multiple way to generate random bytes in php like :

$length = 32;

//PHP 7+
$bytes= random_bytes($length);

//PHP < 7
$bytes= openssl_random_pseudo_bytes($length);

Then you want to turn these random bytes into a printable string :

You can use bin2hex :

$string = bin2hex($bytes);

or base64_encode :

$string = base64_encode($bytes);

However, note that you do not control the length of the string if you use base64. You can use bin2hex to do that, using 32 bytes will turn into a 64 char string. But it will only work like so in an EVEN string.

So basically you can just do :

$length = 32;

if(PHP_VERSION>=7){
    $bytes= random_bytes($length);
}else{
    $bytes= openssl_random_pseudo_bytes($length);
} 

$string = bin2hex($bytes);
Dylan Kas
  • 2,212
  • 1
  • 6
  • 21
3

Here is ultimate unique id generator for you. made by me.

<?php
$d=date ("d");
$m=date ("m");
$y=date ("Y");
$t=time();
$dmt=$d+$m+$y+$t;    
$ran= rand(0,10000000);
$dmtran= $dmt+$ran;
$un=  uniqid();
$dmtun = $dmt.$un;
$mdun = md5($dmtran.$un);
$sort=substr($mdun, 16); // if you want sort length code.

echo $mdun;
?>

you can echo any 'var' for your id as you like. but $mdun is better, you can replace md5 to sha1 for better code but that will be very long which may you dont need.

Thank you.

Krishna Torque
  • 397
  • 4
  • 15
  • 7
    In this case randomness is guaranteed by random written code, right? – Daniel Kucal Jan 16 '16 at 14:27
  • 3
    yeah, this is kind of "random" because it's non standard, obscure code. This makes it difficult to predict ... but not actually it uses insecure parts to generate a random number. so the result is technically not random or secure. – Philipp Jul 05 '16 at 18:06
3

Here is what I use:

md5(time() . rand());    
// Creates something like 0c947c3b1047334f5bb8a3b7adc1d97b
Faraz Kelhini
  • 3,717
  • 30
  • 38
2
function random_string($length = 8) {
    $alphabets = range('A','Z');
    $numbers = range('0','9');
    $additional_characters = array('_','=');
    $final_array = array_merge($alphabets,$numbers,$additional_characters);
       while($length--) {
      $key = array_rand($final_array);

      $password .= $final_array[$key];
                        }
  if (preg_match('/[A-Za-z0-9]/', $password))
    {
     return $password;
    }else{
    return  random_string();
    }

 }
poyynt
  • 72
  • 11
Nidhin
  • 1,668
  • 19
  • 20
2

If you want to generate a unique string in PHP, try following.

md5(uniqid().mt_rand());

In this,

uniqid() - It will generate unique string. This function returns timestamp based unique identifier as a string.

mt_rand() - Generate random number.

md5() - It will generate the hash string.

akshaypjoshi
  • 1,180
  • 1
  • 11
  • 22
1

after reading previous examples I came up with this:

protected static $nonce_length = 32;

public static function getNonce()
{
    $chars = array();
    for ($i = 0; $i < 10; $i++)
        $chars = array_merge($chars, range(0, 9), range('A', 'Z'));
    shuffle($chars);
    $start = mt_rand(0, count($chars) - self::$nonce_length);
    return substr(join('', $chars), $start, self::$nonce_length);
}

I duplicate 10 times the array[0-9,A-Z] and shuffle the elements, after I get a random start point for substr() to be more 'creative' :) you can add [a-z] and other elements to array, duplicate more or less, be more creative than me

1

I like to use hash keys when dealing verification links. I would recommend using the microtime and hashing that using MD5 since there should be no reason why the keys should be the same since it hashes based off of the microtime.

  1. $key = md5(rand());
  2. $key = md5(microtime());
René Vogt
  • 40,163
  • 14
  • 65
  • 85
sgtcoder
  • 149
  • 6
1
<?php
function generateRandomString($length = 11) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;

}

?>

above function will generate you a random string which is length of 11 characters.

1

I always use this my function to generate a custom random alphanumeric string... Hope this help.

<?php
  function random_alphanumeric($length) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345689';
    $my_string = '';
    for ($i = 0; $i < $length; $i++) {
      $pos = mt_rand(0, strlen($chars) -1);
      $my_string .= substr($chars, $pos, 1);
    }
    return $my_string;
  }
  echo random_alphanumeric(50); // 50 characters
?>

It generates for example: Y1FypdjVbFCFK6Gh9FDJpe6dciwJEfV6MQGpJqAfuijaYSZ86g

If you want compare with another string to be sure that is a unique sequence you can use this trick...

$string_1 = random_alphanumeric(50);
$string_2 = random_alphanumeric(50);
while ($string_1 == $string_2) {
  $string_1 = random_alphanumeric(50);
  $string_2 = random_alphanumeric(50);
  if ($string_1 != $string_2) {
     break;
  }
}
echo $string_1;
echo "<br>\n";
echo $string_2;

it generate two unique strings:

qsBDs4JOoVRfFxyLAOGECYIsWvpcpMzAO9pypwxsqPKeAmYLOi

Ti3kE1WfGgTNxQVXtbNNbhhvvapnaUfGMVJecHkUjHbuCb85pF

Hope this is what you are looking for...

Alessandro
  • 771
  • 8
  • 21
1

A simple solution is to convert base 64 to alphanumeric by discarding the non-alphanumeric characters.

This one uses random_bytes() for a cryptographically secure result.

function random_alphanumeric(int $length): string
{
    $result='';
    do
    {
        //Base 64 produces 4 characters for each 3 bytes, so most times this will give enough bytes in a single pass
        $bytes=random_bytes(($length+3-strlen($result))*2);
        //Discard non-alhpanumeric characters
        $result.=str_replace(['/','+','='],['','',''],base64_encode($bytes));
        //Keep adding characters until the string is long enough
        //Add a few extra because the last 2 or 3 characters of a base 64 string tend to be less diverse
    }while(strlen($result)<$length+3);
    return substr($result,0,$length);
}

Edit: I just revisited this because I need something a bit more flexible. Here is a solution that performs a bit better than the above and gives the option to specify any subset of the ASCII character set:

<?php
class RandomText
{
    protected
        $allowedChars,
        //Maximum index to use
        $allowedCount,
        //Index values will be taken from a pool of this size
        //It is a power of 2 to keep the distribution of values even
        $distributionSize,
        //This many characters will be generated for each output character
        $ratio;
    /**
     * @param string $allowedChars characters to choose from
     */
    public function __construct(string $allowedChars)
    {
        $this->allowedCount = strlen($allowedChars);
        if($this->allowedCount < 1 || $this->allowedCount > 256) throw new \Exception('At least 1 and no more than 256 allowed character(s) must be specified.');
        $this->allowedChars = $allowedChars;
        //Find the power of 2 equal or greater than the number of allowed characters
        $this->distributionSize = pow(2,ceil(log($this->allowedCount, 2)));
        //Generating random bytes is the expensive part of this algorithm
        //In most cases some will be wasted so it is helpful to produce some extras, but not too many
        //On average, this is how many characters needed to produce 1 character in the allowed set
        //50% of the time, more characters will be needed. My tests have shown this to perform well.
        $this->ratio = $this->distributionSize / $this->allowedCount;
    }

    /**
     * @param int $length string length of required result
     * @return string random text
     */
    public function get(int $length) : string
    {
        if($length < 1) throw new \Exception('$length must be >= 1.');
        $result = '';
        //Keep track of result length to prevent having to compute strlen()
        $l = 0;
        $indices = null;
        $i = null;
        do
        {
            //Bytes will be used to index the character set. Convert to integers.
            $indices = unpack('C*', random_bytes(ceil(($length - $l) * $this->ratio)));
            foreach($indices as $i)
            {
                //Reduce to the smallest range that gives an even distribution
                $i %= $this->distributionSize;
                //If the index is within the range of characters, add one char to the string
                if($i < $this->allowedCount)
                {
                    $l++;
                    $result .= $this->allowedChars[$i];
                }
                if($l >= $length) break;
            }
        }while($l < $length);
        return $result;
    }
}
Jon Hulka
  • 1,187
  • 9
  • 14
0

Scott, yes you are very write and good solution! Thanks.

I am also required to generate unique API token for my each user. Following is my approach, i used user information (Userid and Username):

public function generateUniqueToken($userid, $username){

        $rand = mt_rand(100,999);
    $md5 = md5($userid.'!(&^ 532567_465 ///'.$username);

    $md53 = substr($md5,0,3);
    $md5_remaining = substr($md5,3);

    $md5 = $md53. $rand. $userid. $md5_remaining;

    return $md5;
}

Please have a look and let me know if any improvement i can do. Thanks

Himanshu Sharma
  • 234
  • 1
  • 5
0

Here is what I'm using on one of my projects, it's working great and it generates a UNIQUE RANDOM TOKEN:

$timestampz=time();

function generateRandomString($length = 60) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}


$tokenparta = generateRandomString();


$token = $timestampz*3 . $tokenparta;

echo $token;

Please note that I multiplied the timestamp by three to create a confusion for whoever user might be wondering how this token is generated ;)

I hope it helps :)

Fery Kaszoni
  • 3,740
  • 1
  • 16
  • 11
  • 1
    "Please note that I multiplied the timestamp by three to create a confusion for whoever user might be wondering how this token is generated" Sounds like security through obfuscation http://stackoverflow.com/questions/533965/why-is-security-through-obscurity-a-bad-idea – Harry Apr 06 '17 at 09:14
  • This is not a "security through obfuscation " as the timestamp is then added to a random string that was generated, and that's the actual final string that we return ;) – Fery Kaszoni Oct 23 '18 at 11:17
0

I think this is the best method to use.

str_shuffle(md5(rand(0,100000)))
Davinder Kumar
  • 642
  • 4
  • 16
0

we can use this two line of code to generate unique string have tested around 10000000 times of iteration

  $sffledStr= str_shuffle('abscdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_-+');
    $uniqueString = md5(time().$sffledStr);
0

Here is a fun one

public function randomStr($length = 16) {
    $string = '';
        
    while (($len = strlen($string)) < $length) {
        $size = $length - $len;
            
        $bytes = random_bytes($size);
            
        $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
    }
        
        return $string;
}

Stolen from laravel here

Nixon Kosgei
  • 329
  • 1
  • 11
-1

I believe the problem with all the existing ideas is that they are probably unique, but not definitely unique (as pointed out in Dariusz Walczak's reply to loletech). I have a solution that actually is unique. It requires that your script have some sort of memory. For me this is a SQL database. You could also simply write to a file somewhere. There are two implementations:

First method: have TWO fields rather than 1 that provide uniqueness. The first field is an ID number that is not random but is unique (The first ID is 1, the second 2...). If you are using SQL, just define the ID field with the AUTO_INCREMENT property. The second field is not unique but is random. This can be generated with any of the other techniques people have already mentioned. Scott's idea was good, but md5 is convenient and probably good enough for most purposes:

$random_token = md5($_SERVER['HTTP_USER_AGENT'] . time());

Second method: Basically the same idea, but initially pick a maximum number of strings that will ever be generated. This could just be a really big number like a trillion. Then do the same thing, generate an ID, but zero pad it so that all IDs are the same number of digits. Then just concatenate the ID with the random string. It will be random enough for most purposes, but the ID section will ensure that it is also unique.

bytesized
  • 1,420
  • 11
  • 22
-2

Simplifying Scotts code above by removing unnecessary loops which is slowing down badly and does not make it any more secure than calling openssl_random_pseudo_bytes just once

function crypto_rand_secure($min, $max)
{
 $range = $max - $min;
 if ($range < 1) return $min; // not so random...
 $log = ceil(log($range, 2));
 $bytes = (int) ($log / 8) + 1; // length in bytes
 $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
 return $min + $rnd%$range;
}

function getToken($length)
{
 return bin2hex(openssl_random_pseudo_bytes($length)
}
Mark
  • 3,373
  • 2
  • 21
  • 34
  • Unfortunately, I cannot agree with you that this is an appropriate simplification of the crypto_rand_secure function. The loop is necessary to avoid modulo bias that occurs when you take the result of a randomly generated number and then mod it by a number that does divide the range. https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias. By using a loop to throw out any numbers outside the range and selecting a new number, it avoids this bias. http://stackoverflow.com/questions/10984974/why-do-people-say-there-is-modulo-bias-when-using-a-random-number-generator – Scott Jun 02 '16 at 12:44
  • @Scott, that post is about rand(). However in this case, it's already taken care by openssl_random_pseudo_bytes so loop in unnecessary. – Mark Jun 02 '16 at 13:53
  • 1
    https://gist.github.com/anonymous/87e3ae6fd3320447f46b973e75c2ea85 - Please run this script. You will see that the distribution of the results are not random. Numbers between 0-55 have a higher occurrence than numbers between 56-100. `openssl` is not causing this, it is because of modulo bias in your return statement. 100(range) does not divide 255(1 byte) evenly and causes these results. My biggest issue with your answer is that you state that the function is as secure as using a loop, which is false. Your implementation of crypto_rand_secure is not cryptographically secure and is misleading. – Scott Jun 02 '16 at 15:08
  • @Scott, we are digressing here, it's not about last return statement, it can also be progressively calculated from the return value of openssl_random_pseudo_bytes without using modulo. The point I am making is that the loop is unnecessary once openssl_random_pseudo_bytes returns the random bytes. By making loop, you are not making it more secure, just making it slow. – Mark Jun 02 '16 at 16:13
  • 2
    This answer propagates bad information. You are correct that the loop does not make `openssl` more secure, but more importantly it is not making it less secure, this answer does. The code above takes a perfectly good random number and mangles it by biasing it towards lower numbers. If you put a disclaimer on your answer stating that it is faster(by removing the loop) but it is not a CSPRNG it would resolve this issue. Please, for the love of crypto, let users know that the output of this function could be biased and is *not* an equivalent solution. The loop is not just there to annoy you. – Scott Jun 02 '16 at 17:44