Metadata-Version: 2.4
Name: randcrack
Version: 0.3.0
Summary: Predict python's random module random generated values
Author-email: Maxim Kochukov <kochukov.ma@gmail.com>
License: MIT License
        
        Copyright (c) 2017 Maxim
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/tna0y/Python-random-module-cracker
Project-URL: Repository, https://github.com/tna0y/Python-random-module-cracker
Project-URL: Issues, https://github.com/tna0y/Python-random-module-cracker/issues
Keywords: random,security,cryptography,cracker,encryption
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Security
Classifier: Topic :: Security :: Cryptography
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file


# randcrack – Python random module cracker / predictor

[![Test](https://github.com/tna0y/Python-random-module-cracker/actions/workflows/test.yml/badge.svg)](https://github.com/tna0y/Python-random-module-cracker/actions/workflows/test.yml)
![PyPI](https://img.shields.io/pypi/v/randcrack.svg)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/randcrack.svg)
![PyPI - Implementation](https://img.shields.io/pypi/implementation/randcrack.svg)

This script is able to predict python's `random` module random generated values.

Script is tested against Python versions from **3.9** to **3.13**. Should work against other versions of CPython as well, since the underlying Mersenne Twister generator has not changed. Enjoy!

## Installation
To install randcrack, simply:

```bash
$ pip install randcrack
```

## How it works
The generator is based upon *Mersenne Twister*, which is able to generate numbers with excellent statistical properties(indistinguishable from truly random). However, this generator was not designed to be cryptographycally secure. You should NEVER use in critical applications as a PRNG for your crypto scheme.
You can learn more about this generator [on Wikipedia](https://en.wikipedia.org/wiki/Mersenne_Twister).

This cracker works as the following way. It obtains first 624 32 bit numbers from the generator and obtains the most likely state of Mersenne Twister matrix, which is the internal state. From this point generator should be synchronized with the cracker.

## How to use
It is **important to feed cracker exactly 32-bit integers** generated by the generator due to the fact that they will be generated anyway, but dropped if you don't request for them.
As well, you must feed the cracker exactly after new seed is presented, or after 624*32 bits are generated since every 624 32-bit numbers generator shifts it's state and cracker is designed to be fed from the begining of some state.

### Implemented methods

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`, `predict_choice` and `predict_random`

Here's an example usage:
```python
import random, time
from randcrack import RandCrack

random.seed(time.time())

rc = RandCrack()

for i in range(624):
	rc.submit(random.getrandbits(32))
	# Could be filled with random.randint(0,4294967294) or random.randrange(0,4294967294)

print("Random result: {}\nCracker result: {}"
	.format(random.randrange(0, 4294967295), rc.predict_randrange(0, 4294967295)))
```
**Output**
```	
Random result: 127160928
Cracker result: 127160928
```

As well as predicting future values, it can recover the *previous* states to predict earlier values, ones that came before the numbers you submit. After having submitted enough random numbers to clone the internal state (624), you can use the `offset(n)` method to offset the state by some number.

A positive number simply advances the RNG by `n`, as if you would ask for a number repeatedly `n` times. A **negative** number however will *untwist* the internal state (which can also be done manually with `untwist()`). Then after untwisting enough times it will set the internal state to exactly the point in the past where previous numbers were generated from. From then on, you can call the `predict_*()` methods again to get random numbers, now in the past. 

```python
import random, time
from randcrack import RandCrack

random.seed(time.time())

unknown = [random.getrandbits(32) for _ in range(10)]

cracker = RandCrack()

for _ in range(624):
	cracker.submit(random.getrandbits(32))

cracker.offset(-624)  # Go back -624 states from submitted numbers
cracker.offset(-10)   # Go back -10 states to the start of `unknown`

print("Unknown:", unknown)
print("Guesses:", [cracker.predict_getrandbits(32) for _ in range(10)])
```

> **Warning**: The `randint()`, `randrange()` and `choice()` methods all use `randbelow(n)`, which will internally may advance the state **multiple times** depending on the random number that comes from the generator. A number is generated with the number of bits `n` has, but it may still be above `n` the first time. In that case numbers keep being generated in this way until one is below `n`. 
> 
> This causes predicting **previous** values of these functions to become imprecise as it is not yet known how many numbers were generated with the single function call. You will still be able to generate all the numbers if you offset back further than expected to include all numbers, but there will be an amount of numbers before/after the target sequence (e.g. if the sequence is `[1, 2, 3]`, guesses may be `[123, 42, 1, 2, 3, 1337]`).
>
> This is not a problem with the `getrandbits()` method, as it always does exactly 1. And the `random()` method always does exactly 2
