Python中生成随机数据安全性、多功能性、用途和速度方面进行比较


Posted in Python onApril 14, 2022

在日常工作编程中存在着各种随机事件,同样在编程中生成随机数字的时候也是一样,随机有多随机呢?在涉及信息安全的情况下,它是最重要的问题之一。每当在 Python 中生成随机数据、字符串或数字时,最好至少大致了解这些数据是如何生成的。

用于在 Python 中生成随机数据的不同选项,然后在安全性、多功能性、用途和速度方面对每个选项进行比较。

本篇内容不是数学或密码学相关内容,仅仅是根据需要进行尽可能多的数学运算仅此而已。

随机性有多随机

大多数用 Python 生成的随机数据在科学意义上并不是完全随机的。相反是伪随机的:使用伪随机数生成器(PRNG)生成,它本质上是任何用于生成看似随机但仍可重现的数据的算法。『真』随机数可以由真随机数生成器(TRNG)生成。

可能已经 Python 中看到过类似 random.seed(999) 的东西。此函数调用 Python 模块 random.seed(1234) 使用的底层随机数生成器。random 使得后续调用生成随机数具有确定性:输入 A 总是产生输出 B。

也许『随机』和『确定性』这两个术语似乎不能并存。为了更清楚地说明这一点这里有一个极其精简的版本,random() 它通过使用迭代创建一个『随机』数字 x = (x * 3) % 19 。x 最初定义为种子值,然后根据该种子变形为确定性的数字序列。

class NotSoRandom(object):
    def seed(self, a=3):
        """随机数生成器"""
        self.seedval = a
    def random(self):
        """随机数"""
        self.seedval = (self.seedval * 3) % 19
        return self.seedval

_inst = NotSoRandom()
seed = _inst.seed
random = _inst.random

for i in range(10):
    seed(123)
    print([random() for _ in range(10)])
    
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]
[8, 5, 15, 7, 2, 6, 18, 16, 10, 11]

加密安全性

如果对『RNG』首字母缩略词还不够了解,再添加一个 CSPRNG,或加密安全 PRNG。 CSPRNG 适用于生成敏感数据,例如密码、身份验证器和令牌。 给定一个随机字符串,实际上无法确定在随机字符串序列中哪个字符串出现在该字符串之前或之后。

另一个术语熵,引入或期望的随机性数量。 例如将在介绍的一个 Python 模块定义了 DEFAULT_ENTROPY = 32,即默认返回的字节数。

关于 CSPRNG 的一个关键点是它们仍然是伪随机的。它们以某种内部确定性的方式设计,但添加了一些其他变量或具有使它们『足够随机』以禁止返回到任何强制执行确定性的函数的属性。

Python 工具中的 PRNG 和 CSPRNG :

  • PRNG 选项包括 Python 标准库中的 random 模块及其基于数组的 NumPy 对应模块 numpy.random。
  • Python 的 os、secrets 和 uuid 模块包含用于生成加密安全对象的函数。

PRNG

Python中生成随机数据安全性、多功能性、用途和速度方面进行比较

random 模块

random模块是在 Python 中生成随机数据的最广为人知的工具可,使用Mersenne Twister PRNG 算法作为其核心生成器。

构建一些没有播种的随机数据。该 random.random() 函数返回区间 [0.0, 1.0) 内的随机浮点数。

import random
random.random()
0.1250920165739744
random.random()
0.7327868824782764

使用 random.seed(),可以使结果可重现,并且之后的调用链random.seed() 将产生相同的数据轨迹。

随机数序列变为确定性的,或完全由种子值确定。

random.seed(444)
random.random()
0.3088946587429545
random.random()
0.01323751590501987

random.seed(444)
random.random()
0.3088946587429545
random.random()
0.01323751590501987

使用 random.randint() 可以使用该函数在 Python 中的两个端点之间生成一个随机整数。数据在整个 [x, y] 区间并且可能包括两个端点。

>>> random.randint(0, 10)
2
>>> random.randint(500, 50000)
9991

使用 random.randrange() 可以排除区间的右侧,生成的数字始终位于 [x, y) 范围内,并且始终小于右端点。

random.randrange(1, 10)
9

