Generating Strong Unique User ID's w/PHP & MySQL - php

Ahoy Stack Overflow! This be mai first post...
I'm attempting to identify users with a salted unique public key.
Algorithm - Should I use uniqid(), sha256, sha512, something else? All hashes will be salted. NIST recommended SHA256, but I prefer to hear what others might suggest.
Generation - Does hash(SALT + AUTO_INCREMENT_PK + CREATED_TIMESTAMP) suffice? More entropy?
I'd use email, as it is unique for each user, however the user can modify their email address. I was also considering storing signup_email so that hashes would not have to be re-calculated.
MySQL Storage - Currently, our ID's are INT(255) auto_increment primary key's. As stated earlier, potentially hundreds of millions of keys. Depending on the crypto algo, I should have a fixed-size ID. Can I keep INT(255) or should I use CHAR(n)?
---------------------- Thanks for reading :) -------------------------------

One thing: If you don't trust the users with their IDs, sending them over GET or POST will not work; those are all visible to motivated users.
I would use SHA256 using a salt.counter.time string, and use the output to generate GUIDs for the actual id. This would minimize the possibility for collisions.
You will have to use CHAR for MySQL to store GUIDs.
See the comments at http://us2.php.net/manual/en/function.uniqid.php for more in-depth info. AFAIK GUID is not part of the PHP core so you have to fake it a bit.

