如何在PostgreSQL中生成指定范围内的加密安全随机整数?
问题描述
我希望编写一个PL/pgSQL函数,用于生成指定范围内的加密安全随机整数。
背景信息
许多应用层编程语言都提供了生成加密安全随机整数的内置函数,这类函数可用于生成密钥或验证码。比如Node.js和Python都有对应的实现方法。但PostgreSQL没有内置此类函数。
实现难点
合理的实现必须避免引入模偏倚(modulo bias),否则生成的整数可能分布不均。StackOverflow上部分相关PostgreSQL解决方案未考虑该问题。
尝试实现
我基于Node.js的crypto.randomInt函数实现编写了以下函数,pgTAP测试已通过,且我通过生成200万次不同范围内的随机整数并绘图验证,未发现模偏倚。但我仍需谨慎确认是否存在疏漏或错误。
以下函数是否需要调整?它接收两个整数参数low和high,返回[low, high)范围内的随机整数。
CREATE EXTENSION pgcrypto; CREATE FUNCTION random_int(low INT, high INT) RETURNS INT LANGUAGE plpgsql STRICT AS $$ DECLARE -- 4字节有符号整数的最大值 RAND_MAX INT := 2147483647; rand_range INT; rand_limit INT; rand_bytes BYTEA; x INT; BEGIN IF low >= high THEN RAISE EXCEPTION 'Low must be less than high'; END IF; -- 可生成的随机整数范围 rand_range := high - low; -- 能被rand_range整除的最大值 rand_limit := RAND_MAX - (RAND_MAX % rand_range); LOOP -- 生成4个随机字节 rand_bytes := gen_random_bytes(4); -- 将4个随机字节转换为4字节有符号整数,屏蔽符号位确保为正整数 -- 127的二进制为'01111111',按位与操作会丢弃第一个字节的符号位 x := ((get_byte(rand_bytes, 0) & 127) << 24) | (get_byte(rand_bytes, 1) << 16) | (get_byte(rand_bytes, 2) << 8) | get_byte(rand_bytes, 3); -- 仅接受小于rand_limit的随机整数以避免模偏倚 -- 我们接受小于rand_limit的值,确保x % rand_range分布均匀 -- 任何x >= rand_limit时,x % rand_range会存在偏倚 -- 举个简化示例:rand_range = 5,RAND_MAX = 12,rand_limit = 12 - (12 % 5) = 10 -- x : | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -- x % rand_range: | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 2 | 3 | 4 | 0 (偏倚) | 1 (偏倚) | 2 (偏倚) | -- 此时0、1、2出现的概率高于3和4 -- 为避免偏倚,我们只接受x < rand_limit的情况 -- x : | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -- x % rand_range: | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 2 | 3 | 4 | -- 加上low确保随机整数落在[low, high)范围内 IF x < rand_limit THEN RETURN (x % rand_range) + low; END IF; END LOOP; -- 若rand_range超过4字节有符号整数的表示范围,PostgreSQL会抛出整数越界错误 -- 捕获该错误并抛出更明确的异常 EXCEPTION WHEN numeric_value_out_of_range THEN RAISE EXCEPTION 'The difference between high and low cannot exceed the maximum value of a 4-byte signed integer'; END; $$;
测试结果
SELECT random_int(1, 4); -- 返回[1, 4)范围内的随机整数
SELECT random_int(-4, 1); -- 返回[-4, 1)范围内的随机整数
SELECT random_int(-2, 2); -- 返回[-2, 2)范围内的随机整数
SELECT random_int(0, 2147483647); -- 返回[0, 2147483647)范围内的随机整数
SELECT random_int(-1, 2147483647); -- 抛出异常 -- (high - low) 超过4字节有符号整数的最大值
内容的提问来源于stack exchange,提问作者user23454440
相关产品推荐
相关产品推荐

