Repository: tna0y/Python-random-module-cracker

Issue:
random.random() is not based on /dev/urandom
The README states: `Note: Cracker does not implement prediction of random() function since it is based on the os.urandom module which is based on /dev/urandom` - however, at least in current versions of CPython, this is not true. You can find the implementation of this function for Python 3.10 here: https://github.com/python/cpython/blob/60adc4b92a8a6fe115a023c8f639a6de4730fac1/Modules/_randommodule.c#L153-L177
It's basically these two lines:
```c
uint32_t a=genrand_uint32(self)>>5, b=genrand_uint32(self)>>6;
return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0));
```
with `genrand_uint32` being the MT random generator, the same as in `getrandbits()`. One needs to use `SystemRandom` to get the randomness from the os directly.

There is a slight loss of information about the state here, as in total 11 bits are zeroed, so I'm not sure if it can be used to crack the generator anyway, but it might be a good idea to correct the README at least, and I'll try to add a prediction generator at least.

Discussion:

Patch:
diff --git a/README.md b/README.md
index a6d4f17..653c10a 100644
--- a/README.md
+++ b/README.md
@@ -31,9 +31,7 @@ As well, you must feed the cracker exactly after new seed is presented, or after
 
 Cracker has one method for feeding: `submit(n)`. After submitting 624 integers it won't take any more and will be ready for predicting new numbers.
 
-Cracker can predict new numbers with following methods, which work exactly the same as their siblings from the `random` module but without `predict_` prefix. These are: `predict_getrandbits`, `predict_randbelow`, `predict_randrange`,  `predict_randint` and `predict_choice`
-
-**Note:** Cracker does not implement prediction of `random()` function since it is based on the `os.urandom` module which is based on `/dev/urandom`.
+Cracker can predict new numbers with following methods, which work exactly the same as their siblings from the `random` module but without `predict_` prefix. These are: `predict_getrandbits`, `predict_randbelow`, `predict_randrange`,  `predict_randint`, `predict_choice` and `predict_random`
 
 Here's an example usage:
 ```python
diff --git a/randcrack/randcrack.py b/randcrack/randcrack.py
index 9c4e03f..c092238 100644
--- a/randcrack/randcrack.py
+++ b/randcrack/randcrack.py
@@ -98,6 +98,11 @@ class RandCrack:
             raise IndexError('Cannot choose from an empty sequence')
         return seq[i]
 
+    def predict_random(self):
+        a = self._to_int(self._predict_32()) >> 5
+        b = self._to_int(self._predict_32()) >> 6
+        return ((a*67108864.0)+b)/9007199254740992.0
+
     def _to_bitarray(self, num):
         k = [int(x) for x in bin(num)[2:]]
         return [0] * (32 - len(k)) + k


Test patch:
diff --git a/tests/test_randcrack.py b/tests/test_randcrack.py
index 74032cb..f36cf82 100644
--- a/tests/test_randcrack.py
+++ b/tests/test_randcrack.py
@@ -50,3 +50,12 @@ def test_predict_first_1000_close():
         cracker.submit(random.randint(0, 4294967294))
 
     assert sum([random.getrandbits(32) == cracker.predict_getrandbits(32) for _ in range(1000)]) >= 980
+
+def test_predict_random():
+    random.seed(time.time())
+
+    cracker = RandCrack()
+
+    for i in range(624):
+        cracker.submit(random.randint(0, 4294967294))
+    assert sum([random.random() == cracker.predict_random() for _ in range(1000)]) >= 980