使用 random.uniform(),从连续均匀分布中提取生成位于特定 [x, y] 区间内的随机浮点数。

random.uniform(20, 30)
27.42639687016509
random.uniform(30, 40)
36.33865802745107

使用 random.choice() 从非空序列(如列表或元组)中选择随机元素。

items = ['A', 'B', 'C', 'D', 'E']
random.choice(items)
'B'

random.choices(items, k=2)
['A', 'C']
random.choices(items, k=3)
['C', 'D', 'E']

使用 random.sample() 不替换的情况下模拟采样。

random.sample(items, 4)
['A', 'D', 'B', 'E']

使用 random.shuffle() 修改序列对象并随机化元素的顺序。

random.shuffle(items)
items
['E', 'B', 'A', 'C', 'D']

生成一系列唯一长度一致的随机字符串的例子,一般用于验证码这种。

from random import Random

# 随机生成邮件验证码的随机字符串
def RandomsStr(random_length):
    Str = ''
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'  # 设置可选字符
    length = len(chars) - 1
    random = Random()
    for i in range(random_length):
        Str += chars[random.randint(0, length)]
    return Str

RandomsStr(10)
LhK3vFepch

RandomsStr(16)
iGy1g0FO54Cjx3WP

数组 numpy.random

大多数函数都 random 返回一个标量值(单个int、float或其他对象)。生成序列的话可以使用列表生成的方法。

[random.random() for _ in range(5)]
[0.7401011155476498,
 0.9892634439644596,
 0.36991622177966765, 
 0.14950913503744223, 
 0.4868906039708182]

numpy.random 使用自己的 PRNG,与普通的 random 不太一样。

"""
从标准正态分布返回样本
"""
np.random.randn(5)
array([-0.59656657, -0.6271152 , -1.51244475, -1.02445644, -0.36722254])

np.random.randn(3, 4)
array([[ 0.34054183,  1.59173609, -0.5257795 , -0.86912511],
       [-0.86855499, -0.64487065,  1.47682128,  1.8238103 ],
       [ 0.05477224,  0.35452769,  0.14088743,  0.55049185]])

"""
根据概率随机分配
"""
np.random.choice([0, 1], p=[0.6, 0.4], size=(5, 4))
array([[0, 1, 0, 1],
       [0, 1, 1, 1],
       [0, 1, 0, 1],
       [1, 0, 0, 0],
       [0, 0, 0, 0]])

"""
创建一系列随机布尔值
"""
np.random.randint(0, 2, size=25, dtype=np.uint8).view(bool)
array([ True, False,  True,  True, False,  True, False, False, False,
       False, False,  True,  True, False, False, False,  True, False,
        True, False,  True,  True,  True, False,  True])

相关数据的生成

假设要模拟两个相关的时间序列。解决此问题的一种方法是使用 NumPy 的multivariate_normal() 函数,该函数将协方差矩阵考虑在内。换句话说要从单个正态分布的随机变量中提取,需要指定其均值和方差(或标准差)。

def corr2cov(p, s):
    """相关性和标准差的协方差矩阵"""
    d = np.diag(s)
    return d @ p @ d
corr = np.array([[1., -0.40],[-0.40, 1.]])
stdev = np.array([6., 1.])
mean = np.array([2., 0.5])
cov = corr2cov(corr, stdev)
data = np.random.multivariate_normal(mean=mean, cov=cov, size=50)
data[:10]

[[-0.33377432  0.22889428]
 [-1.5311996   0.31678635]
 [-6.02684472  0.90562824]
 [ 5.2696086   0.86518295]
 [ 6.43832395  0.36507745]
 [-8.49347011  0.68663565]
 [-5.05968126  0.55214914]
 [ 2.02314646  1.32325775]
 [ 0.98705556 -0.63118682]
 [ 2.90724439 -1.26188307]]

random模块与NumPy对照表

random模块 NumPy 对应方 说明
random() rand() [0.0, 1.0) 中的随机浮点数
randint(a, b) random_integers() [a, b] 中的随机整数
randrange(a, b[, step]) randint() [a, b) 中的随机整数
uniform(a, b) uniform() [a, b] 中的随机浮点数
choice(seq) choice() 随机元素来自seq
choices(seq, k=1) choice() 带有替换的随机k元素seq
sample(population, k) choice()和replace=False 无替换的随机k元素seq
shuffle(x[, random]) shuffle() 将序列随机打乱
normalvariate(mu, sigma)或者gauss(mu, sigma) normal() mu具有均值和标准差的正态分布样本sigma

