Take the 2-minute tour ×
Information Security Stack Exchange is a question and answer site for Information security professionals. It's 100% free, no registration required.

This question already has an answer here:

I need to generate thousands (possibly millions) of unique codes for a client. These codes may have a monetary value, so it's vital that they are TRULY random, and that no pattern could be spotted and exploited by a clever hacker.

I've looked at various solutions for generating the code (cyclically generated = could be guessed, too risky, uniqid() = good, but I may need a code of a different length).

Right now I'm thinking of generating the code using A-Z and 0-9 MINUS VOWELS (in order to ensure no rude words could be accidentally generated) although this may be increased in the future to lowercase characters, etc.

So far I have this (using a custom rand function taken from here):

function generateCode($length = 12) {
    $chars = 'BCDFGHJKLMNPQRSTVWXYZ0123456789';
    $count = mb_strlen($chars);

    for ($i = 0, $result = ''; $i < $length; $i++) {
        $randomIndex = devurandom_rand(0, $count - 1);
        $result .= mb_substr($chars, $randomIndex, 1);
    }

    return $result;
}

// CUSTOM RAND FUNCTION
// equiv to rand, mt_rand
// returns int in *closed* interval [$min,$max]                                                
function devurandom_rand($min = 0, $max = 0x7FFFFFFF) {
    $diff = $max - $min;
    if ($diff < 0 || $diff > 0x7FFFFFFF) {
    throw new RuntimeException("Bad range");
    }
    $bytes = mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
    if ($bytes === false || strlen($bytes) != 4) {
        throw new RuntimeException("Unable to get 4 bytes");
    }
    $ary = unpack("Nint", $bytes);
    $val = $ary['int'] & 0x7FFFFFFF;   // 32-bit safe                           
    $fp = (float) $val / 2147483647.0; // convert to [0,1]                          
    return round($fp * $diff) + $min;
}

It's pretty basic, so I'm obviously concerned that it's not random enough. Can anyone tell me if this a decent way to ensure that the generated code is truly random?

(Note: Obviously a wannabe hacker will always be able to attempt a brute force attack. Such attempts will be handled elsewhere, I just want to make sure this code is truly random.)

share|improve this question

marked as duplicate by Adnan, Xander, Noordung, NULLZ, Terry Chia Nov 18 '13 at 2:31

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

    
Your question have already been answered numerous times here. For example: Using Random Numbers As Session variables –  Adnan Nov 16 '13 at 14:23
    
@Adnan That uses the wrong character set. –  CodesInChaos Nov 16 '13 at 15:21
    
@Adnan So you would suggest that $code = bin2hex(openssl_random_pseudo_bytes(12)); is the perfect solution to this problem? –  Django Reinhardt Nov 16 '13 at 16:23
    
@DjangoReinhardt To generate securely random codes that will never repeat in your life time or the life time of humanity and for billions and billions of years? Yes. Unfortunately, the resulting character set isn't what you want. But, to be honest, if you were just looking to generate random codes and you don't really need that specific character set, I'd say go with the solution I suggested. –  Adnan Nov 16 '13 at 16:32
    
If, however, your question isn't essentially about security and rather about generating a random string comprised of that specific character set you're requesting, then your question is better asked at stackoverflow.com –  Adnan Nov 16 '13 at 16:34

1 Answer 1

up vote -1 down vote accepted

/dev/urandom isn't truly random, but once it has been properly seeded it's good enough for security critical use. For true randomness, you need /dev/random, but I'd stay with urandom.

Your implementation produces slightly biased output for two reasons:

  1. The number characters (31) doesn't evenly divide 2^31. This bias is small enough to have a negligible effect on security. It's possible to fix this, but I wouldn't bother.
  2. The rounding technique is flawed. The first and last character are only half as likely as the rest.

    I'd change the following:

    $diff = $max - $min + 1;
    
    ...
    
    $fp = (float) $val / 2147483648.0;// 0x80000000, choosing this value ensures that $fp is truly smaller than 1, and not equal
    return floor($fp * $diff) + $min;// now all values 0 <= x <= diff are almost equally likely    
    

With 12 characters the total entropy is around 60 bits. With one million codes guessing even one of them should take around 2^40, or a thousand billion attempts. This is infeasible for online attacks, where the attacker needs to send one request to your server, but cheap with offline attacks. It looks like offline attacks don't apply to your system, so the codes seem large enough.

Assuming your php code has no subtle flaws this should be fine. I'm no php programmer, so I might have missed something.

share|improve this answer
    
+1 I don't understand enough about cryptography to be able to appreciate if your answer truly is an improvement or not! It's complicated stuff :) Thanks, though! –  Django Reinhardt Nov 16 '13 at 16:27
    
@DjangoReinhardt You can test it. Generate a bunch of strings, count how common each letter occurs. I'd expect them to be equally common with my code, but B and 9 should be rarer than the rest with your code. A bit of testing doesn't hurt as a sanity check, even if it can't prove crypto secure. So I recommend writing such a test. –  CodesInChaos Nov 16 '13 at 16:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.