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双向链表实现实例代码
Nov 21 Python
python基础教程之分支、循环简单用法
Jun 16 Python
详解Python map函数及Python map()函数的用法
Nov 16 Python
python实现kMeans算法
Dec 21 Python
详解tensorflow训练自己的数据集实现CNN图像分类
Feb 07 Python
详解Python3之数据指纹MD5校验与对比
Jun 11 Python
Python timeit模块的使用实践
Jan 13 Python
Python vtk读取并显示dicom文件示例
Jan 13 Python
Python如何使用内置库matplotlib绘制折线图
Feb 24 Python
python3.6.5基于kerberos认证的hive和hdfs连接调用方式
Jun 06 Python
python re.match()用法相关示例
Jan 27 Python
Python xlwings插入Excel图片的实现方法
Feb 26 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
PHP STRING 陷阱原理说明
2010/07/24 PHP
推荐十款免费 WordPress 插件
2015/03/24 PHP
PHP操作MySQL中BLOB字段的方法示例【存储文本与图片】
2017/09/15 PHP
php判断IP地址是否在多个IP段内
2020/08/18 PHP
JavaScript 事件属性绑定带参数的函数
2009/03/13 Javascript
jQuery 1.4 15个你应该知道的新特性(译)
2010/01/24 Javascript
屏蔽F1~F12的快捷键的js函数
2010/05/06 Javascript
jquery 学习之二 属性(html()与html(val))
2010/11/25 Javascript
javascript之typeof、instanceof操作符使用探讨
2013/05/19 Javascript
js判断选择的时间是否大于今天的代码
2013/08/20 Javascript
Javascript Objects详解
2014/09/04 Javascript
js获取时间并实现字符串和时间戳之间的转换
2015/01/05 Javascript
js获取当前日期前七天的方法
2015/02/28 Javascript
JavaScript中Boolean对象的属性解析
2015/10/21 Javascript
大型JavaScript应用程序架构设计模式
2016/06/29 Javascript
详解js中call与apply关键字的作用
2016/11/21 Javascript
js和jquery中获取非行间样式
2017/05/05 jQuery
JS库之wow.js使用方法
2017/09/14 Javascript
一起深入理解js中的事件对象
2021/02/06 Javascript
详细介绍Python语言中的按位运算符
2013/11/26 Python
python实现字符串中字符分类及个数统计
2018/09/28 Python
解决python3运行selenium下HTMLTestRunner报错的问题
2018/12/27 Python
python爬取基于m3u8协议的ts文件并合并
2019/04/26 Python
解决windows下python3使用multiprocessing.Pool出现的问题
2020/04/08 Python
Python ORM框架Peewee用法详解
2020/04/29 Python
Python读取图像并显示灰度图的实现
2020/12/01 Python
python实现简单的井字棋游戏(gui界面)
2021/01/22 Python
浏览器实现移动端高性能css3动画(开启gpu加速)
2013/12/23 HTML / CSS
海量信息软件测试笔试题
2015/08/08 面试题
大二自我鉴定
2014/01/31 职场文书
《巨人的花园》教学反思
2014/02/12 职场文书
《王二小》教学反思
2014/02/27 职场文书
人事科岗位职责范本
2014/03/02 职场文书
财务出纳岗位职责
2015/03/31 职场文书
暑期辅导班宣传单
2015/07/14 职场文书
品牌形象定位,全面分析
2019/07/23 职场文书