5

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
michael
  • 387
  • 2
  • 5
  • 11

7 Answers7

6

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)
);
Arnaud Le Blanc
  • 98,321
  • 23
  • 206
  • 194
  • 1
    cool thanks. dont know why i didn't think of that. with that method i can even do it 'stupidly' by generating a full 11 digit random number, concatenate it, and truncate any trailing digits and it will still always be unique. – michael Aug 25 '11 at 17:24
  • Randomly generated is unpredictable and non-sequential. It may be incremental, but not sequential. – netcoder Aug 25 '11 at 17:59
  • Mmm... actually I see a problem with this. if the id was 1 10 or 100 for example, the appended number could possibly make two items the same (although unlikelely). for example if id 9 was appended with 910000000000, and then 99 was appened with 100000000000 the combined keys would be the same. – michael Aug 25 '11 at 18:03
  • This method is guaranteed no collision as long as the number of id digits dont change... so I will just start my id's at 100,000, and use a randomly generated trailing 6 digits. – michael Aug 25 '11 at 18:10
  • You're right, you have to pad the numbers with zeros, or separate them with a `-` for example. – Arnaud Le Blanc Aug 25 '11 at 18:10
  • @emboss - the first 6 digits would be highly predictable.. how would you be able to guess the combined key though? – michael Aug 25 '11 at 18:10
  • @emboss it is as predictable as a random 32bit numbers, 4 billion possibilities. – Arnaud Le Blanc Aug 25 '11 at 18:13
  • @Arnaud,@michael: Google how to break the "Mersenne Twister" for example. You can predict a non-secure PRNG much faster than by brute-forcing it. Predictable here means there is a possibility to predict a number in less average effort than brute-forcing. That's definitely the case for MySQL's random number generator. – emboss Aug 25 '11 at 18:22
  • im not using mysqls generator I'm using php's mt_rand, which uses random seeds by default, although that uses mersenne twister as well I believe. In reality, I'm only trying to prevent human guessing with these numbers. Because of the way I'm using them a sophisticated attack would be unlikely to benefit the attacker. – michael Aug 25 '11 at 18:33
  • @michael: Depends on the revenue that's to be gained by breaking it. But to harden the solution (still not perfect, but a lot better) you should additionally SHA-1 the output of `mt_rand` - that makes it much harder to predict it. – emboss Aug 25 '11 at 18:40
  • @emboss - sounds good.. I'll have to convert it back to an integer though after sha1. – michael Aug 25 '11 at 18:46
  • What I've learned from this - creating a truly random unique number is a bitch. – michael Aug 25 '11 at 18:49
2

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.

netcoder
  • 66,435
  • 19
  • 125
  • 142
  • 1
    This is nothing more than global incremented number. This is **highly predictable**, see how it is generated: `(server_id & 255) << 56 + (server_startup_time_in_seconds << 24) + incremented_variable++;` – Arnaud Le Blanc Aug 25 '11 at 17:33
  • Yes, I know that. It's still better than `1,2,3,4`. Also, by definition, sequential *is* predictable. – netcoder Aug 25 '11 at 17:36
  • Did you guys even read the question? What part of sequential did you miss? Random can't be sequential. – netcoder Aug 25 '11 at 17:38
  • @netcoder OP asks for a non-predictable number ;) My answer gives numbers that are both sequential AND non-predictable. – Arnaud Le Blanc Aug 25 '11 at 17:40
  • @arnaud576875: Funny because I don't see the word "random" anywhere in that question. You can't possibly have sequential random numbers, they're actually contraries. – netcoder Aug 25 '11 at 17:41
  • **Sequential** Adjective: *Forming or following in a logical order or sequence: "following a series of sequential steps".* – netcoder Aug 25 '11 at 17:43
  • You're right, he didn't said random. But he said unpredictable ;) You can have sequential AND unpredictable numbers (whith sequential = each generated number is upper than the previous one, potentially with gaps) – Arnaud Le Blanc Aug 25 '11 at 17:44
  • @arnaud576875: That's still a sequence, and that's still predictable, because a sequence is *always* predictable, that's the point of a sequence. – netcoder Aug 25 '11 at 17:46
  • Assuming that (concat(auto_increment_column, 32bit_random_number())) is predictable, yes. But a 32 bit random number is 1<<32 possibilities. – Arnaud Le Blanc Aug 25 '11 at 17:48
1
<?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

genesis
  • 50,477
  • 20
  • 96
  • 125
1

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!

cwallenpoole
  • 79,954
  • 26
  • 128
  • 166
0

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.

Marc B
  • 356,200
  • 43
  • 426
  • 500
0

You want two things

  1. Uniqueness
  2. 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 !

Ajeet Ganga
  • 8,353
  • 10
  • 56
  • 79
0

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.

Community
  • 1
  • 1
emboss
  • 38,880
  • 7
  • 101
  • 108
  • The verification number is indeed a seperate column. I was thinking of making it a composite of a random number and the primary key to make sure it would always be unique (since if you keep pulling random numbers eventually there will be a collision). – michael Aug 25 '11 at 18:18
  • The possibility of two 12 byte random numbers colliding is negligible. Don't worry for that - it would produce an error on DB insertion at most and you simply retry if that should ever occur. But *please*, I'm serious there, don't rely on any random number generated by MySQL's or PHP's built-in functions if attempts at exploiting your scheme is desirable (saving/making money etc.) - you will regret it! – emboss Aug 25 '11 at 18:26