【问题标题】:How to generate a byte string consisting of random nonzero bytes using Python?如何使用 Python 生成由随机非零字节组成的字节字符串?
【发布时间】:2022-01-29 23:55:15
【问题描述】:

作为实现 RSA 的PKCS #1 v1.5 填充方案的一部分,我需要生成一个长度为 n 的八位字节字符串,其中包含伪随机生成的非零八位字节。 我正在寻找使用 Python 执行此操作的最佳方法。

这是我当前的实现的样子:

def nonzero_random_bytes(n: int) -> bytes:
    values = [x.to_bytes(1, "big") for x in range(1, 256)]
    seq = [secrets.choice(values) for _ in range(n)]
    return b"".join(seq)

我查看了使用secrets.token_bytes(n) 生成字节字符串、过滤结果并生成非零值以回填字符串。我知道我也可以执行secrets.token_bytes(2 * n) 之类的操作、过滤和修剪结果,但这并不是一个优雅的解决方案。

我还研究了 PyCryptodomepython-pkcs1 是如何做到这一点的,但我认为一定有更好的方法(我在 pyca/cryptography 周围闲逛 但找不到他们是如何做到的 似乎他们使用 OpenSSL 绑定 - here's 我认为这是实现的)。

免责声明:我知道我不应该使用 PKCS1 v1.5,更不用说自己推出任何加密代码了。这纯粹是一个学术练习。 :)

【问题讨论】:

  • 您从secrets.token_bytes(2 * n) 开始并修剪零的想法已经足够优雅了。 secrets 中没有接口可以更优雅地完成此操作。
  • n 有多大?它需要很快吗?
  • n 是 RSA 的填充长度,可能是 100-400 字节,具体取决于模数大小

标签: python random cryptography rsa


【解决方案1】:

您没有定义“最佳”对您意味着什么。我会选择这个,这基本上是一种不那么罗嗦的方式来做你已经做过的事情:

from secrets import randbelow

def nonzero_random_bytes(n: int) -> bytes:
    return bytes(randbelow(255) + 1 for _ in range(n))

【讨论】:

  • 好点; best 可能会在这里定义为 pythonic,同时尊重加密的良好实践。这是一个很好的解决方案,+1 转换在事后看来是如此明显。
  • 是的,它保持简单。过滤更复杂,因为它更难表达。在幕后,randbelow()choice() 已经在过滤——它们从源中挑选“下一个”位串,看看它们是否代表一个足够小的整数——如果不是,则返回内部循环的顶部再试一次。该代码已经被许多人审查过,所以现在稍微值得信赖;-)
  • 在哪里可以看到randbelow的代码?我已经看了,还没有找到。
  • Lib/random.py中有两个实现,方法_randbelow_with_getrandbits()_randbelow_without_getrandbits()getrandbits() 依次用 C 实现,函数 _random_Random_getrandbits_impl() 在 Modules/_randommodule.c 中。
  • 虽然getrandbits() 的版本最终在Lib/random.py 中使用secrets 模块是SystemRandom.getrandbits()
【解决方案2】:

与蒂姆几乎相同,但认为“最好”可能需要速度。 n = 250 的基准(“可能是 100-400”范围的中间):

471.3 us  nonzero_random_bytes_original
438.3 us  nonzero_random_bytes_randbelow
  4.7 us  nonzero_random_bytes_2n
  3.1 us  nonzero_random_bytes_plus10

代码(Try it online!):

from timeit import timeit
import secrets

def nonzero_random_bytes_original(n: int) -> bytes:
    values = [x.to_bytes(1, "big") for x in range(1, 256)]
    seq = [secrets.choice(values) for _ in range(n)]
    return b"".join(seq)

def nonzero_random_bytes_randbelow(n: int) -> bytes:
    return bytes(1 + secrets.randbelow(255) for _ in range(n))

def nonzero_random_bytes_2n(n: int) -> bytes:
    return secrets.token_bytes(2 * n).replace(b'\0', b'')[:n]

def nonzero_random_bytes_plus10(n: int) -> bytes:
    result = b''
    while need := n - len(result):
        result += secrets.token_bytes(need + 10).replace(b'\0', b'')[:need]
    return result

funcs = [
    nonzero_random_bytes_original,
    nonzero_random_bytes_randbelow,
    nonzero_random_bytes_2n,
    nonzero_random_bytes_plus10,
]

for _ in range(3):
    for func in funcs:
        t = timeit(lambda: func(250), number=1000)
        print('%5.1f us ' % (t * 1e3), func.__name__)
    print()

【讨论】:

  • 我打算跟进类似的事情,但这很壮观。很高兴看到我最初对 token_bytes 的直觉是经过验证的性能最高的。您将nonzero_random_bytes_randbelow2 中使用的技术称为什么?我不太熟悉保留本地参考的好处。
  • 不知道有没有合适的名字。蒂姆会知道的。我有时称它为“本地化”。好处是加载速度更快,但这是否重要取决于整个代码。
  • 在编译器术语中,用更便宜的等价物替换操作称为“强度降低”。在 CPython 中访问局部变量比访问全局变量要快。在加密环境中,“显然正确”的价值高于“没有明显错误”。如果过滤方法的速度很重要,那就让它防弹:检查最终长度 n;如果不是,请再试一次。一些加密极客可能会抱怨 2 倍的过采样是一个坏主意,它会以所需速率的两倍消耗操作系统的真实熵池。不打扰我 - 但我不是加密极客 ;-)
  • @Tim Peters 你会如何在句子中使用“强度降低”来描述我在那里所做的事情? (不过,我刚刚删除了它,因为我得到了相互矛盾的结果,所以它似乎具有误导性和分散注意力。)我可能会说“我本地化了 secrets.randbelow”。现在添加了“加 10”解决方案,以使其防弹且减少过采样。结果也更快。
  • 我只想说“我将secrets.randbelow 绑定到一个本地名称”。每个人在 Python 中都做过无数次类似的事情,所以每个人都知道它的含义。
猜你喜欢
  • 1970-01-01
  • 2014-05-21
  • 2014-02-09
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 2016-11-16
  • 2011-07-26
  • 2014-09-03
相关资源
最近更新 更多