It need not be meaningful words - more like random password generation, but the catch is - they should be unique. I will be using this for some kind of package / product code. Which is the best method available? :)
It is generally not possible to generate sequences with both unique and random elements: obviously to be unique the algorithm has to take into account the previously generated elements in the sequence, so the next ones will not really be random.
Therefore your best bet would be to detect collisions and just retry (which could be very expensive in your particular case).
If you are constrained to just 7 chars, there's not much you can do above:
$allowed_chars = 'abcdefghijklmnopqrstuvwxz';
$allowed_count = strlen($allowed_chars);
$password = null;
$password_length = 7;
while($password === null || already_exists($password)) {
$password = '';
for($i = 0; $i < $password_length; ++$i) {
$password .= $allowed_chars{mt_rand(0, $allowed_count - 1)};
}
}
This should eventually give you a new password.
However, in similar cases I have encountered I usually pick a larger password size which also happens to be the size of the hex representation of a popular hash function (e.g. md5). Then you can make it easier on yourself and less error prone:
$password = time(); // even better if you have some other "random" input to use here
do {
$password = md5(time().$password);
}
while (already_exists($password));
This also has the added advantage that the sequence space is larger, hence there will be less collisions. You can pick the size of the hash function according to the expected numbers of passwords you will generate in the future to "guarantee" a low collision probability and thus less calls to the possibly expensive already_exists function.
Here's a way you could do it without hashes or loops:
$password = sprintf(
"%04s%03s",
base_convert(mt_rand(0, pow(36, 4) - 1), 10, 36),
base_convert(mt_rand(0, pow(36, 3) - 1), 10, 36)
);
As a few others have mentioned, ensuring uniqueness is more complicated, and should be unneeded. The simplest way you could do it would be to add extra characters at the end, incrementing with each password generated.
Here is something that looks random and should be unique and have 7 chars for the times to come:
echo base_convert(intval(microtime(true) * 10000), 10, 36);
Or for a little more randomness and less uniqueness (between 1000 and 10000 per second):
echo base_convert(mt_rand(1, 9) . intval(microtime(true) * 1000), 10, 36);
Or (uniqueness between 100 and 10000 per second) - this is probably the best option:
echo base_convert(mt_rand(10, 99) . intval(microtime(true) * 100), 10, 36);
Or (uniqueness between 10 and 10000 per second):
echo base_convert(mt_rand(100, 999) . intval(microtime(true) * 10), 10, 36);
You get the idea.
A random alphanumeric (base 36 = 0..9 + a..z) value that has 7 chars has to have a base 10 representation between 2176782336 and 78364164095, the following snippet proves it:
var_dump(base_convert('1000000', 36, 10)); // 2176782336
var_dump(base_convert('zzzzzzz', 36, 10)); // 78364164095
In order for it to be unique we have to rely on a non-repeating factor, the obvious choice is time():
var_dump(time()); // 1273508728
var_dump(microtime(true)); // 1273508728.2883
If we only wanted to ensure a minimum uniqueness factor of 1 unique code per second we could do:
var_dump(base_convert(time() * 2, 10, 36)); // 164ff8w
var_dump(base_convert(time() * 2 + 1, 10, 36)); // 164ff8x
var_dump(base_convert(time() * 2 + 2, 10, 36)); // 164ff8y
var_dump(base_convert(time() * 2 + 3, 10, 36)); // 164ff8z
You'll notice that these codes aren't random, you'll also notice that time() (1273508728) is less than 2176782336 (the minimum base 10 representation of a 7 char code), that's why I do time() * 2.
Now lets do some date math in order to add randomness and increase the uniqueness factor while complying with the integer limitations of older versions of PHP (< 5.0?):
var_dump(1 * 60 * 60); // 3600
var_dump(1 * 60 * 60 * 24); // 86400
var_dump(1 * 60 * 60 * 24 * 366); // 31622400
var_dump(1 * 60 * 60 * 24 * 366 * 10); // 316224000
var_dump(1 * 60 * 60 * 24 * 366 * 20); // 632448000
var_dump(1 * 60 * 60 * 24 * 366 * 30); // 948672000
var_dump(1 * 60 * 60 * 24 * 366 * 31); // 980294400
var_dump(PHP_INT_MAX); // 2147483647
Regarding PHP_INT_MAX I'm not sure what exactly changed in recent versions of PHP because the following clearly works in PHP 5.3.1, maybe someone could shed some light into this:
var_dump(base_convert(PHP_INT_MAX, 10, 36)); // zik0zj
var_dump(base_convert(PHP_INT_MAX + 1, 10, 36)); // zik0zk
var_dump(base_convert(PHP_INT_MAX + 2, 10, 36)); // zik0zl
var_dump(base_convert(PHP_INT_MAX * 2, 10, 36)); // 1z141z2
var_dump(base_convert(PHP_INT_MAX * 2 + 1, 10, 36)); // 1z141z3
var_dump(base_convert(PHP_INT_MAX * 2 + 2, 10, 36)); // 1z141z4
I got kinda lost with my rationalization here and I'm bored so I'll just finish really quick. We can use pretty much the whole base 36 charset and safely generate sequential codes with a minimum guaranteed uniqueness factor of 1 unique code per second for 3.16887646 years using this:
base_convert(mt_rand(22, 782) . substr(time(), 2), 10, 36);
I just realized that the above can sometimes return duplicated values due to the first argument of mt_rand(), in order to produce unique results we need to limit a our base 36 charset a little bit:
base_convert(mt_rand(122, 782) . substr(time(), 2), 10, 36);
Remember that the above values are still sequential, in order to make them look random we can use microtime() but we can only ensure a uniqueness factor of 10 codes per second for 3.8 months:
base_convert(mt_rand(122, 782) . substr(number_format(microtime(true), 1, '', ''), 3), 10, 36);
This proved to be more difficult than I originally antecipated since there are lot of constrains:
use the whole base 36 charset
generate random-looking codes
trade-offs between uniqueness factor per second and durability of uniqueness
PHP integer limitations
If we can ignore any of the above it would be a lot easier and I'm sure this can be further optimized but like I said: this is boring me. Maybe someone would like to pick this up where I left. =) I'm hungry! =S
this is my favorite way to do it.
$pretrimmedrandom = md5(uniqid(mt_rand(),true));
$trimmed = substr($pretrimmedrandom ,0,7);
uniqid uses the current time to generate a very unique random string. results look like "3f456yg".
Given that you mention passwords here, I'll assume you need a secure method (ie: someone shouldn't be able to guess someone else's password based on knowing any other password). You could use the following:
Decide on a master password, for example "MasterPassword"
For each password generated, append to that either a random or sequential nonce, for example "MasterPassword1", "MasterPassword2".
Perform a cryptographic hash on that (SHA, MD5, etc) and covert the hash to a hexadecimal representation, eg "ce7f181a44a4a5b7e43fe2b9a0b1f0c1".
Truncate that to as many characters as you need - perhaps seven as you indicated: "ce7f181".
Check if that has been assigned before. If not, return that as your password. Otherwise, repeat from 2.
If security is not an issue, steps 1 and 2 by themselves would be sufficient. If security is an issue, it is vital that no one but yourself knows the value of "MasterPassword".
Here's how I would solve this problem:
Consider that the 7 characters can be one of 26 letters (abc..z), or 10 numbers (01...9). This makes 36 possible characters.
Each time your application generates a new code, have it increment a global variable. You can turn this unique number into a unique string by using a "Hexatridecimal" converter, and by adding filler characters to make up the rest of the string.
Take a look at this link. I think this guy had the same problem as you:
http://www.codemaxima.com/2010/04/the-hexatridecimal-numbering-system/
$random = substr(hash('md5',openssl_random_pseudo_bytes(32)),0,7);
+1 to #Michael Haren's comment. If the passwords on your site should not have a constraint to be unique.
If I try to use a given password and I get an error that I can't use it because it's already in use, then I know some user on the system has that password. If there are 1000 users I only need to try a max of 1000 other accounts before I find the one who has that password.
Not really answering your question, but more than a comment. So I'm marking this CW.
md5( microtime() );
Galen's answer allows only one use of each character in the password. Not much information in that string. A simple change though:
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$passwordlength = 7;
for ($x = 1; $x <= $passwordlength; $x++) {
$charlist .= $chars;
}
$temp_pw = substr( str_shuffle( $charlist ), 0, $passwordlength );
substr(str_shuffle(md5(microtime())),rand(0,21),7);
try this
echo $unique_key = substr(md5(rand(0, 1000000)), 0, 5);
it will give string with length 5.
Use Kohana text,
http://docs.kohanaphp.com/helpers/text
For example,
$prod_id = text::random('alpha', 7);
If you don't want use the framework, you can simply copy the code. You will find lots of goodies there.
Heres a very simple way
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$temp_pw = substr( str_shuffle( $chars ), 0, 7 );
if ( check_unique( $temp_pw ) ) {
$pw = $temp_pw;
}
You'll have to implement your own check_unique function. That part should be easy.
Related
How does one generate a random float between 0 and 1 in PHP?
I'm looking for the PHP's equivalent to Java's Math.random().
You may use the standard function: lcg_value().
Here's another function given on the rand() docs:
// auxiliary function
// returns random number with flat distribution from 0 to 1
function random_0_1()
{
return (float)rand() / (float)getrandmax();
}
Example from documentation :
function random_float ($min,$max) {
return ($min+lcg_value()*(abs($max-$min)));
}
rand(0,1000)/1000 returns:
0.348 0.716 0.251 0.459 0.893 0.867 0.058 0.955 0.644 0.246 0.292
or use a bigger number if you want more digits after decimal point
class SomeHelper
{
/**
* Generate random float number.
*
* #param float|int $min
* #param float|int $max
* #return float
*/
public static function rand($min = 0, $max = 1)
{
return ($min + ($max - $min) * (mt_rand() / mt_getrandmax()));
}
}
update:
forget this answer it doesnt work wit php -v > 5.3
What about
floatVal('0.'.rand(1, 9));
?
this works perfect for me, and it´s not only for 0 - 1 for example between 1.0 - 15.0
floatVal(rand(1, 15).'.'.rand(1, 9));
function mt_rand_float($min, $max, $countZero = '0') {
$countZero = +('1'.$countZero);
$min = floor($min*$countZero);
$max = floor($max*$countZero);
$rand = mt_rand($min, $max) / $countZero;
return $rand;
}
example:
echo mt_rand_float(0, 1);
result: 0.2
echo mt_rand_float(3.2, 3.23, '000');
result: 3.219
echo mt_rand_float(1, 5, '00');
result: 4.52
echo mt_rand_float(0.56789, 1, '00');
result: 0.69
$random_number = rand(1,10).".".rand(1,9);
function frand($min, $max, $decimals = 0) {
$scale = pow(10, $decimals);
return mt_rand($min * $scale, $max * $scale) / $scale;
}
echo "frand(0, 10, 2) = " . frand(0, 10, 2) . "\n";
This question asks for a value from 0 to 1. For most mathematical purposes this is usually invalid albeit to the smallest possible degree. The standard distribution by convention is 0 >= N < 1. You should consider if you really want something inclusive of 1.
Many things that do this absent minded have a one in a couple billion result of an anomalous result. This becomes obvious if you think about performing the operation backwards.
(int)(random_float() * 10) would return a value from 0 to 9 with an equal chance of each value. If in one in a billion times it can return 1 then very rarely it will return 10 instead.
Some people would fix this after the fact (to decide that 10 should be 9). Multiplying it by 2 should give around a ~50% chance of 0 or 1 but will also have a ~0.000000000465% chance of returning a 2 like in Bender's dream.
Saying 0 to 1 as a float might be a bit like mistakenly saying 0 to 10 instead of 0 to 9 as ints when you want ten values starting at zero. In this case because of the broad range of possible float values then it's more like accidentally saying 0 to 1000000000 instead of 0 to 999999999.
With 64bit it's exceedingly rare to overflow but in this case some random functions are 32bit internally so it's not no implausible for that one in two and a half billion chance to occur.
The standard solutions would instead want to be like this:
mt_rand() / (getrandmax() + 1)
There can also be small usually insignificant differences in distribution, for example between 0 to 9 then you might find 0 is slightly more likely than 9 due to precision but this will typically be in the billionth or so and is not as severe as the above issue because the above issue can produce an invalid unexpected out of bounds figure for a calculation that would otherwise be flawless.
Java's Math.random will also never produce a value of 1. Some of this comes from that it is a mouthful to explain specifically what it does. It returns a value from 0 to less than one. It's Zeno's arrow, it never reaches 1. This isn't something someone would conventionally say. Instead people tend to say between 0 and 1 or from 0 to 1 but those are false.
This is somewhat a source of amusement in bug reports. For example, any PHP code using lcg_value without consideration for this may glitch approximately one in a couple billion times if it holds true to its documentation but that makes it painfully difficult to faithfully reproduce.
This kind of off by one error is one of the common sources of "Just turn it off and on again." issues typically encountered in embedded devices.
Solution for PHP 7. Generates random number in [0,1). i.e. includes 0 and excludes 1.
function random_float() {
return random_int(0, 2**53-1) / (2**53);
}
Thanks to Nommyde in the comments for pointing out my bug.
>>> number_format((2**53-1)/2**53,100)
=> "0.9999999999999998889776975374843459576368331909179687500000000000000000000000000000000000000000000000"
>>> number_format((2**53)/(2**53+1),100)
=> "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
Most answers are using mt_rand. However, mt_getrandmax() usually returns only 2147483647. That means you only have 31 bits of information, while a double has a mantissa with 52 bits, which means there is a density of at least 2^53 for the numbers between 0 and 1.
This more complicated approach will get you a finer distribution:
function rand_754_01() {
// Generate 64 random bits (8 bytes)
$entropy = openssl_random_pseudo_bytes(8);
// Create a string of 12 '0' bits and 52 '1' bits.
$x = 0x000FFFFFFFFFFFFF;
$first12 = pack("Q", $x);
// Set the first 12 bits to 0 in the random string.
$y = $entropy & $first12;
// Now set the first 12 bits to be 0[exponent], where exponent is randomly chosen between 1 and 1022.
// Here $e has a probability of 0.5 to be 1022, 0.25 to be 1021, etc.
$e = 1022;
while($e > 1) {
if(mt_rand(0,1) == 0) {
break;
} else {
--$e;
}
}
// Pack the exponent properly (add four '0' bits behind it and 49 more in front)
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
// Now convert to a double.
return unpack("d", $y | $z)[1];
}
Please note that the above code only works on 64-bit machines with a Litte-Endian byte order and Intel-style IEEE754 representation. (x64-compatible computers will have this). Unfortunately PHP does not allow bit-shifting past int32-sized boundaries, so you have to write a separate function for Big-Endian.
You should replace this line:
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
with its big-endian counterpart:
$z = pack("S", $e << 4) . "\0\0\0\0\0\0";
The difference is only notable when the function is called a large amount of times: 10^9 or more.
Testing if this works
It should be obvious that the mantissa follows a nice uniform distribution approximation, but it's less obvious that a sum of a large amount of such distributions (each with cumulatively halved chance and amplitude) is uniform.
Running:
function randomNumbers() {
$f = 0.0;
for($i = 0; $i < 1000000; ++$i) {
$f += \math::rand_754_01();
}
echo $f / 1000000;
}
Produces an output of 0.49999928273099 (or a similar number close to 0.5).
I found the answer on PHP.net
<?php
function randomFloat($min = 0, $max = 1) {
return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
var_dump(randomFloat());
var_dump(randomFloat(2, 20));
?>
float(0.91601131712832)
float(16.511210331931)
So you could do
randomFloat(0,1);
or simple
mt_rand() / mt_getrandmax() * 1;
what about:
echo (float)('0.' . rand(0,99999));
would probably work fine... hope it helps you.
I generate an unique security code with this every time user login:
$code = substr(str_shuffle(str_repeat("0123456789", 4)), 0, 4);
it seems works but sometimes it generate 3 number instead of 4. also this problem occurred with rand() in past, then i decide to use str_shuffle + str_repeat.
also i insert this code in db with integer data type and length is 6.
what did i wrong or missed?
or is it a bug?
While I can't immediately say why your code sometimes returns only 3 digits, I find myself wondering why you don't create this 4-digit (call it a PIN?) code through the more numerically appropriate rand? For example, since you are going for a 4-digit PIN (between 0000 and 9999), I might write it like:
$code = rand(0, 9999);
$code = substr("000$code", -4);
That is much clearer as to its purpose (generate a random number, guarantee it's 4 digits), and less esoteric than str_repeat/str_shuffle.
EDIT (after learning $code is inserted into an integer DB field)
Why is your random string of 4 digits sometimes turning into 3 digits? Because you are inserting the value into an integer column. Either the DB or the DB Driver will attempt the moral equivalent of:
$code_to_insert = (int)$code;
at which point, if the number is less than 1000, you would get three digits.
Further, if you run your code enough times as it currently stands, you should get PIN lengths of 2 and 1 as well:
0 - 9 = ( 10 / 10000) -> 0.1% of the time
10 - 99 = ( 90 / 10000) -> 0.9% of the time
100 - 999 = ( 900 / 10000) -> 9.0% of the time
1000 - 9999 = (9000 / 10000) -> 90.0% of the time
A possible fix, given the current setup of your code and DB, might be to ensure the PIN length when you pull it out of the DB. You could use the same trick as above:
$sql = "SELECT code FROM ...";
...
$code = $row['code'];
$code = substr("000$code", -4);
Since you're storing the result in an integer field, it's not being stored as separate digits, just as a number. So it doesn't know anything about leading zeroes.
When you later retrieve the value, you can convert it to a string with leading zeroes using the str_pad function:
$code = str_pad($num, 4, '0', STR_PAD_LEFT);
The other option would be to change the datatype in the database to CHAR(4) instead of INT.
Try this:
$code = str_pad($num, 4, '0', STR_PAD_LEFT);
given a script that generates a string of 12 characters randomly generated, how many possibilities there are for two string to be equal?
function rand_string( $length ) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$size = strlen( $chars );
for( $i = 0; $i < $length; $i++ ) {
$str .= $chars[ rand( 0, $size - 1 ) ];
}
return $str;
}
Assuming, A-Za-z0-9, there are 62 possible character values. Therefore, there are 62^12 (to-the-power-of) possible strings. That's roughly 3x10^21 (3 with 21 zeros).
Assuming a perfect random number generator, that's a 1 in 3x10^21 chance that any two particular strings will be equal.
Given that code and a length of 12, there are 6212 possible values. So (assuming a perfectly uniform random number generator, which rand() probably isn't) the chances are 1 in 3226266762397899821056 that a single call to that function will return any arbitrary 12-character string.
OTOH, if you are calling the function repeatedly and want to know how long until you are likely to get a repeat of any previously returned value, you would have to call it about 6.7e+10 times to have a 50% chance of a collision (again, assuming a uniform random number generator). You can get a reasonable approximation of the number of calls required for any collision probability p between 0 and 1 by calculating sqrt(-ln(1 - p) * 2 * 6212).
This falls under the Birth Paradox (how many people do you need in a room to have a 50% chance of two or more people having the same birthday).
Your 12-long 62-char strings come out to be about 72 bits. With the approximate detailed here, you can expect to generate about SQRT((pi / 2) * 62^12)) = 7.112x10^10 strings before getting a collision. So about 1 in 70 billion.
How can I generate a 6 digit unique number? I have verification mechanisms in place to check for duplicate entries.
$six_digit_random_number = random_int(100000, 999999);
As all numbers between 100,000 and 999,999 are six digits, of course.
If you want it to start at 000001 and go to 999999:
$num_str = sprintf("%06d", mt_rand(1, 999999));
Mind you, it's stored as a string.
Another one:
str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
Anyway, for uniqueness, you will have to check that your number hasn't been already used.
You tell that you check for duplicates, but be cautious since when most numbers will be used, the number of "attempts" (and therefore the time taken) for getting a new number will increase, possibly resulting in very long delays & wasting CPU resources.
I would advise, if possible, to keep track of available IDs in an array, then randomly choose an ID among the available ones, by doing something like this (if ID list is kept in memory):
$arrayOfAvailableIDs = array_map(function($nb) {
return str_pad($nb, 6, '0', STR_PAD_LEFT);
}, range(0, 999999));
$nbAvailableIDs = count($arrayOfAvailableIDs);
// pick a random ID
$newID = array_splice($arrayOfAvailableIDs, mt_rand(0, $nbAvailableIDs-1), 1);
$nbAvailableIDs--;
You can do something similar even if the ID list is stored in a database.
Here's another one:
substr(number_format(time() * rand(),0,'',''),0,6);
There are some great answers, but many use functions that are flagged as not cryptographically secure. If you want a random 6 digit number that is cryptographically secure you can use something like this:
$key = random_int(0, 999999);
$key = str_pad($key, 6, 0, STR_PAD_LEFT);
return $key;
This will also include numbers like 000182 and others that would otherwise be excluded from the other examples.
You can also use a loop to make each digit random and generate random number with as many digits as you may need:
function generateKey($keyLength) {
// Set a blank variable to store the key in
$key = "";
for ($x = 1; $x <= $keyLength; $x++) {
// Set each digit
$key .= random_int(0, 9);
}
return $key;
}
For reference, random_int — Generates cryptographically secure pseudo-random integers that are suitable for use where unbiased results are critical, such as when shuffling a deck of cards for a poker game." - php.net/random_int
<?php
$file = 'count.txt';
//get the number from the file
$uniq = file_get_contents($file);
//add +1
$id = $uniq + 1 ;
// add that new value to text file again for next use
file_put_contents($file, $id);
// your unique id ready
echo $id;
?>
i hope this will work fine. i use the same technique in my website.
In PHP 7.0+ I would suggest random_int($min, $max) over mt_rand().
$randomSixDigitInt = \random_int(100000, 999999);
From php.net:
Caution
This function does not generate cryptographically secure values, and should not be used for cryptographic purposes. If you need a cryptographically secure value, consider using random_int(), random_bytes(), or openssl_random_pseudo_bytes() instead.
So this depends mostly on context. I'll also add that as of PHP 7.1.0 rand() is now an alias to mt_rand().
Cheers
$characters = '123456789';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < 6; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
$pin=$randomString;
This will generate random 6 digit number
<?php
mt_rand(100000,999999);
?>
I would use an algorithm, brute force could be as follows:
First time through loop:
Generate a random number between 100,000 through 999,999 and call that x1
Second time through the loop
Generate a random number between 100,000 and x1 call this xt2, then generate a random number between x1 and 999,999 call this xt3, then randomly choose x2 or x3, call this x2
Nth time through the loop
Generate random number between 100,000 and x1, x1 and x2, and x2 through 999,999 and so forth...
watch out for endpoints, also watch out for x1
<?php echo rand(100000,999999); ?>
you can generate random number
You can use $uniq = round(microtime(true));
it generates 10 digit base on time
which is never be duplicated
Try this using uniqid and hexdec,
echo hexdec(uniqid());
Among the answers given here before this one, the one by "Yes Barry" is the most appropriate one.
random_int(100000, 999999)
Note that here we use random_int, which was introduced in PHP 7 and uses a cryptographic random generator, something that is important if you want random codes to be hard to guess. random_bytes was also introduced in PHP 7 and likewise uses a cryptographic random generator.
Many other solutions for random value generation, including those involving time(), microtime(), uniqid(), rand(), mt_rand(), str_shuffle(), array_rand(), and shuffle(), are much more predictable and are unsuitable if the random string will serve as a password, a bearer credential, a nonce, a session identifier, a "verification code" or "confirmation code", or another secret value.
The code above generates a string of 6 decimal digits. If you want to use a bigger character set (such as all upper-case letters, all lower-case letters, and the 10 digits), this is a more involved process, but you have to use random_int or random_bytes rather than rand(), mt_rand(), str_shuffle(), etc., if the string will serve as a password, a "confirmation code", or another secret value. See an answer to a related question, and see also: generating a random code in php?
I also list other things to keep in mind when generating unique identifiers, especially random ones.
This is the easiest method to generate 6 digits random number
$data = random_int(100000, 999999);
echo $data;
I want to create a unique id but uniqid() is giving something like '492607b0ee414'. What i would like is something similar to what tinyurl gives: '64k8ra'. The shorter, the better. The only requirements are that it should not have an obvious order and that it should look prettier than a seemingly random sequence of numbers. Letters are preferred over numbers and ideally it would not be mixed case. As the number of entries will not be that many (up to 10000 or so) the risk of collision isn't a huge factor.
Any suggestions appreciated.
Make a small function that returns random letters for a given length:
<?php
function generate_random_letters($length) {
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= chr(rand(ord('a'), ord('z')));
}
return $random;
}
Then you'll want to call that until it's unique, in pseudo-code depending on where you'd store that information:
do {
$unique = generate_random_letters(6);
} while (is_in_table($unique));
add_to_table($unique);
You might also want to make sure the letters do not form a word in a dictionnary. May it be the whole english dictionnary or just a bad-word dictionnary to avoid things a customer would find of bad-taste.
EDIT: I would also add this only make sense if, as you intend to use it, it's not for a big amount of items because this could get pretty slow the more collisions you get (getting an ID already in the table). Of course, you'll want an indexed table and you'll want to tweak the number of letters in the ID to avoid collision. In this case, with 6 letters, you'd have 26^6 = 308915776 possible unique IDs (minus bad words) which should be enough for your need of 10000.
EDIT:
If you want a combinations of letters and numbers you can use the following code:
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'), ord('z')));
#gen_uuid() by gord.
preg_replace got some nasty utf-8 problems, which causes the uid somtimes to contain "+" or "/".
To get around this, you have to explicitly make the pattern utf-8
function gen_uuid($len=8) {
$hex = md5("yourSaltHere" . uniqid("", true));
$pack = pack('H*', $hex);
$tmp = base64_encode($pack);
$uid = preg_replace("#(*UTF8)[^A-Za-z0-9]#", "", $tmp);
$len = max(4, min(128, $len));
while (strlen($uid) < $len)
$uid .= gen_uuid(22);
return substr($uid, 0, $len);
}
Took me quite a while to find that, perhaps it's saves somebody else a headache
You can achieve that with less code:
function gen_uid($l=10){
return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyz"), 0, $l);
}
Result (examples):
cjnp56brdy
9d5uv84zfa
ih162lryez
ri4ocf6tkj
xj04s83egi
There are two ways to obtain a reliably unique ID: Make it so long and variable that the chances of a collision are spectacularly small (as with a GUID) or store all generated IDs in a table for lookup (either in memory or in a DB or a file) to verify uniqueness upon generation.
If you're really asking how you can generate such a short key and guarantee its uniqueness without some kind of duplicate check, the answer is, you can't.
Here's the routine I use for random base62s of any length...
Calling gen_uuid() returns strings like WJX0u0jV, E9EMaZ3P etc.
By default this returns 8 digits, hence a space of 64^8 or roughly 10^14,
this is often enough to make collisions quite rare.
For a larger or smaller string, pass in $len as desired. No limit in length, as I append until satisfied [up to safety limit of 128 chars, which can be removed].
Note, use a random salt inside the md5 [or sha1 if you prefer], so it cant easily be reverse-engineered.
I didn't find any reliable base62 conversions on the web, hence this approach of stripping chars from the base64 result.
Use freely under BSD licence,
enjoy,
gord
function gen_uuid($len=8)
{
$hex = md5("your_random_salt_here_31415" . uniqid("", true));
$pack = pack('H*', $hex);
$uid = base64_encode($pack); // max 22 chars
$uid = ereg_replace("[^A-Za-z0-9]", "", $uid); // mixed case
//$uid = ereg_replace("[^A-Z0-9]", "", strtoupper($uid)); // uppercase only
if ($len<4)
$len=4;
if ($len>128)
$len=128; // prevent silliness, can remove
while (strlen($uid)<$len)
$uid = $uid . gen_uuid(22); // append until length achieved
return substr($uid, 0, $len);
}
Really simple solution:
Make the unique ID with:
$id = 100;
base_convert($id, 10, 36);
Get the original value again:
intval($str,36);
Can't take credit for this as it's from another stack overflow page, but I thought the solution was so elegant and awesome that it was worth copying over to this thread for people referencing this.
You could use the Id and just convert it to base-36 number if you want to convert it back and forth. Can be used for any table with an integer id.
function toUId($baseId, $multiplier = 1) {
return base_convert($baseId * $multiplier, 10, 36);
}
function fromUId($uid, $multiplier = 1) {
return (int) base_convert($uid, 36, 10) / $multiplier;
}
echo toUId(10000, 11111);
1u5h0w
echo fromUId('1u5h0w', 11111);
10000
Smart people can probably figure it out with enough id examples. Dont let this obscurity replace security.
I came up with what I think is a pretty cool solution doing this without a uniqueness check. I thought I'd share for any future visitors.
A counter is a really easy way to guarantee uniqueness or if you're using a database a primary key also guarantees uniqueness. The problem is it looks bad and and might be vulnerable. So I took the sequence and jumbled it up with a cipher. Since the cipher can be reversed, I know each id is unique while still appearing random.
It's python not php, but I uploaded the code here:
https://github.com/adecker89/Tiny-Unique-Identifiers
Letters are pretty, digits are ugly.
You want random strings, but don't want "ugly" random strings?
Create a random number and print it in alpha-style (base-26), like the reservation "numbers" that airlines give.
There's no general-purpose base conversion functions built into PHP, as far as I know, so you'd need to code that bit yourself.
Another alternative: use uniqid() and get rid of the digits.
function strip_digits_from_string($string) {
return preg_replace('/[0-9]/', '', $string);
}
Or replace them with letters:
function replace_digits_with_letters($string) {
return strtr($string, '0123456789', 'abcdefghij');
}
You can also do it like tihs:
public static function generateCode($length = 6)
{
$az = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$azr = rand(0, 51);
$azs = substr($az, $azr, 10);
$stamp = hash('sha256', time());
$mt = hash('sha256', mt_rand(5, 20));
$alpha = hash('sha256', $azs);
$hash = str_shuffle($stamp . $mt . $alpha);
$code = ucfirst(substr($hash, $azr, $length));
return $code;
}
You can do that without unclean/costy stuff like loops, String concatenations or multiple calls to rand(), in a clean and easy to read way. Also, it is better to use mt_rand():
function createRandomString($length)
{
$random = mt_rand(0, (1 << ($length << 2)) - 1);
return dechex($random);
}
If you need the String to have the exact length in any case, just pad the hex number with zeros:
function createRandomString($length)
{
$random = mt_rand(0, (1 << ($length << 2)) - 1);
$number = dechex($random);
return str_pad($number, $length, '0', STR_PAD_LEFT);
}
The "theoretical backdraw" is, that you are limited to PHPs capabilities - but this is more a philosophical issue in that case ;) Let's go through it anyways:
PHP is limited in what it can represent as a hex number doing it like this. This would be $length <= 8 at least on a 32bit system, where PHPs limitation for this should be 4.294.967.295 .
PHPs random number generator also has a maximum. For mt_rand() at least on a 32bit system, it should be 2.147.483.647
So you are theoretically limited to 2.147.483.647 IDs.
Coming back to the topic - the intuitive do { (generate ID) } while { (id is not uniqe) } (insert id) has one drawback and one possible flaw that might drive you straight to darkness...
Drawback: The validation is pessimistic. Doing it like this always requires a check at the database. Having enough keyspace (for example length of 5 for your 10k entries) will quite unlikely cause collisions as often, as it might be comparably less resource consuming to just try to store the data and retry only in case of a UNIQUE KEY error.
Flaw: User A retrieves an ID that gets verified as not taken yet. Then the code will try to insert the data. But in the meantime, User B entered the same loop and unfortunately retrieves the same random number, because User A is not stored yet and this ID was still free. Now the system stores either User B or User A, and when attempting to store the second User, there already is the other one in the meantime - having the same ID.
You would need to handle that exception in any case and need to re-try the insertion with a newly created ID. Adding this whilst keeping the pessimistic checking loop (that you would need to re-enter) will result in quite ugly and hard to follow code. Fortunately the solution to this is the same like the one to the drawback: Just go for it in the first place and try to store the data. In case of a UNIQUE KEY error just retry with a new ID.
Take a lookt at this article
Create short IDs with PHP - Like Youtube or TinyURL
It explains how to generate short unique ids from your bdd ids, like youtube does.
Actually, the function in the article is very related to php function base_convert which converts a number from a base to another (but is only up to base 36).
10 chars:
substr(uniqid(),-10);
5 binary chars:
hex2bin( substr(uniqid(),-10) );
8 base64 chars:
base64_encode( hex2bin( substr(uniqid(),-10) ) );
function rand_str($len = 12, $type = '111', $add = null) {
$rand = ($type[0] == '1' ? 'abcdefghijklmnpqrstuvwxyz' : '') .
($type[1] == '1' ? 'ABCDEFGHIJKLMNPQRSTUVWXYZ' : '') .
($type[2] == '1' ? '123456789' : '') .
(strlen($add) > 0 ? $add : '');
if(empty($rand)) $rand = sha1( uniqid(mt_rand(), true) . uniqid( uniqid(mt_rand(), true), true) );
return substr(str_shuffle( str_repeat($rand, 2) ), 0, $len);
}
If you do like a longer version of unique Id use this:
$uniqueid = sha1(md5(time()));
Best Answer Yet: Smallest Unique "Hash Like" String Given Unique Database ID - PHP Solution, No Third Party Libraries Required.
Here's the 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 200 maps to $base36value\n";
$database_id = 1;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1 maps to $base36value\n";
$database_id = 1987645;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1987645 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;
}
?>