diff options
| author | Victor Stinner <vstinner@python.org> | 2020-04-17 19:05:35 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-04-17 19:05:35 +0200 |
| commit | 9f5fe7910f4a1bf5a425837d4915e332b945eb7b (patch) | |
| tree | 5f652699332e33a81cf6baa5ff5b6fecadea1903 /Lib | |
| parent | 22386bb4ef740ee92d34c87b8cb90d681423a853 (diff) | |
| download | cpython-git-9f5fe7910f4a1bf5a425837d4915e332b945eb7b.tar.gz | |
bpo-40286: Add randbytes() method to random.Random (GH-19527)
Add random.randbytes() function and random.Random.randbytes()
method to generate random bytes.
Modify secrets.token_bytes() to use SystemRandom.randbytes()
rather than calling directly os.urandom().
Rename also genrand_int32() to genrand_uint32(), since it returns an
unsigned 32-bit integer, not a signed integer.
The _random module is now built with Py_BUILD_CORE_MODULE defined.
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/random.py | 7 | ||||
| -rw-r--r-- | Lib/secrets.py | 3 | ||||
| -rw-r--r-- | Lib/test/test_random.py | 51 |
3 files changed, 59 insertions, 2 deletions
diff --git a/Lib/random.py b/Lib/random.py index e24737d450..82345fab92 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -739,6 +739,12 @@ class SystemRandom(Random): x = int.from_bytes(_urandom(numbytes), 'big') return x >> (numbytes * 8 - k) # trim excess bits + def randbytes(self, n): + """Generate n random bytes.""" + # os.urandom(n) fails with ValueError for n < 0 + # and returns an empty bytes string for n == 0. + return _urandom(n) + def seed(self, *args, **kwds): "Stub method. Not used for a system random number generator." return None @@ -819,6 +825,7 @@ weibullvariate = _inst.weibullvariate getstate = _inst.getstate setstate = _inst.setstate getrandbits = _inst.getrandbits +randbytes = _inst.randbytes if hasattr(_os, "fork"): _os.register_at_fork(after_in_child=_inst.seed) diff --git a/Lib/secrets.py b/Lib/secrets.py index 130434229e..a546efbdd4 100644 --- a/Lib/secrets.py +++ b/Lib/secrets.py @@ -14,7 +14,6 @@ __all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom', import base64 import binascii -import os from hmac import compare_digest from random import SystemRandom @@ -44,7 +43,7 @@ def token_bytes(nbytes=None): """ if nbytes is None: nbytes = DEFAULT_ENTROPY - return os.urandom(nbytes) + return _sysrand.randbytes(nbytes) def token_hex(nbytes=None): """Return a random text string, in hexadecimal. diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 548af706db..f709e52ecb 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -291,6 +291,22 @@ class TestBasicOps: k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n)) self.assertTrue(0.30 < k/n < .37, (k/n)) + def test_randbytes(self): + # Verify ranges + for n in range(1, 10): + data = self.gen.randbytes(n) + self.assertEqual(type(data), bytes) + self.assertEqual(len(data), n) + + self.assertEqual(self.gen.randbytes(0), b'') + + # Verify argument checking + self.assertRaises(TypeError, self.gen.randbytes) + self.assertRaises(TypeError, self.gen.randbytes, 1, 2) + self.assertRaises(ValueError, self.gen.randbytes, -1) + self.assertRaises(TypeError, self.gen.randbytes, 1.0) + + try: random.SystemRandom().random() except NotImplementedError: @@ -747,6 +763,41 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase): c = self.gen.choices(population, cum_weights=cum_weights, k=10000) self.assertEqual(a, c) + def test_randbytes(self): + super().test_randbytes() + + # Mersenne Twister randbytes() is deterministic + # and does not depend on the endian and bitness. + seed = 8675309 + expected = b'f\xf9\xa836\xd0\xa4\xf4\x82\x9f\x8f\x19\xf0eo\x02' + + self.gen.seed(seed) + self.assertEqual(self.gen.randbytes(16), expected) + + # randbytes(0) must not consume any entropy + self.gen.seed(seed) + self.assertEqual(self.gen.randbytes(0), b'') + self.assertEqual(self.gen.randbytes(16), expected) + + # Four randbytes(4) calls give the same output than randbytes(16) + self.gen.seed(seed) + self.assertEqual(b''.join([self.gen.randbytes(4) for _ in range(4)]), + expected) + + # Each randbytes(2) or randbytes(3) call consumes 4 bytes of entropy + self.gen.seed(seed) + expected2 = b''.join(expected[i:i + 2] + for i in range(0, len(expected), 4)) + self.assertEqual(b''.join(self.gen.randbytes(2) for _ in range(4)), + expected2) + + self.gen.seed(seed) + expected3 = b''.join(expected[i:i + 3] + for i in range(0, len(expected), 4)) + self.assertEqual(b''.join(self.gen.randbytes(3) for _ in range(4)), + expected3) + + def gamma(z, sqrt2pi=(2.0*pi)**0.5): # Reflection to right half of complex plane if z < 0.5: |
