0

I am using the php rand() function to generate coupon codes for my e commerce system. It worked fine for a while but now I am getting a lot of errors that the code is already in the system.

This is the function I use:

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

And my codes are 32 characters long.

I did a sample of ~150 tries and noticed that more than 50% of the generated codes where laready in the system.

I have 4212 codes in the system. The odds of a 32 character random string with 36 different symbols producing a collision are basically zero, and I get 50% collisions.

When I re-seeded the random number generator in my function by calling srand(); I did not have any collisions any more.

But on the man page of php it cleary says:

Note: As of PHP 4.2.0, there is no need to seed the random number generator with srand() or mt_srand() as this is now done automatically.

I am running php version PHP 5.5.9

So my thoughts where something like that seeding is done, but only once per webserver worker, and then when the process is forked, it is not reseeded or something like that. But that would be clearly a bug in apache...

I am running php as apache modul in apache version Apache/2.4.7 (Ubuntu) and the mpm_prefork_module module

So do I still need to call srand() at the top of every script dispite the manpages saying other wise, and why? Is it apaches fault or PHP's?

And yes, I am aware that I should not use this function for this purpose, and I will update it to use cryptographically secure numbers. But I think this should not happen anyway and I am still interested in what is going on!

The Surrican
  • 29,118
  • 24
  • 122
  • 168
  • 1
    How about `mt_rand`? – zerkms Oct 23 '14 at 23:16
  • I don't know since I have no pool to test it against. I may build one, however that may change the conditions. So I cannot say whether mt_rand has the same problem. I know that I have to replace the rand function here, I am primarily interested in why the problem occurred. – The Surrican Oct 24 '14 at 08:24
  • I would like to refer you to an SO post where it seems it is explained [why you getting same results](http://stackoverflow.com/a/14246927/1316372). – HenryW Oct 26 '14 at 10:44
  • @HenryW A promising lead, however this appears to refer to concurrency issues (where duplicates occur because two scripts seed with the same value at the exact same second). In this case here the duplicates are found in a pool generated over a long period of time and in the past. Unfortunately the phrase "number of seconds" is vague. Does he mean a unix time stamp? I suppose so. If he refers to the number of seconds since the process started, that would just be absurd... – The Surrican Oct 26 '14 at 10:59
  • From what i read, it comes literally as a "comment from php.net".But i am not an expert, just try to be a bit of help :) . – HenryW Oct 26 '14 at 12:50
  • further investigation brought me to [THIS](http://stackoverflow.com/a/26245863/1316372) post on SO . The way you use the rand function in your own function uses also `time()` , is your system still living in 1970? – HenryW Oct 26 '14 at 13:10
  • Sorry, your function does not uses the standard `time()` .So i leave the mystery to you now :) – HenryW Oct 26 '14 at 13:31
  • What do you mean by my function does not use the standard time()? I don't seed at all in my function. The manpage of srand clearly says that the seed is done with a random value (not the current time): "Seeds the random number generator with seed or with a random value if no seed is given.". But whatever it is, it still does not explain why my problems went away when I started manually seeding instead of relying on the automatic seeding. I think it is necessary to directly look at the source code to get a clue what is going on... – The Surrican Oct 27 '14 at 00:15
  • Okay this is from the source how php generates the seed: `#define GENERATE_SEED() (((zend_long) (time(0) * getpid())) ^ ((zend_long) (1000000.0 * php_combined_lcg(TSRMLS_C))))` so time and process id are a factor and the rest is just to make it system specific i think.. – The Surrican Oct 27 '14 at 01:06
  • There seems to be a contradiction in the manual. I don't see how it is possible for the PRNG to auto-seed with a truly "random value", since at this stage the PRNG hasn't been seeded. So the auto-seeding process must be somewhat deterministic. – RandomSeed Oct 27 '14 at 12:31
  • @TheSurrican , in previous comment i assumed you used standard `rand`, so system generates a rand via `time()`, but in your function you using a FIXED string `$characters`, so `time()` is neglected.So... your function not uses `rand` default. – HenryW Oct 29 '14 at 03:45
  • I am very curious the reason it currently makes duplicates.Did you made any change to the server before the duplicates started? For example move hosting? Do you control the server yourself, or is it done via hosting company? – HenryW Oct 29 '14 at 03:55
  • @HenryW Its a standard ubuntu 14.04 LTS amazon ec2 server with libapache2-mod-php5 installed. I moved to the sew server a while back when upgrading from ubuntu 12.04 to ubuntu 14.04. – The Surrican Oct 29 '14 at 05:49
  • Then i think , just as @ArtisiticPhoenix mentioned, that is the reason for the duplicates. – HenryW Oct 29 '14 at 10:39

