如何在PHP中生成随机的64位十进制字符串值

8

Oauth需要一个随机的64位无符号数字,以十进制格式编码为ASCII字符串。你能帮我用PHP实现这个吗? 谢谢。

3个回答

30

这是一个非常有趣的问题(如何在PHP中创建任意长度随机数的十进制表示,且不能使用可选扩展)。以下是解决方案:

步骤1:任意长度随机数

// 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;
}

现在,调用random_bits(2048)将给您一个2048位的随机比特作为十六进制编码字符串,没有问题。

第二步:任意精度进制转换

数学很难,所以这里是代码:

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;
}

这个函数将按照广告宣传的方式工作,例如尝试base_convert_arbitrary('ffffffffffffffff', 16, 10) == '18446744073709551615'base_convert_arbitrary('10000000000000000', 16, 10) == '18446744073709551616'

将它们组合起来

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

1
即使只是考虑在PHP中实现这个想法,也足以让人震惊。请记住,count_bits无法处理大于PHP_INT_MAX的数字。 - Charles
1
@Charles:感谢您的支持。count_bits确实有限,但由于我们只需要用它来测量mt_getrandmax的返回值,因此它足以胜任这项工作。 - Jon
非常充分,考虑到即使在64位平台上,mt_getrandmax通常似乎也是2^31-1。 - Charles
1
只是提醒一下:有时候数字的表示可以包含前导零(比如hash()函数的结果),而这个函数显然无法在转换后恢复它们。所以如果 nab = convert(n, a, b) ,可能存在 n !== convert(nab, b, a) 的情况。要小心注意。 - ksimka
有趣的函数 base_convert_arbitrary。但是为什么要用所有的字母呢?$value = $value * $fromBase + $nibbles[$i]; 不是保证操作只对 2 到 16 进制有效吗? - Chibueze Opata
有趣的是,我刚刚注意到nibbles实际上具有的是strpos而不是substr。然而,我仍然对($value >= $toBase) { $nibbles[$newlen++] = (int)($value / $toBase); $value %= $toBase; }感到困惑,这里到底发生了什么?评论将不胜感激。 - Chibueze Opata

4
你可以使用两个32位数字、四个16位数字等。
PHP有rand()mt_rand(),但它们提供的随机位数没有在标准中指定(尽管可以通过getrandmax()mt_getrandmax()进行查询)。
因此,最简单的方法是生成64个随机位并逐个设置它们。
至于使用64位整数,我建议使用GMP库,因为它有许多函数可帮助你。
你可以创建一个数字,使用连续的位置调用64次gmp_setbit(),然后使用gmp_strval()将其转换为字符串。

2
你正在构建一个OAuth适配器吗?如果是的话,你可能需要重新考虑。有很多好的OAuth库,包括来自PECL的一个, PEAR中的一个, Zend Framework中的另一个托管在Google Code上的另一个。我用过前三个,它们都相当不错。
如果你真的想自己做这个,你可能会遇到一个问题。PHP无法以64位数字思考,除非它在64位平台上编译或安装了高级数学扩展。这将使将64位数字表示为十进制数非常困难。看起来我上面链接的许多库完全忽略了格式要求,而是直接使用原始的MD5哈希值。以下是ZF适配器的代码:
/**
 * Generate nonce
 * 
 * @return string
 */
public function generateNonce()
{
    return md5(uniqid(rand(), true));
}

他们似乎在没有互操作性问题的情况下逃脱了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接