If you are using user id as the way to allow a user to do anything with your service, if one user "guesses" the user id of another one, he'll be able to do whatever he wants with that one's account ?
You do not have any kind of other password or anything to go along that ?
Well, in that case, you need something quite unique, don't you ;-)
(Hoping I understood the question well -- but that might not be the case -- sorry, if it isn't)
What do you think of using Globally Unique Identifier (like, for instance, 61350955-9755-4AF3-8C19-6DBC42CA69E2) for your users ?
For an example of how they look like, take a look at http://createguid.com/
As a sidenote, that GUID is quite long ; which means lots of bytes in your DB, if you have millions users... So, it probably shouldn't be used as any kind of primary/foreign key.
What about using the smallest possible integer (that fits the number of users you'll have) as primary/foreign key, as that one will be duplicated in many places of the application ; and only have the "long user id" stored only once, in your user table ?

I wrote this class that gives you an unique id of 24 chars, compatible with the id field of MongoDB (and using the same logic to construct it). Might be useful in the future.
<?php
/**
* Generator for Mongo-like ObjectIds in pure PHP
* Author: Mauricio Piacentini
*
* Inspired by https://github.com/justaprogrammer/ObjectId.js
*
*/
class ObjectIdFactory
{
private $_datetime = null;
private $_machine = null;
private $_pid = null;
private $_increment = null;
public function __construct()
{
$this->_machine = str_pad(dechex(rand(0, 16777215)), 6, "0", STR_PAD_LEFT);
$this->_pid = str_pad(dechex(rand(0, 32767)), 4, "0", STR_PAD_LEFT);
$this->_increment = rand(0, 16777215);
//We need a DateTime object to get timestamps, cache it
$this->_datetime = new DateTime();
}
public function getNewId($forcedincrement = null)
{
if (is_null($forcedincrement)) {
$this->_increment++;
if ($this->_increment > 0xffffff) {
$this->_increment = 0;
}
} else {
$this->_increment = $forcedincrement;
}
$timestamp = $this->_datetime->getTimestamp();
$timestamp_final = str_pad(dechex($timestamp), 8, "0", STR_PAD_LEFT);
$increment_final = str_pad(dechex($this->_increment), 6, "0", STR_PAD_LEFT);
return $timestamp_final . $this->_machine . $this->_pid . $increment_final;
}
}
https://github.com/piacentini/ObjectId.php

Have you looked into using a UUID?
A quick google search yields some good resources/links.

Personally I use md5(uniqid(mt_rand(), true)) which will create 32 character identifier (a 128 bit hex number) that is extremely difficult to predict.

Related

best practice to generate random token for forgot password

I want to generate identifier for forgot password . I read i can do it by using timestamp with mt_rand(), but some people are saying that time stamp might not be unique every time. So i am bit of confused here. Can i do it with using time stamp with this ?
Question
What's best practice to generate random/unique tokens of custom length?
I know there are lot of questions asked around here but i am getting more confused after reading different opinion from the different people.
In PHP, use random_bytes(). Reason: your are seeking the way to get a password reminder token, and, if it is a one-time login credentials, then you actually have a data to protect (which is - whole user account)
So, the code will be as follows:
//$length = 78 etc
$token = bin2hex(random_bytes($length));
Update: previous versions of this answer was referring to uniqid() and that is incorrect if there is a matter of security and not only uniqueness. uniqid() is essentially just microtime() with some encoding. There are simple ways to get accurate predictions of the microtime() on your server. An attacker can issue a password reset request and then try through a couple of likely tokens. This is also possible if more_entropy is used, as the additional entropy is similarly weak. Thanks to #NikiC and #ScottArciszewski for pointing this out.
For more details see
http://phpsecurity.readthedocs.org/en/latest/Insufficient-Entropy-For-Random-Values.html
This answers the 'best random' request:
Adi's answer1 from Security.StackExchange has a solution for this:
Make sure you have OpenSSL support, and you'll never go wrong with this one-liner
$token = bin2hex(openssl_random_pseudo_bytes(16));
1. Adi, Mon Nov 12 2018, Celeritas, "Generating an unguessable token for confirmation e-mails", Sep 20 '13 at 7:06, https://security.stackexchange.com/a/40314/
The earlier version of the accepted answer (md5(uniqid(mt_rand(), true))) is insecure and only offers about 2^60 possible outputs -- well within the range of a brute force search in about a week's time for a low-budget attacker:
mt_rand() is predictable (and only adds up to 31 bits of entropy)
uniqid() only adds up to 29 bits of entropy
md5() doesn't add entropy, it just mixes it deterministically
Since a 56-bit DES key can be brute-forced in about 24 hours, and an average case would have about 59 bits of entropy, we can calculate 2^59 / 2^56 = about 8 days. Depending on how this token verification is implemented, it might be possible to practically leak timing information and infer the first N bytes of a valid reset token.
Since the question is about "best practices" and opens with...
I want to generate identifier for forgot password
...we can infer that this token has implicit security requirements. And when you add security requirements to a random number generator, the best practice is to always use a cryptographically secure pseudorandom number generator (abbreviated CSPRNG).
Using a CSPRNG
In PHP 7, you can use bin2hex(random_bytes($n)) (where $n is an integer larger than 15).
In PHP 5, you can use random_compat to expose the same API.
Alternatively, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM)) if you have ext/mcrypt installed. Another good one-liner is bin2hex(openssl_random_pseudo_bytes($n)).
Separating the Lookup from the Validator
Pulling from my previous work on secure "remember me" cookies in PHP, the only effective way to mitigate the aforementioned timing leak (typically introduced by the database query) is to separate the lookup from the validation.
If your table looks like this (MySQL)...
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id)
);
... you need to add one more column, selector, like so:
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
selector CHAR(16),
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id),
KEY(selector)
);
Use a CSPRNG When a password reset token is issued, send both values to the user, store the selector and a SHA-256 hash of the random token in the database. Use the selector to grab the hash and User ID, calculate the SHA-256 hash of the token the user provides with the one stored in the database using hash_equals().
Example Code
Generating a reset token in PHP 7 (or 5.6 with random_compat) with PDO:
$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);
$urlToEmail = 'http://example.com/reset.php?'.http_build_query([
'selector' => $selector,
'validator' => bin2hex($token)
]);
$expires = new DateTime('NOW');
$expires->add(new DateInterval('PT01H')); // 1 hour
$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
'userid' => $userId, // define this elsewhere!
'selector' => $selector,
'token' => hash('sha256', $token),
'expires' => $expires->format('Y-m-d\TH:i:s')
]);
Verifying the user-provided reset token:
$stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()");
$stmt->execute([$selector]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
$calc = hash('sha256', hex2bin($validator));
if (hash_equals($calc, $results[0]['token'])) {
// The reset token is valid. Authenticate the user.
}
// Remove the token from the DB regardless of success or failure.
}
These code snippets are not complete solutions (I eschewed the input validation and framework integrations), but they should serve as an example of what to do.
You can also use DEV_RANDOM, where 128 = 1/2 the generated token length. Code below generates 256 token.
$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
This may be helpful whenever you need a very very random token
<?php
echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>

