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 strip()函数 介绍
May 24 Python
全面解析Python的While循环语句的使用方法
Oct 13 Python
python多个模块py文件的数据共享实例
Jan 11 Python
Python中按值来获取指定的键
Mar 04 Python
django settings.py 配置文件及介绍
Jul 15 Python
python装饰器相当于函数的调用方式
Dec 27 Python
Python连接SQLite数据库并进行增册改查操作方法详解
Feb 18 Python
Ubuntu18.04安装 PyCharm并使用 Anaconda 管理的Python环境
Apr 08 Python
解决使用python print打印函数返回值多一个None的问题
Apr 09 Python
什么是Python包的循环导入
Sep 08 Python
如何理解及使用Python闭包
Jun 01 Python
Python如何导出导入所有依赖包详解
Jun 08 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
phpstrom使用xdebug配置方法
2013/12/17 PHP
Javascript 面向对象特性
2009/12/28 Javascript
实现JavaScript的组成----BOM和DOM详解
2016/05/18 Javascript
jquery 仿锚点跳转到页面指定位置的实例
2017/02/14 Javascript
BootstrapValidator实现注册校验和登录错误提示效果
2017/03/10 Javascript
深入理解Javascript中的作用域链和闭包
2017/04/25 Javascript
老生常谈js-react组件生命周期
2017/05/02 Javascript
AngularJS动态绑定ng-options的ng-model实例代码
2017/06/21 Javascript
jquery单击文字或图片内容放大并居中显示
2017/06/23 jQuery
JS常见DOM节点操作示例【创建 ,插入,删除,复制,查找】
2018/05/14 Javascript
jQuery实现的简单歌词滚动功能示例
2019/01/07 jQuery
微信小程序官方动态自定义底部tabBar的例子
2019/09/04 Javascript
vue项目中使用vue-layer弹框插件的方法
2020/03/11 Javascript
vue实现整屏滚动切换
2020/06/29 Javascript
[01:09:19]DOTA2-DPC中国联赛 正赛 VG vs Aster BO3 第二场 2月28日
2021/03/11 DOTA
Python线程详解
2015/06/24 Python
python下调用pytesseract识别某网站验证码的实现方法
2016/06/06 Python
简单谈谈python的反射机制
2016/06/28 Python
python爬虫_实现校园网自动重连脚本的教程
2018/04/22 Python
python 实现分页显示从es中获取的数据方法
2018/12/26 Python
python Manager 之dict KeyError问题的解决
2019/12/21 Python
Python 生成VOC格式的标签实例
2020/03/10 Python
python 如何利用argparse解析命令行参数
2020/09/11 Python
在Ubuntu中安装并配置Pycharm教程的实现方法
2021/01/06 Python
CSS3 text shadow字体阴影效果
2016/01/08 HTML / CSS
英国计算机产品零售商:Novatech(定制个人电脑、笔记本电脑、工作站和服务器)
2018/01/28 全球购物
法国票务网站:Ticketmaster法国
2018/07/09 全球购物
元旦晚会策划方案
2014/02/18 职场文书
《问银河》教学反思
2014/02/19 职场文书
《白鹅》教学反思
2014/04/13 职场文书
2014年三万活动总结
2014/04/26 职场文书
酒店总经理岗位职责
2015/04/01 职场文书
工作会议通知
2015/04/15 职场文书
2015年生产车间工作总结
2015/04/22 职场文书
最感人的道歉情书
2015/05/12 职场文书
Mysql将字符串按照指定字符分割的正确方法
2022/05/30 MySQL