CSPRNG

Python中生成随机数据安全性、多功能性、用途和速度方面进行比较

尽可能随机 os.urandom()

在不涉及太多细节的情况下,生成依赖于操作系统的随机字节,可以安全地称为密码安全 secretsuuidos.urandom(),在技术上仍然是伪随机的。

唯一的参数是要返回的字节数。

os.urandom(3)
b'\xa2\xe8\x02'

x = os.urandom(6)
x
b'\xce\x11\xe7"!\x84'

type(x), len(x)
(bytes, 6)

但是这种保存格式不太符合开发的要求。

secrets 最佳保存方式

Python 3.6+ 版本引入的 PEP,secrets模块旨在成为事实上的 Python 模块,用于生成加密安全的随机字节和字符串。

secrets 基本上是一个包装器 os.urandom()。只导出了少数用于生成随机数、字节和字符串的函数。

n = 16

# 生成安全令牌
secrets.token_bytes(n)
b'A\x8cz\xe1o\xf9!;\x8b\xf2\x80pJ\x8b\xd4\xd3'
secrets.token_hex(n)
'9cb190491e01230ec4239cae643f286f'  
secrets.token_urlsafe(n)
'MJoi7CknFu3YN41m88SEgQ'
# `random.choice()` 的安全版本
secrets.choice('rain')
'a'

UUID

生成随机令牌的最后一个选项是 Python 的 uuid 模块中的 uuid4() 函数。 UUID 是一个通用唯一标识符,一个 128 位序列(长度为 32 的字符串),旨在『保证跨空间和时间的唯一性』。 uuid4() 是该模块最有用的函数之一,该函数也使用了 os.urandom()。

import uuid

uuid.uuid4()
UUID('3e3ef28d-3ff0-4933-9bba-e5ee91ce0e7b')
uuid.uuid4()
UUID('2e115fcb-5761-4fa1-8287-19f4ee2877ac')

可能还看到了其他一些变体:uuid1()、uuid3() 和 uuid5()。它们之间的主要区别在于这 uuid4() 三个函数都采用某种形式的输入,不符合 uuid4() 的『保证跨空间和时间的唯一性』。

除了安全模块(例如 secrets)之外,Python 的 random 模块实际上还有一个很少使用的类,称为 SystemRandom,它使用 os.urandom()。 (反过来,SystemRandom 也被秘密使用。这有点像一个可以追溯到 urandom() 的网络。)

那么为什么不『默认』这个版本? 为什么不『永远安全』,而不是默认使用在密码学上不安全的确定性随机函数?

1.因为有时希望数据具有确定性和可重复性,以供其他人后续使用。

2.时间效率问题。

"""
CSPRNG 至少在 Python 中,往往比 PRNG 慢得多。 
让我们使用脚本 timed.py 来测试,该脚本使用 timeit.repeat() 比较 randint() 的 PRNG 和 CSPRNG 版本。
"""

import random
import timeit

# CSPRNG 版本依次使用 `SystemRandom()` 和 `os.urandom()`。
_sysrand = random.SystemRandom()

def prng() -> None:
    random.randint(0, 95)

def csprng() -> None:
    _sysrand.randint(0, 95)

setup = 'import random; from __main__ import prng, csprng'

if __name__ == '__main__':
    print('Best of 3 trials with 1,000,000 loops per trial:')

    for f in ('prng()', 'csprng()'):
        best = min(timeit.repeat(f, setup=setup))
        print('\t{:8s} {:0.2f} seconds total time.'.format(f, best))

Best of 3 trials with 1,000,000 loops per trial:
	prng()   0.93 seconds total time.
	csprng() 1.70 seconds total time.

工程随机性的比较

封装/模块 描述 加密安全
random 使用 Mersenne Twister 快速简单的随机数据
numpy.random 像random但对于(可能是多维的)数组
os 包含urandom(),这里介绍的其他功能的基础
secrets 设计为 Python 的事实上的模块,用于生成安全的随机数、字节和字符串
uuid 用于构建 128 位标识符的一些函数的所在地 uuid4()是