Unique Key Generation Logic

I am planning to build, for lack of a better term, a multi user Customer Relationship Manager (CRM) and I want to create a unique identifier that is easy to transmit in email, via text, and verbally to other team members.
For Example:
I upload my list of 100 customers and John Smith and his phone number are included in that list. Upon upload, I want to generate a hidden fingerprint / unique identifier for John Smith in the database, and then propagate a 12 digit number that can be shared publicly.
In my mind like this - john smith + ph: 5557898095 = fingerprint: 7e013d7962800374e6e67dd502f2d7c0 displays to end user id number: 103457843983
My question is - what method or process should I use to take the name and phone number, generate a hidden key, and then translate to a displayable key that is linked to the hidden one?
I hope this clear. I mainly want to use the right logic process.
You could use crc32('fingerprint') for the end user id number:
<?php
echo printf("%u", crc32('7e013d7962800374e6e67dd502f2d7c0')); //226407465310
?>
I don't understand what your problem really is, but I'll try.
It seems like you mean something like this:
A SQL table which saves the public and private ID (and maybe other things).
You can generate a key like this:
$chars = '0123456789abcedfghijklmnopqrstuvwxyz';
function generateKey($length, $charsLength = 10) {
global $chars;
$key = '';
for($i=0;$i<$length;++$i) {
$key .= $chars[rand(0, $charsLength - 1)];
}
return $key;
}
$keyPublic = generateKey(10); // Public key with length 10
// Now check if the key already exist
while(mysql_num_rows(mysql_select('SELECT publicKey FROM keys WHERE publicKey = \''.$keyPublic.'\')) === 1) {
$keyPublic = generateKey(10);
}
$keyPrivate = generateKey(10, 36); // Private key with length 10
// Now check if the key already exist
while(mysql_num_rows(mysql_select('SELECT privateKey FROM keys WHERE privateKey = \''.$keyPrivate.'\')) === 1) {
$keyPrivate = generateKey(10, 36);
}
In this example there are two keys generated and it is checked if the keys already exist. (in the example in the table "keys").
Assuming your real ID is the auto_incremented field in your customer table, then just have a second table that maps your public ID to the real ID.
Assuming you're using some sort of hashing algorithm to generate your public ID, it'd be a simple process to do a lookup on that table when you create a new user to detect a clash with an existing user, then regenerate a new ID until there's no clash (e.g. include system time as part of your hash input, then just keep regenerating until you find a unique ID)

php unique identifier for clients

I have a unique identifier for client i generate everytime a client request a service support.
once the id is generate, the id is inserted in the database
here my code:
function makeUnique() {
$start_time = uniqid(microtime(1));
$duration = sprintf('%0.24f', $start_time);
return date('dmYHis').$duration;
}
echo makeUnique();
this output: 071120112032291320715949.928639888763427734375000
for some reason i get 071120112032291320715949 as the number. what am i doing wrong?
you need to remove the "." (dot) in your function. you are probably using an INT which remove numbers after the dot.
return str_replace('.', '', date('dmYHis').$duration);
and make sure the field is big enough - like varchar(50)
my recommendation will be to simply hash the client id using md5
md5($clientid)
and you have the mysql field as a char(32)
You've already got your solution in your code. The php function uniqid() provides... well, a unique ID. If you want to ensure it's really really really really unique (though it's not necessary), just append time() to the end, like so:
return uniqid().time();
This would also return letters mixed in with numbers (higher entropy), like this response:
4eb8895a76edc1320716634

Unique, unpredictable, 12 digit, integer id

How would I go about generating this... I want to keep my primary key sequential and have a 12 digit unique pin generated for each new object added to the database.
The reason it cant just be autoincrement is i don't want the sequential numbers to be easily guessable.
It needs to be integer numbers, because I'm going to have verification codes that need to be dialed on a phone pad.
Use a concatenation of a unique incremented number and a randomly generated number.
The unique incremented number ensures that the result is unique, and the randomly generated number makes it hardly guessable.
This is simple and guaranteed to have no collision (1). The result is incremental, partly random, and non-predictable (provided that the random number part is generated with a good PRNG).
(1): You have to either pad id and random with zeros, or to separate them with some non-digit character.
With a MySQL db, this translates to:
CREATE TABLE foo (
id int not null auto_increment,
random int not null,
...
primary key (id)
);
Maybe you can use UUID_SHORT(). Not 12 digits long, but still could be a viable option:
mysql> select uuid_short();
+-------------------+
| uuid_short() |
+-------------------+
| 22048742962102272 |
+-------------------+
So:
INSERT INTO `table` (`id`, `text`) VALUES (UUID_SHORT(), 'hello world!');
Note: If you really want to have exactly 12 digits, then don't even try to substring the result, if would not ensure the uniqueness of the identifier and may cause collisions.
<?php
$allowed_characters = array(1,2,3,4,5,6,7,8,9,0);
for($i = 1;$i <= 12; $i++){
$pass .= $allowed_characters[rand(0, count($allowed_characters) - 1)];
}
echo $pass;
?>
demo: http://sandbox.phpcode.eu/g/c0190/4
Generally, I will prefer to do something a little bit more low tech. I obscure the values in PHP and leave them as auto-incrementing in JS.
$seeds = array( /*series 100 of very large >= 10-digit numbers*/ );
$seedID = rand( count( $seeds ) ); // randomly choose one of those.
// a string combination which represents the ID + some hash.
$id = bcadd( $seeds[ $seedID ], /* id retrieved from database */ );
// make sure we've not accidentally passed the 10^12 point
$id = bcmod( $id, 1000000000000 );
// make sure to pad
$id = str_pad('' . $id, 3, "0", STR_PAD_LEFT);
$outID = substr( $id, 0, 5 ) . $seedID . substr( $id, 6 );
Then, when receiving the ID from the user:
$seedID = substr( $outID, 6, 2 );
$tmpID = substr( $outID, 0, 5 ) . substr( $outID, 8 );
$id = bcsub( $tmpID, $seeds[ $seedID ] );
// we passed the modulus se we need to add this back in.
if( $id < 0 ) $id = bcmod( bcadd( $id, 1000000000000 ), 1000000000000 );
This will basically mean that you're simply obscuring whatever number you want -- you can use auto_increment with impunity!
One method would be to take your primary key value, salt it with a few other random-ish bits of data (username, current time, process ID, fixed string, etc...) and hash it with md5 or sha1. You then take the hash string and convert it into digits via basic string operations. That'll give you a relatively unique numeric code.
of course, with only 12 digits, you're far more likely to end up with a collision than by using the raw string hash - but since you're requiring this to be dialed on a keypad, it's an acceptable tradeoff.
If the pins are invalidated/deleted after usage, then the collision chances will be much reduced.
You want two things
Uniqueness
Incremental
If you want both the things from same sequence you will run out of luck (literally)
Uniqueness is guaranteed by having large sample space + random + check-unique. Which means, the actual number could be anywhere in between the sample space.
But if you want unique + incremental property, you are dividing sample space by 2. In 64 tries you would have reduced a 64 bit int sample space to 1 bit sample space.
Good luck !
All solutions so far lack one thing essential to your application: Security!
You said you will be using these numbers as a (product) verification code - so you really, really want this to be unpredictable, otherwise it will get exploited.
Neither MySQL's built-in RANDOM function nor any of the random functions PHP provides today are secure random functions. They behave pseudo-randomly, alright, but they all are predictable!
Your only chance is to whip up something of your own using /dev/urandom on a *nix machine or leveraging the Crypto API on Windows. OpenSSL does provide secure random numbers based on these mechanisms - you could reuse this either in a C extension for PHP or by reading the output from a command line script called from PHP. See also this answer.
About your requirement for the numbers to be sequential - is this really so important? It does complicate things enormously. Otherwise you would be good to go with a simple secure 6 byte random number encoded to a string using hex encoding (yielding a 12 character string). Although I would recommend making it 10 bytes and 20 characters to be safer.
But if you want to be sequential, which I interpret as monotonously increasing (because a simple +1 would be trivially predictable), this makes things just so much more complicated. And you don't gain anything from this complexity, the only thing that might happen is that you break the security by inventing some obscure scheme that is easily exploitable.
My suggestion: Add another column that acts as a plain old auto-incremented ID and add the code as a random number constructed as above as a separate column. As far as I see, there's no need to require the product activation code to be the ID at the same time.

Generating a unique id for a given string using php

I'm using Zend_Cache_Core with Zend_Cache_Backend_File to cache results of queries executed for a model class that accesses the database.
Basically the queries themselves should form the id by which to cache the obtained results, only problem is, they are too long. Zend_Cache_Backend_File doesn't throw an exception, PHP doesn't complain but the cache file isn't created.
I've come up with a solution that is not efficient at all, storing any executed query along with an autoincrementing id in a separate file like so:
0->>SELECT * FROM table
1->>SELECT * FROM table1,table2
2->>SELECT * FROM table WHERE foo = bar
You get the idea; this way i have a unique id for every query. I clean out the cache whenever an insert, delete, or update is done.
Now i'm sure you see the potential bottleneck here, for any test, save or fetch from cache two (or three, where we need to add a new id) requests are made to the file system. This may even defeat the need to cache alltogether. So is there a way i can generate a unique id, ie a much shorter representation, of the queries in php without having to store them on the file system or in a database?
Strings are arbitrarily long, so obviously it's impossible to create a fixed-size identifier that can represent any arbitrary input string without duplication. However, for the purposes of caching, you can usually get away with a solution that's simple "good enough" and reduces collisions to an acceptable level.
For example, you can simply use MD5, which will only produce a collision in 1 in 2128 cases. If you're still worried about collisions (and you probably should be, just to be safe) you can store the query and the result in the "value" of the cache, and check when you get the value back that it's actually the query you were looking for.
As a quick example (my PHP is kind of rusty, but hopefully you get the idea):
$query = "SELECT * FROM ...";
$key = "hash-" + hash("md5", $query);
$result = $cache->load($key);
if ($result == null || $result[0] != $query) {
// object wasn't in cache, do the real fetch and store it
$result = $db->execute($query); // etc
$result = array($query, $result);
$cache->save($result, $key);
}
// the result is now in $result[1] (the original query is in $result[0])
MD5!!
Md5 generates a string of length 32 that seems to be working fine, the cache files are created (with filenames about of length 47) so it seems as though the operating system doesn't reject them.
//returns id for a given query
function getCacheId($query) {
return md5($query);
}
And that's it! But there's that issuse of collisions and i think salting the md5 hash (maybe with the name of the table) should make it more robust.
//returns id for a given query
function getCacheId($query, $table) {
return md5($table . $query);
}
If anyone wants the full code for how i've implemented the results caching, just leave a comment and i'll be happy to post it.

Categories