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.
This commit is contained in:
parent
22386bb4ef
commit
9f5fe7910f
|
@ -112,6 +112,13 @@ Bookkeeping functions
|
||||||
:meth:`randrange` to handle arbitrarily large ranges.
|
:meth:`randrange` to handle arbitrarily large ranges.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: randbytes(n)
|
||||||
|
|
||||||
|
Generate *n* random bytes.
|
||||||
|
|
||||||
|
.. versionadded:: 3.9
|
||||||
|
|
||||||
|
|
||||||
Functions for integers
|
Functions for integers
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -353,6 +353,12 @@ The documentation string is now shown not only for class, function,
|
||||||
method etc, but for any object that has its own ``__doc__`` attribute.
|
method etc, but for any object that has its own ``__doc__`` attribute.
|
||||||
(Contributed by Serhiy Storchaka in :issue:`40257`.)
|
(Contributed by Serhiy Storchaka in :issue:`40257`.)
|
||||||
|
|
||||||
|
random
|
||||||
|
------
|
||||||
|
|
||||||
|
Add a new :attr:`random.Random.randbytes` method: generate random bytes.
|
||||||
|
(Contributed by Victor Stinner in :issue:`40286`.)
|
||||||
|
|
||||||
signal
|
signal
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -739,6 +739,12 @@ class SystemRandom(Random):
|
||||||
x = int.from_bytes(_urandom(numbytes), 'big')
|
x = int.from_bytes(_urandom(numbytes), 'big')
|
||||||
return x >> (numbytes * 8 - k) # trim excess bits
|
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):
|
def seed(self, *args, **kwds):
|
||||||
"Stub method. Not used for a system random number generator."
|
"Stub method. Not used for a system random number generator."
|
||||||
return None
|
return None
|
||||||
|
@ -819,6 +825,7 @@ weibullvariate = _inst.weibullvariate
|
||||||
getstate = _inst.getstate
|
getstate = _inst.getstate
|
||||||
setstate = _inst.setstate
|
setstate = _inst.setstate
|
||||||
getrandbits = _inst.getrandbits
|
getrandbits = _inst.getrandbits
|
||||||
|
randbytes = _inst.randbytes
|
||||||
|
|
||||||
if hasattr(_os, "fork"):
|
if hasattr(_os, "fork"):
|
||||||
_os.register_at_fork(after_in_child=_inst.seed)
|
_os.register_at_fork(after_in_child=_inst.seed)
|
||||||
|
|
|
@ -14,7 +14,6 @@ __all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom',
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import os
|
|
||||||
|
|
||||||
from hmac import compare_digest
|
from hmac import compare_digest
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
|
@ -44,7 +43,7 @@ def token_bytes(nbytes=None):
|
||||||
"""
|
"""
|
||||||
if nbytes is None:
|
if nbytes is None:
|
||||||
nbytes = DEFAULT_ENTROPY
|
nbytes = DEFAULT_ENTROPY
|
||||||
return os.urandom(nbytes)
|
return _sysrand.randbytes(nbytes)
|
||||||
|
|
||||||
def token_hex(nbytes=None):
|
def token_hex(nbytes=None):
|
||||||
"""Return a random text string, in hexadecimal.
|
"""Return a random text string, in hexadecimal.
|
||||||
|
|
|
@ -291,6 +291,22 @@ class TestBasicOps:
|
||||||
k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n))
|
k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n))
|
||||||
self.assertTrue(0.30 < k/n < .37, (k/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:
|
try:
|
||||||
random.SystemRandom().random()
|
random.SystemRandom().random()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
|
@ -747,6 +763,41 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
|
||||||
c = self.gen.choices(population, cum_weights=cum_weights, k=10000)
|
c = self.gen.choices(population, cum_weights=cum_weights, k=10000)
|
||||||
self.assertEqual(a, c)
|
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):
|
def gamma(z, sqrt2pi=(2.0*pi)**0.5):
|
||||||
# Reflection to right half of complex plane
|
# Reflection to right half of complex plane
|
||||||
if z < 0.5:
|
if z < 0.5:
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :func:`random.randbytes` function and
|
||||||
|
:meth:`random.Random.randbytes` method to generate random bytes.
|
|
@ -174,7 +174,7 @@ _symtable symtablemodule.c
|
||||||
#_weakref _weakref.c # basic weak reference support
|
#_weakref _weakref.c # basic weak reference support
|
||||||
#_testcapi _testcapimodule.c # Python C API test module
|
#_testcapi _testcapimodule.c # Python C API test module
|
||||||
#_testinternalcapi _testinternalcapi.c -I$(srcdir)/Include/internal -DPy_BUILD_CORE_MODULE # Python internal C API test module
|
#_testinternalcapi _testinternalcapi.c -I$(srcdir)/Include/internal -DPy_BUILD_CORE_MODULE # Python internal C API test module
|
||||||
#_random _randommodule.c # Random number generator
|
#_random _randommodule.c -DPy_BUILD_CORE_MODULE # Random number generator
|
||||||
#_elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c # elementtree accelerator
|
#_elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c # elementtree accelerator
|
||||||
#_pickle _pickle.c # pickle accelerator
|
#_pickle _pickle.c # pickle accelerator
|
||||||
#_datetime _datetimemodule.c # datetime accelerator
|
#_datetime _datetimemodule.c # datetime accelerator
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
* renamed genrand_res53() to random_random() and wrapped
|
* renamed genrand_res53() to random_random() and wrapped
|
||||||
in python calling/return code.
|
in python calling/return code.
|
||||||
|
|
||||||
* genrand_int32() and the helper functions, init_genrand()
|
* genrand_uint32() and the helper functions, init_genrand()
|
||||||
and init_by_array(), were declared static, wrapped in
|
and init_by_array(), were declared static, wrapped in
|
||||||
Python calling/return code. also, their global data
|
Python calling/return code. also, their global data
|
||||||
references were replaced with structure references.
|
references were replaced with structure references.
|
||||||
|
@ -67,9 +67,9 @@
|
||||||
/* ---------------------------------------------------------------*/
|
/* ---------------------------------------------------------------*/
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include <time.h> /* for seeding to current time */
|
#include "pycore_byteswap.h" // _Py_bswap32()
|
||||||
#ifdef HAVE_PROCESS_H
|
#ifdef HAVE_PROCESS_H
|
||||||
# include <process.h> /* needed for getpid() */
|
# include <process.h> // getpid()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Period parameters -- These are all magic. Don't change. */
|
/* Period parameters -- These are all magic. Don't change. */
|
||||||
|
@ -116,7 +116,7 @@ class _random.Random "RandomObject *" "&Random_Type"
|
||||||
|
|
||||||
/* generates a random number on [0,0xffffffff]-interval */
|
/* generates a random number on [0,0xffffffff]-interval */
|
||||||
static uint32_t
|
static uint32_t
|
||||||
genrand_int32(RandomObject *self)
|
genrand_uint32(RandomObject *self)
|
||||||
{
|
{
|
||||||
uint32_t y;
|
uint32_t y;
|
||||||
static const uint32_t mag01[2] = {0x0U, MATRIX_A};
|
static const uint32_t mag01[2] = {0x0U, MATRIX_A};
|
||||||
|
@ -171,7 +171,7 @@ static PyObject *
|
||||||
_random_Random_random_impl(RandomObject *self)
|
_random_Random_random_impl(RandomObject *self)
|
||||||
/*[clinic end generated code: output=117ff99ee53d755c input=afb2a59cbbb00349]*/
|
/*[clinic end generated code: output=117ff99ee53d755c input=afb2a59cbbb00349]*/
|
||||||
{
|
{
|
||||||
uint32_t a=genrand_int32(self)>>5, b=genrand_int32(self)>>6;
|
uint32_t a=genrand_uint32(self)>>5, b=genrand_uint32(self)>>6;
|
||||||
return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0));
|
return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +481,7 @@ _random_Random_getrandbits_impl(RandomObject *self, int k)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k <= 32) /* Fast path */
|
if (k <= 32) /* Fast path */
|
||||||
return PyLong_FromUnsignedLong(genrand_int32(self) >> (32 - k));
|
return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k));
|
||||||
|
|
||||||
words = (k - 1) / 32 + 1;
|
words = (k - 1) / 32 + 1;
|
||||||
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
|
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
|
||||||
|
@ -498,7 +498,7 @@ _random_Random_getrandbits_impl(RandomObject *self, int k)
|
||||||
for (i = words - 1; i >= 0; i--, k -= 32)
|
for (i = words - 1; i >= 0; i--, k -= 32)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
r = genrand_int32(self);
|
r = genrand_uint32(self);
|
||||||
if (k < 32)
|
if (k < 32)
|
||||||
r >>= (32 - k); /* Drop least significant bits */
|
r >>= (32 - k); /* Drop least significant bits */
|
||||||
wordarray[i] = r;
|
wordarray[i] = r;
|
||||||
|
@ -510,6 +510,56 @@ _random_Random_getrandbits_impl(RandomObject *self, int k)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
|
||||||
|
_random.Random.randbytes
|
||||||
|
|
||||||
|
self: self(type="RandomObject *")
|
||||||
|
n: Py_ssize_t
|
||||||
|
/
|
||||||
|
|
||||||
|
Generate n random bytes.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_random_Random_randbytes_impl(RandomObject *self, Py_ssize_t n)
|
||||||
|
/*[clinic end generated code: output=67a28548079a17ea input=7ba658a24150d233]*/
|
||||||
|
{
|
||||||
|
if (n < 0) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"number of bytes must be non-negative");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
|
/* Don't consume any entropy */
|
||||||
|
return PyBytes_FromStringAndSize(NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *bytes = PyBytes_FromStringAndSize(NULL, n);
|
||||||
|
if (bytes == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
uint8_t *ptr = (uint8_t *)PyBytes_AS_STRING(bytes);
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint32_t word = genrand_uint32(self);
|
||||||
|
#if PY_LITTLE_ENDIAN
|
||||||
|
/* Convert to big endian */
|
||||||
|
word = _Py_bswap32(word);
|
||||||
|
#endif
|
||||||
|
if (n < 4) {
|
||||||
|
memcpy(ptr, &word, n);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(ptr, &word, 4);
|
||||||
|
ptr += 4;
|
||||||
|
n -= 4;
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
random_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
random_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
@ -539,6 +589,7 @@ static PyMethodDef random_methods[] = {
|
||||||
_RANDOM_RANDOM_GETSTATE_METHODDEF
|
_RANDOM_RANDOM_GETSTATE_METHODDEF
|
||||||
_RANDOM_RANDOM_SETSTATE_METHODDEF
|
_RANDOM_RANDOM_SETSTATE_METHODDEF
|
||||||
_RANDOM_RANDOM_GETRANDBITS_METHODDEF
|
_RANDOM_RANDOM_GETRANDBITS_METHODDEF
|
||||||
|
_RANDOM_RANDOM_RANDBYTES_METHODDEF
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -114,4 +114,45 @@ _random_Random_getrandbits(RandomObject *self, PyObject *arg)
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=a7feb0c9c8d1b627 input=a9049054013a1b77]*/
|
|
||||||
|
PyDoc_STRVAR(_random_Random_randbytes__doc__,
|
||||||
|
"randbytes($self, n, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Generate n random bytes.");
|
||||||
|
|
||||||
|
#define _RANDOM_RANDOM_RANDBYTES_METHODDEF \
|
||||||
|
{"randbytes", (PyCFunction)_random_Random_randbytes, METH_O, _random_Random_randbytes__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_random_Random_randbytes_impl(RandomObject *self, Py_ssize_t n);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_random_Random_randbytes(RandomObject *self, PyObject *arg)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
Py_ssize_t n;
|
||||||
|
|
||||||
|
if (PyFloat_Check(arg)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"integer argument expected, got float" );
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Py_ssize_t ival = -1;
|
||||||
|
PyObject *iobj = PyNumber_Index(arg);
|
||||||
|
if (iobj != NULL) {
|
||||||
|
ival = PyLong_AsSsize_t(iobj);
|
||||||
|
Py_DECREF(iobj);
|
||||||
|
}
|
||||||
|
if (ival == -1 && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
n = ival;
|
||||||
|
}
|
||||||
|
return_value = _random_Random_randbytes_impl(self, n);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
/*[clinic end generated code: output=e515c651860c4001 input=a9049054013a1b77]*/
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -808,7 +808,8 @@ class PyBuildExt(build_ext):
|
||||||
self.add(Extension('_datetime', ['_datetimemodule.c'],
|
self.add(Extension('_datetime', ['_datetimemodule.c'],
|
||||||
libraries=['m']))
|
libraries=['m']))
|
||||||
# random number generator implemented in C
|
# random number generator implemented in C
|
||||||
self.add(Extension("_random", ["_randommodule.c"]))
|
self.add(Extension("_random", ["_randommodule.c"],
|
||||||
|
extra_compile_args=['-DPy_BUILD_CORE_MODULE']))
|
||||||
# bisect
|
# bisect
|
||||||
self.add(Extension("_bisect", ["_bisectmodule.c"]))
|
self.add(Extension("_bisect", ["_bisectmodule.c"]))
|
||||||
# heapq
|
# heapq
|
||||||
|
|
Loading…
Reference in New Issue