到此这篇关于详解Python中生成随机数据的示例详解的文章就介绍到这了!

Python 相关文章推荐
简单介绍Python中用于求最小值的min()方法
May 15 Python
Python 基础之字符串string详解及实例
Apr 01 Python
python的dataframe转换为多维矩阵的方法
Apr 11 Python
Python pyinotify模块实现对文档的实时监控功能方法
Oct 13 Python
python创建n行m列数组示例
Dec 02 Python
Python使用GitPython操作Git版本库的方法
Feb 29 Python
python实现将range()函数生成的数字存储在一个列表中
Apr 02 Python
如何打包Python Web项目实现免安装一键启动的方法
May 21 Python
Python如何实现的二分查找算法
May 27 Python
如何教少儿学习Python编程
Jul 10 Python
Python 爬取淘宝商品信息栏目的实现
Feb 06 Python
Python干货实战之八音符酱小游戏全过程详解
Oct 24 Python
python中 .npy文件的读写操作实例
Apr 14 #Python
Python Matplotlib绘制等高线图与渐变色扇形图
python读取并查看npz/npy文件数据以及数据显示方法
Apr 14 #Python
在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理
Python捕获、播放和保存摄像头视频并提高视频清晰度和对比度
Apr 14 #Python
Python中time标准库的使用教程
Apr 13 #Python
Python函数对象与闭包函数
Apr 13 #Python
You might like
使用YUI+Ant 实现JS CSS压缩
2014/09/02 PHP
WordPress中获取所使用的模板的页面ID的简单方法
2015/12/31 PHP
如何用JavaScript动态呼叫函数(两种方式)
2013/05/03 Javascript
Jquery Uploadify上传带进度条的简单实例
2014/02/12 Javascript
使用js dom和jquery分别实现简单增删改
2014/09/11 Javascript
jQuery实现输入框下拉列表树插件特效代码分享
2015/08/27 Javascript
在JavaScript中使用JSON数据
2016/02/15 Javascript
详解Node.js包的工程目录与NPM包管理器的使用
2016/02/16 Javascript
一些实用性较高的js方法
2016/04/19 Javascript
关于JS中setTimeout()无法调用带参函数问题的解决方法
2016/06/21 Javascript
vue-cli脚手架搭建的项目去除eslint验证的方法
2018/09/29 Javascript
vue实现双向绑定和依赖收集遇到的坑
2018/11/29 Javascript
微信小程序websocket聊天室的实现示例代码
2019/02/12 Javascript
JavaScript 防抖和节流遇见的奇怪问题及解决
2020/11/20 Javascript
基于vuex实现购物车功能
2021/01/10 Vue.js
[49:43]VG vs FNATIC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
Python解析excel文件存入sqlite数据库的方法
2016/11/15 Python
详解python3百度指数抓取实例
2016/12/12 Python
使用python进行拆分大文件的方法
2018/12/10 Python
python后端接收前端回传的文件方法
2019/01/02 Python
Python实现获取系统临时目录及临时文件的方法示例
2019/06/26 Python
Python及Pycharm安装方法图文教程
2019/08/05 Python
python ubplot使用方法解析
2020/01/10 Python
英国内衣连锁店:Boux Avenue
2018/01/24 全球购物
捷克时尚网上商店:OTTO
2018/03/15 全球购物
Banana Republic英国官网:香蕉共和国,GAP集团旗下偏贵族风
2018/04/24 全球购物
你在项目中用到了xml技术的哪些方面?如何实现的?
2014/01/26 面试题
人力资源职位说明书
2014/07/29 职场文书
2014年财务科工作总结
2014/11/11 职场文书
瞿秋白纪念馆观后感
2015/06/10 职场文书
2016年三八红旗手先进事迹材料
2016/02/26 职场文书
共青团优秀团员申请书(范文)
2019/08/15 职场文书
导游词之丽江普济寺
2019/10/22 职场文书
jquery插件实现悬浮的菜单
2021/04/24 jQuery
JAVA SpringMVC实现自定义拦截器
2022/03/16 Python
vue生命周期钩子函数以及触发时机
2022/04/26 Vue.js