When a user signs up to my website, for example, I create a unique string of length 40 and insert it into the database. We then send an email to the newly signed up user containing this unique string in the form of an URL, and this all works fine.
But how do you create and insert this signup entity ensuring that the field with the unique property is actually unique?
My current approach is as follows:
$key = '';
$repeat = true;
while ($repeat) {
$key = generate_hash(40);
$is_unique = (count($model->get_by_key($key)) === 0) ? true : false;
$repeat = ($is_unique) ? false : true;
}
// At this point I know the key is unique and can insert it...
I am not happy with this code, as I try to avoid while loops in general. Is there a better way to achieve what I am doing here?
The bottom line is that the MySQL field containing these keys has the unique property and I need to know that no unique-violations will occur when inserting these keys.
Ok, basically you have two approaches.
Let the DB handle this and use UUID() to create a unique identifier that you can use in the emails.
According to your MySQL version you have some options:
A. With MySQL 4.1 and above you can use the function directly with INSERT INTO:
INSERT INTO table_name
(unique_id, field1, field2)
VALUES
(UUID(), "smth", "smth")
B. With MySQL 5.0 you can create a TRIGGER and have the unique field automatically filled upon insertion:
CREATE TRIGGER
unique_id
BEFORE INSERT ON
table_name
FOR EACH ROW
SET NEW.unique_id = UUID()
You can check programatically if the the generated id is really unique. The same you are doing now but you can refactor the code a little bit like so:
$key = generate_hash(40);
while ( count($model->get_by_key($key)) ) {
$key = generate_hash(40);
}
//here you have an unique id in $key
There are built in unique id functions in almost every language for that.In php,
echo uniqid();
You can generate 256^40 different 40-characters strings.
Sun will explode before you generate 2 identical strings.
You can use md5(time()) as your unique value instead. md5 of time wouldn't be same for each user submitting.
$time = time();
$md5 = md5($time);
As long as md5() just containing 32 string length, you can add an unique word after the md5().
$unique = $md5."uniq1234";
Now you have 40 unique string length. At last you can add $unique as your unique value in database
You can use this function to generate uniqueID ,
function keygen($length=10)
{
$key = '';
list($usec, $sec) = explode(' ', microtime());
mt_srand((float) $sec + ((float) $usec * 100000));
$inputs = array_merge(range('z','a'),range(0,9),range('A','Z'));
for($i=0; $i<$length; $i++)
{
$key .= $inputs{mt_rand(0,61)};
}
return $key;
}
echo keygen(10);
Related
I need to generate unique custom reference ids for each user based on their user id. Right now I'm using the md5 method for this and have limited the length to 12 digits/characters.
$user_id = '120';
$ref_id = substr(md5($user_id, 0, 12);
I know there are many ways to generate a string from another string, but what would be the best way to generate a simple but unique user ID with a relatively short length (max. 16 chr/digits)?
The ID is used to preserve and mask the true user ID or the name of the user in publications.
why not just use their ID number?
Because user ids do not have the same scheme and are sequential. I want all of my users to have let’s say a 16 chr/digit but unique ref ID. It's not really about security, but here the uniqueness is in the foreground. I just selected MD5 for the staging server. I am open for any other advice.
From the conversation in comments, a good solution would be to generate a unique random key and associate it with the value (such as the database row).
This random key is neither an encryption or a hash. This key is a unique reference variable.
So for each user you have their database membership row, and one column would be "reference_key"; this can be populated when a unique value associated only with this account,
For Example
The below code will generate a unique key and save it to the array value $saver['reference_key'] , you can then insert this into your database when you save your other customer data.
The MySQL column should be UNIQUE indexed and UTF8mb4 collation and character set.
The reason that the testing checks if the value exists already would be its easier to re roll the value before the MySQL error is triggered when an identical value is tried to be inserted into the UNIQUE column.
function nonce_generator($length = 40)
{
// Source can be any valid characters you want to use.
$source = 'abcdefghijklmnopqrstuvwxyzANCEDFGHIJKLMNOPQRSTUVWXYZ0123456789!-=+';
$max = strlen($source) - 1;
$i = 0;
$output = "";
do {
$output .= $source[random_int(0,$max)];
$i++;
}
while($i < $length);
return $output;
}
...
do {
$found = false;
$saver['reference_key'] = nonce_generator(12); //see function above to generate a key.
$check = $dataBase->getSelect("SELECT COUNT(*) as numb FROM customer WHERE reference_key = ? ", $saver['reference_key']);
if ($check['numb'] > 0) {
// check if key already exists in the database.
$found = true;
}
unset($check);
} while ($found === true);
// once a unique key has been found then save this to the database user.
// along with all other user details.
$dataBase->arrayToSQLInsert("customer", $saver);
Please note that for this code uses customised database interactions and is for illustration purposes ONLY
Advantages:
Unique key does not reference any other customer data.
Unique key is not a hash or encryption so can not be 'compromised'.
Key is assured to be unique.
Disadvantages:
On very large data sets the do/while process of finding a unique value may cause a slight slowdown
I need to generate key codes for users so i need that to be unique in db.
I need to repeat this cycle until key is unique:
create key;
if($key is not in db){
insert key;
}else{
repeat control with new key;
}
i thought about while() but can't understand how to implement that.
Other method to insert db unique keys and update db unique key could be written in sql queries directly instead of using php script? (INSERT/UPDATE UNIQUE KEYS with Sql Query)
hoping is clear the question.
$key = create_unique_key();
while( this_key_exists($key) )
{
$key = create_unique_key();
}
//If you got here - the value of $key is unique and doesn't exist in db
Explanation:
You first create a unique key using the custom function create_uniuqe_key, so now
we have a starting value for $key.
Second , we have a while loop , please remember:
while( expr )
as long the expression return true we will get into the loop.
So , as long as our custom function this_key_exists (which return true or false) returns true (which means - the key is not that uniuqe - exists in the database) we will create a new unique key and check it again and again.
Try just using UUID
or add a unique constraint on the column in the database and keep trying to insert the row until there's no mysql error
do {
//insert using uniqid() or some random string
} while( $check_for_mysql_error );
/**
* Creates a unique URL for a page language by adding a 'dash number'
* at the end of the provided user URL
* #param string $page The current page
* #param string $url The URL to make unique
* #param type $lang The language to make unique for (you can have the same URL for same page in diffrent languages)
* #return string The new URL
*/
function uniquePageUrl($page, $url, $lang){
while(TRUE){
$result = $this->db->from('pages')->where('page <>', $page)->where('lang',$lang)->where('url',$url)->limit(1)->get()->row();
if($result) //we already have a page URL for this language like the one we try to use
$url = increment_string($url,'-');
else
return $url;
}
}
I'm trying to create a function that, when run, creates numerical ID, checks to see if that ID exists in the DB and if it does exists then create another ID and check again until it has a unique ID. I'm stuck on how to loop in the functions withing a function
function createUniqueID() {
function buildUnique() {
$uniqueID = rand(100000000000,999999999999);
return $uniqueID;
}
function compareWithDB($uniqueID) {
$s = "SELECT id FROM table WHERE id='{$uniqueID}'";
$r = mysql_query($s);
return $r;
}
function countDBRows($r) {
if(mysql_num_rows($r) >0){
$f = false; // found
} else{
$f = true;
}
}
$uniqueID = buildUnique();
$r = compareWithDB($uniqueID);
$f = countDBRows($r);
if (!$f) {
$uniqueID = 'nope';
}
return $uniqueID;
}
You're much better off to call MySQL's UUID() function, and store & return the value of that, unless the value absolutely must be numeric.
SELECT UUID();
If you want a unique number, then just use AUTO_INCREMENT
If you want a unique random number (why?) create a unique index on the ID column and keep trying until you get no errors. This is better for concurrency: multiple concurrent calls can have the same number and pass the test. Better still, let the DB engine do it with RAND...
Theres another way too,
You can use the sha1() function in conjuction with an static variable as shown:
function GetUniqueID() {
static $salt=1;
$id = sha1("somestring".(string)$salt);
$salt++;
return $id;
}
Here $salt being an static one retains its value between all calls incrementing ensuring unique ID's (hash of SHA1).for more security "somestring" can be made randomized also .
Might I suggest the much easier and more efficient uniqid function which will do this for you. It generates guaranteed unique IDs based on the timestamp in milliseconds. The generated Id is 13 digits long unless you decide to add a prefix or use extra 'entropy' (more uniqueness).
Edit: Note that this is both numbers and letters returned by the function.
http://php.net/manual/en/function.uniqid.php
Edit 2:
Using your method here is how to nest the loops...
while (true) {
$uniqueID = buildUnique();
$r = compareWithDB($uniqueID)
if (countDBRows($r)) { break; }
}
Your unique ID is then stored in $uniqueID. I do however discourage this because it's bulky and inefficient... but there you go! :)
I'm answering my own question because I have found a solution of creating a big number that (likely) will never duplicate. I use three random 2-digit variables and three different date syntax.
Putting them together makes it clear that they will never duplicate unless someone happens to post during the same year, week and with the same number of seconds in the minute an also have 3 different random number all be the same.
I think the chances of this happening would be in the millions and if there are millions of posts happening then i'm sure I will have more resources to solve this issue.
$rand1 = rand(10,99);
$rand2 = rand(10,99);
$rand3 = rand(10,99);
$date1 = date("s"); // seconds
$date2 = date("y"); // 2 digit year
$date3 = date("W"); // week number (out of 52)
$uniqueID = $date1.$rand1.$date2.$rand2.$date3.$rand3;
return $uniqueID;
i'm trying to generate a unique id using this code:
$RCode = md5(uniqid(rand(), true));
then i want it to check in my database if the RCode is unique.
if it isnt i want it to use that part of the code again and check in my database again if it is unique,
if it is unique it should write to my database.
i have all the code i need for checking and writing into the database, i just have no idea how to make it loop back to the start.
Help is appreciated!
Thanks a lot in advance!
Don't bother checking at first. Instead put a unique constraint on the column, that way the insert will fail if the RCode isn't unique. Then you can handle that error/exception and try another hash. The probability of a collision is low so in this case you probably aren't going to be hammering the database.
Typical example for do-while loop.
Some PHP-pseudocode:
do {
$rcode = md5(uniqid(rand(), true));
$res = mysql_result(mysql_query("SELECT COUNT(*) FROM records WHERE rcode='$rcode'"));
} while ($res[0] > 0);
mysql_query("INSERT INTO records (rcode) VALUES ('$rcode')");
$found = false;
while (! $found) {
//try..
if (...unique...) {
$found = true;
}
}
Going to the start is as easy as implementing a while loop. Heck, you could even use goto (kidding!).
But I don't understand why you don't want to use auto_increment.
You can use the "loop forever then break out on success" method:
while (true) {
$RCode = ...;
if ($RCode does not exist in db) {
break;
}
}
write to the db
Edit: Or better, make sure the field has a unique constraint on it, then test for uniqueness by checking for failure of an insert:
while (true) {
$RCode = ...
try to insert RCode
if (no failure) {
break;
}
}
This will be more resilient to concurrent hits.
This answer is a little different to what you asked but it solves the problem of a unique ID in a different way that may be better to use depending on your application.
To be honest I like to use variables such as date and time combined with another variable such as an IP address for this type of thing, you can then be very certain that your ID will be unique because the date and time will not reoccur and in the event there are 2 requests in the same second the IP address of the user is completely unique at this time also. Plus no having to check with the database. An example would be
$idstring = date('Ymdhis');
$ipstring = $_SERVER['REMOTE_HOST'];
$hashme = $idstring.$ipstring;
$idhash = md5($hashme);
I hope this is helpful.
As you hash uniqid() returned value using md5() (the same goes for any other hashing algorithm), possibility of getting not unique string is extremely low.
In my opinion, its so low that checking for that string to be unique would be overkill.
All you need to do is insert value in the database. It will be unique!
Edit:
You should use uniqid(null, true) to get 23 chars long, unique string. If that is what you need - unique string.
you can just run MD5 on the timestamp...
$uniqueid = md5(time());
First, don't md5 uniqueid. uniqueid is good enough and it does not have the overhead of md5 if used by itself.
Second, you are far better off obscuring an automatically incrementing number than using a UUID or some equivalent.
But, if you must:
do {
$rcode = uniqid(rand(), true);
$res = mysql_query("SELECT COUNT(*) FROM records WHERE rcode='$rcode'");
} while ($res && mysql_num_rows($res));
mysql_query("INSERT INTO records (rcode) VALUES ('$rcode')");
// OR!
// assuming unique index on rcode
do {
$rcode = uniqid(rand(), true);
} while (mysql_query("INSERT INTO records (rcode) VALUES ('$rcode')"););
I'm using PHP and MySQL and
I have a table with 3 fields ((ID, Username, PID)).
I want the PID field to contain strings of 8 unique characters.
My solution is to generate the random string in PHP and check if it exists. If it exists then it will generate another string.
Is there any better solution that will save processing time, like a MySQL trigger or something like that?
This will give you a random 8 character string:
substr(str_pad(dechex(mt_rand()), 8, '0', STR_PAD_LEFT), -8);
Found here: http://www.richardlord.net/blog/php-password-security
Or if the username field is unique you could also use:
substr(md5('username value'), 0, 8);
Though it's extremely unlikely, particularly for the md5, neither case guarantees a unique string, so I would probably do something like this:
// Handle user registration or whatever...
function generatePID($sUsername) {
return substr(md5($sUsername), 0, 8);
}
$bUnique = false;
$iAttempts = 0;
while (!$bUnique && $iAttempts < 10) {
$aCheck = $oDB->findByPID(generatePID("username value")); // Query the database for a PID matching whats generated
if (!$aCheck) { // If nothing is found, exit the loop
$bUnique = true;
} else {
$iAttempts++;
}
}
// Save PID and such...
... which would probably only yield 1 'check' query, maybe 2 in unique cases, and would ensure a unique string.
Do the characters need to be random? Or just unique? If they only need to be unique, you could use a timestamp. Basing the value on time will ensure a uniqueness.
If you go another route, you'll have to check your generated value against the database until you end up with a unique value.
Why not do this the correct way and use UUIDs (aka GUIDs), which are always unique, no need to check if they are or not. It may be 36 chars, but you get the benefit of storing them as HEX which saves disk space and increase speed over standard CHAR data.
You can read the comments on the PHP doc for functions that do this.
You can create 8 chars unique string in Mysql in such a way
CAST(MD5(RAND()) as CHAR(8))
My solution is to generate the random string in PHP and check if it exists. If it exists then it will generate another string.
This is the wrong way to do it. The web server will run multiple instances of your code concurrently, and sooner or later, two instances will store the same PID in your database.
The correct way to solve this problem is to make the PID column UNIQUE, and don't bother with any pre-checks. Just run the INSERT query, and check the result.
If the result is a 1062 (ER_DUP_ENTRY) error, generate a new PID and try again.
Any other database error should be dealt with like you normally would.
Perhaps something like this (untested):
<?php
/* $link = MySQLi connection */
if (!($stmt = mysqli_prepare ('INSERT `t` (`ID`, `Username`, `PID`) VALUES (?, ?, ?)'))) {
/* Prepare error */
}
if (!mysqli_bind_param ('iss', $id, $user, $pid) {
/* Bind error */
}
$e = 0;
for ($i = 0; $i < 10; $i++) {
$pid = /* generate random string */;
if (mysqli_stmt_execute ($stmt))
break; /* success */
$e = mysqli_stmt_errno ($stmt);
if ($e !== 1062)
break; /* other error */
}
mysqli_stmt_close ($stmt);
if ($e) {
if ($e === 1062) {
/* Failed to generate unique PID */
} else {
/* Other database error */
}
} else {
/* success */
}
If you're set on 8 characters for the PID value then you'll need something to generate the string and check that it doesn't already exist.
$alphabet = range('A','Z');
// get all the PIDs from the database
$sql = "select PID from mytable";
// save those all to an array
$pid_array = results of query saved to array
shuffle($alphabet);
$pid_offer = array_slice($alphabet,0,8);
while(in_array($pid_offer, $pid_array)){
shuffle($alphabet);
$pid_offer = array_slice($alphabet,0,8);
}
// found uniuqe $pid_offer...
race conditions still exist.
If the string doesn't need to be random, then use the ID value, which is probably an auto-increment integer and start the count for that at 10000000.
Then just do a simple A=1, B=2, C=3 etc replacement on the digits in that number to generate your string.
Your mileage may vary.
--Mark