如何使用 Python 生成由随机非零字节组成的字节串?

How to generate a byte string consisting of random nonzero bytes using Python?

作为实现 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)、过滤和 trim 结果的事情,但这并不是一个优雅的解决方案。

我还研究了 PyCryptodome and python-pkcs1 do this but I'm thinking there must be a better way (I poked around pyca/cryptography but couldn't find how they did it and it seems they use OpenSSL bindings - here's 我认为是如何实施的)。

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

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

from secrets import randbelow

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

与 Tim 几乎一样,但认为“最佳”可能需要速度。 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'[=11=]', 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'[=11=]', 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()