1

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.)

7
  • Your question have already been answered numerous times here. For example: Using Random Numbers As Session variables
    – Adi
    Commented Nov 16, 2013 at 14:23
  • @Adnan That uses the wrong character set. Commented Nov 16, 2013 at 15:21
  • @Adnan So you would suggest that $code = bin2hex(openssl_random_pseudo_bytes(12)); is the perfect solution to this problem? Commented Nov 16, 2013 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.
    – Adi
    Commented Nov 16, 2013 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
    – Adi
    Commented Nov 16, 2013 at 16:34

1 Answer 1

0

/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.


In PHP 7 you can use random_int which is secure and easy to use.

$randomIndex = random_int(0, $count - 1);

In earlier versions you could use the random_compat library.

2
  • +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! Commented Nov 16, 2013 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. Commented Nov 16, 2013 at 16:54

Not the answer you're looking for? Browse other questions tagged .