3 Answers3

3

If your codes are 32 characters long, then why don't you simply encrypt the current microtime with md5 ?

  $coupon = md5( microtime() );

One line simple. And if you want a touch of randomness, just throw a

   $coupon = md5( microtime() . mt_rand( 0, 10000) );

On there like a salt. That will almost guarantee you will never duplicate. As for the why it is not as random.

PHP’s random number generators are seeded only once per process.

See this posting ...

http://phpsecurity.readthedocs.org/en/latest/Insufficient-Entropy-For-Random-Values.html

by the way I don't think you need cryptographically secure hashes, only sufficiently random ones that cant be easily guessed. Even with a cryptographic hash, users will enter said hash into the cart for the coupon, it's a simple matter to brute force even a cryptographically secure hash then, you'd do better to invest time in only allowing "n" attempts, or "n" attempts per second etc. To reduce the rate a brute force attack can be done.

For example, I would just try all combinations of 32 character hashes. So it doesn't matter in the end, because you are not using plaintext entries like a password, and then hiding your salting and encryption method. The number of coupons active would determine my success rate and the time it takes me in either case ... If you follow.

IMPLIED IN MY ANSWER IS THIS

PHP’s random number generators are seeded only once per process. Forking does not create a new process but copies the current processes state.

See
Calling rand/mt_rand on forked children yields identical results
and
http://wiki.openssl.org/index.php/Random_fork-safety
and
http://www.reddit.com/r/shittyprogramming/comments/2jvzgq/sometimes_it_takes_real_shitty_code_to_expose_an/

Additionally this is not an issue specific to php but more so to psudorandom number generation in general.

Community
  • 1
  • 1
ArtisticPhoenix
  • 21,464
  • 2
  • 24
  • 38
  • the OP already mentioned he understand he should take another approach.However , OP wants to know WHY it do what it is doing with his current code.(i did not down voted, but person who does should at least explain why he/she down vote's) – HenryW Oct 29 '14 at 03:37
  • 1
    I believe this covers his question does it not? "PHP’s random number generators are seeded only once per process." And with the OP's comment of using "and then when the process is forked". Forking copies the current process, therefor it will copy the random seed as well. If all the OP does is fork the process, ie never using a new one then it's implied In my answer that the random seed is never renewed / reseeded. You can downvote all you want, doesn't matter to me at all. Or, whoever downvoted, they should learn to read. – ArtisticPhoenix Oct 29 '14 at 04:02
  • 1
    Their lack of reading comprehension is a systemic error that I can do nothing about. Further more a simple google search would have yielded this similar question. http://stackoverflow.com/questions/14879151/calling-rand-mt-rand-on-forked-children-yields-identical-results – ArtisticPhoenix Oct 29 '14 at 04:07
  • ok, so all the other stuff he NOT asked for you can delete, and put the COMMENT `PHP’s random number generators are seeded only once per process." And with the OP's comment of using "and then when the process is forked". Forking copies the current process, therefor it will copy the random seed as well. If all the OP does is fork the process, ie never using a new one then it's implied In my answer that the random seed is never renewed / reseeded.` as your answer. – HenryW Oct 29 '14 at 04:16
  • 1
    Ok, I shortened it but i wont remove my suggestion about the cryptography as the OP did state "And yes, I am aware that I should not use this function for this purpose, and I will update it to use cryptographically secure numbers" Cryptographically secure numbers will have little impact on security ( keeping codes secure ) in this use case. – ArtisticPhoenix Oct 29 '14 at 04:19
  • lol... not take it so hard, i try to give a comment same as how i get them sometimes, i am down voted so much, sometimes feels like i am in a NAZI culture. – HenryW Oct 29 '14 at 04:23
  • Ahh, I feel the same too. No hard feeling then. – ArtisticPhoenix Oct 29 '14 at 04:24
2

See this: https://github.com/php/php-src/blob/d0cb715373c3fbe9dc095378ec5ed8c71f799f67/ext/standard/rand.c#L66-L68

Apparently RNG is being reseeded on first call to rand() (or explicitly calling srand()).

Since fork copies parent's memory, child also gets parent's seed - never getting reseeded.

Im0rtality
  • 3,463
  • 3
  • 31
  • 41
  • yes that was the same conclusion i ended up with. since the server had a very long uptime this really became a problem. – The Surrican Nov 01 '14 at 15:01
0
function generateRandomString($length) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return substr(time().$randomString,0,$length);
} 
Almamun
  • 1
  • 1