From dc3883f671893fe338ecbd25e84316728c6c48b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20v=2E=20L=C3=B6wis?= Date: Sun, 29 Aug 2004 15:46:35 +0000 Subject: Patch #934711: Expose platform-specific entropy. --- Doc/lib/libos.tex | 19 ++++++++++++++ Lib/os.py | 21 +++++++++++++++ Lib/test/test_os.py | 11 ++++++++ Misc/NEWS | 3 +++ Modules/posixmodule.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Doc/lib/libos.tex b/Doc/lib/libos.tex index f60693c..88e6402 100644 --- a/Doc/lib/libos.tex +++ b/Doc/lib/libos.tex @@ -1828,3 +1828,22 @@ Macintosh. Also available via \module{os.path}. \versionadded{2.4} \end{datadesc} + + +\subsection{Miscellaneous Functions \label{os-miscfunc}} + +\begin{funcdesc}{urandom}{n} +Return a string of \var{n} random bytes suitable for cryptographic use. + +This function returns random bytes from an OS-specific +randomness source. The returned data should be unpredictable enough for +cryptographic applications, though its exact quality depends on the OS +implementation. On a UNIX-like system this will query /dev/urandom, and +on Windows it will use CryptGenRandom. If a randomness source is not +found, \exception{NotImplementedError} will be raised. +\versionadded{2.4} +\end{funcdesc} + + + + diff --git a/Lib/os.py b/Lib/os.py index 5b79981..86b3566 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -656,3 +656,24 @@ try: _make_statvfs_result) except NameError: # statvfs_result may not exist pass + +if not _exists("urandom"): + _urandomfd = None + def urandom(n): + """urandom(n) -> str + + Return a string of n random bytes suitable for cryptographic use. + + """ + global _urandomfd + if not _urandomfd: + try: + _urandomfd = open("/dev/urandom", O_RDONLY) + except: + _urandomfd = NotImplementedError + if _urandomfd is NotImplementedError: + raise NotImplementedError("/dev/urandom (or equivalent) not found") + bytes = "" + while len(bytes) < n: + bytes += read(_urandomfd, n - len(bytes)) + return bytes diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 22cd112..a9668aa 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -343,6 +343,16 @@ class DevNullTests (unittest.TestCase): self.assertEqual(f.read(), '') f.close() +class URandomTests (unittest.TestCase): + def test_urandom(self): + try: + self.assertEqual(len(os.urandom(1)), 1) + self.assertEqual(len(os.urandom(10)), 10) + self.assertEqual(len(os.urandom(100)), 100) + self.assertEqual(len(os.urandom(1000)), 1000) + except NotImplementedError: + pass + def test_main(): test_support.run_unittest( TemporaryFileTests, @@ -351,6 +361,7 @@ def test_main(): WalkTests, MakedirTests, DevNullTests, + URandomTests ) if __name__ == "__main__": diff --git a/Misc/NEWS b/Misc/NEWS index d294440..a084d22 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -47,6 +47,9 @@ Core and builtins Extension modules ----------------- +- os.urandom has been added for systems that support sources of random + data. + - Patch 1012740: truncate() on a writeable cStringIO now resets the position to the end of the stream. This is consistent with the original StringIO module and avoids inadvertently resurrecting data that was diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 474e00e..21cab81 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -228,7 +228,7 @@ extern int lstat(const char *, struct stat *); #include #include #include "osdefs.h" -#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0400 /* Needed for CryptoAPI on some systems */ #include #include /* for ShellExecute() */ #define popen _popen @@ -7221,6 +7221,74 @@ posix_getloadavg(PyObject *self, PyObject *noargs) } #endif +#ifdef MS_WINDOWS + +PyDoc_STRVAR(win32_urandom__doc__, +"urandom(n) -> str\n\n\ +Return a string of n random bytes suitable for cryptographic use."); + +typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\ + LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\ + DWORD dwFlags ); +typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\ + BYTE *pbBuffer ); + +static CRYPTGENRANDOM pCryptGenRandom = NULL; +static HCRYPTPROV hCryptProv = 0; + +static PyObject* win32_urandom(PyObject *self, PyObject *args) +{ + int howMany = 0; + unsigned char* bytes = NULL; + PyObject* returnVal = NULL; + + /* Read arguments */ + if (!PyArg_ParseTuple(args, "i", &howMany)) + return(NULL); + + if (hCryptProv == 0) { + HINSTANCE hAdvAPI32 = NULL; + CRYPTACQUIRECONTEXTA pCryptAcquireContext = NULL; + + /* Obtain handle to the DLL containing CryptoAPI + This should not fail */ + if( (hAdvAPI32 = GetModuleHandle("advapi32.dll")) == NULL) + return win32_error("GetModuleHandle", NULL); + + /* Obtain pointers to the CryptoAPI functions + This will fail on some early versions of Win95 */ + pCryptAcquireContext=(CRYPTACQUIRECONTEXTA)GetProcAddress(hAdvAPI32,\ + "CryptAcquireContextA"); + pCryptGenRandom=(CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,\ + "CryptGenRandom"); + + if (pCryptAcquireContext == NULL || pCryptGenRandom == NULL) + return PyErr_Format(PyExc_NotImplementedError,\ + "CryptGenRandom not found"); + + /* Acquire context */ + if(!pCryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) + return win32_error("CryptAcquireContext", NULL); + } + + /* Allocate bytes */ + if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL) + return PyErr_NoMemory(); + + /* Get random data */ + if (!pCryptGenRandom(hCryptProv, howMany, bytes)) { + PyMem_Free(bytes); + return win32_error("CryptGenRandom", NULL); + } + + /* Build return value */ + returnVal = PyString_FromStringAndSize(bytes, howMany); + PyMem_Free(bytes); + + return returnVal; +} +#endif static PyMethodDef posix_methods[] = { {"access", posix_access, METH_VARARGS, posix_access__doc__}, @@ -7506,6 +7574,9 @@ static PyMethodDef posix_methods[] = { #ifdef HAVE_GETLOADAVG {"getloadavg", posix_getloadavg, METH_NOARGS, posix_getloadavg__doc__}, #endif + #ifdef MS_WINDOWS + {"urandom", win32_urandom, METH_VARARGS, win32_urandom__doc__}, + #endif {NULL, NULL} /* Sentinel */ }; -- cgit v0.12