How to generate random 64-bit value as decimal string in PHP

后端 未结 3 1358
清歌不尽
清歌不尽 2020-11-27 06:52

Oauth requires a random 64-bit, unsigned number encoded as an ASCII string in decimal format. Can you guys help me achieve this with php? Thanks

3条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-11-27 07:41

    This was a really interesting problem (how to create the decimal representation of an arbitrary-length random number in PHP, using no optional extensions). Here's the solution:

    Step 1: arbitrary-length random number

    // Counts how many bits are needed to represent $value
    function count_bits($value) {
        for($count = 0; $value != 0; $value >>= 1) {
            ++$count;
        }
        return $count;
    }
    
    // Returns a base16 random string of at least $bits bits
    // Actual bits returned will be a multiple of 4 (1 hex digit)
    function random_bits($bits) {
        $result = '';
        $accumulated_bits = 0;
        $total_bits = count_bits(mt_getrandmax());
        $usable_bits = intval($total_bits / 8) * 8;
    
        while ($accumulated_bits < $bits) {
            $bits_to_add = min($total_bits - $usable_bits, $bits - $accumulated_bits);
            if ($bits_to_add % 4 != 0) {
                // add bits in whole increments of 4
                $bits_to_add += 4 - $bits_to_add % 4;
            }
    
            // isolate leftmost $bits_to_add from mt_rand() result
            $more_bits = mt_rand() & ((1 << $bits_to_add) - 1);
    
            // format as hex (this will be safe)
            $format_string = '%0'.($bits_to_add / 4).'x';
            $result .= sprintf($format_string, $more_bits);
            $accumulated_bits += $bits_to_add;
        }
    
        return $result;
    }
    

    At this point, calling random_bits(2048) will give you 2048 random bits as a hex-encoded string, no problem.

    Step 2: arbitrary-precision base conversion

    Math is hard, so here's the code:

    function base_convert_arbitrary($number, $fromBase, $toBase) {
        $digits = '0123456789abcdefghijklmnopqrstuvwxyz';
        $length = strlen($number);
        $result = '';
    
        $nibbles = array();
        for ($i = 0; $i < $length; ++$i) {
            $nibbles[$i] = strpos($digits, $number[$i]);
        }
    
        do {
            $value = 0;
            $newlen = 0;
            for ($i = 0; $i < $length; ++$i) {
                $value = $value * $fromBase + $nibbles[$i];
                if ($value >= $toBase) {
                    $nibbles[$newlen++] = (int)($value / $toBase);
                    $value %= $toBase;
                }
                else if ($newlen > 0) {
                    $nibbles[$newlen++] = 0;
                }
            }
            $length = $newlen;
            $result = $digits[$value].$result;
        }
        while ($newlen != 0);
        return $result;
    }
    

    This function will work as advertised, for example try base_convert_arbitrary('ffffffffffffffff', 16, 10) == '18446744073709551615' and base_convert_arbitrary('10000000000000000', 16, 10) == '18446744073709551616'.

    Putting it together

    echo base_convert_arbitrary(random_bits(64), 16, 10);
    

提交回复
热议问题