Skip to main content

a better random cracker can predict python random lib number

Project description

Pyrandcracker

Pyrandcracker is a tool for predicting the random numbers generated by Python’s random library.

English | 中文

English Version is generate by GPT-4o

Project Introduction

Pyrandcracker leverages the properties of random number generators (such as the MT19937 algorithm) by collecting enough random number samples to reverse-engineer the generator’s internal state and thus predict future random numbers.

Features

  • Supports operations on random numbers with any number of bits.
  • Migrates some functions from the SageMath matrix module.
  • Predicts the generator’s internal state when provided with at least 19937 bits of random numbers.

Installation

Not yet uploaded to platforms such as PyPI.

Usage

32-bit Submission

The project supports input of numbers with any bit-length as long as the total submitted bits exceed 19937 bits. Due to the properties of MT19937, the project is optimized when the number of submitted bits is a multiple of 32. This optimization is only effective when each submission is exactly 32 bits or a multiple thereof.

If you strongly prefer solving using matrix methods in certain cases, you can enforce this by passing force_matrix = True when calling the solve method. (Not recommended.)

from randcracker import RandCracker
import time
# Initialize the random number generator
rd = random.Random()
rd.seed(time.time())
# Initialize the predictor
rc = RandCracker()

data = [rd.getrandbits(64) for _ in range(312)]
for num in data:
    # Submitting a total of 312 * 64 = 19968 bits
    rc.submit(num)
# Check if the solution is obtainable and automatically solve
rc.check()

print(f"next random number is {rd.getrandbits(32)}")
# You can either use rc.rnd to access the cracked Random class,
# or use the rc.get_random function to explicitly obtain and save the variable.
print(f"predict next random number is {rc.rnd.getrandbits(32)}")

Arbitrary Bit Submission

Sometimes you might not be able to obtain bits in multiples of 32, and the number of bits you get may vary. In this case, you need to solve a system of linear equations. You can specify the number of bits of each submitted number through the second parameter of the submit function to inform the predictor.

from randcracker import RandCracker
import time
# Initialize the random number generator
rd = random.Random()
rd.seed(time.time())
# Initialize the predictor. The 'detail' parameter uses tqdm’s trange 
# to display a progress bar, but may slightly impact performance and produce unnecessary output.
# The default value for the detail parameter is False.
rc = RandCracker(detail = True)
data = [rd.getrandbits(16) for _ in range(624*2)]
for num in data:
    # Submitting a total of 624*2*16 = 19968 bits. You can submit more and it will still compute.
    rc.submit(num, 16)
# Check if the solution is obtainable and automatically solve
rc.check()
print(f"next random number is {rd.getrandbits(16)}")
print(f"predict next random number is {rc.rnd.getrandbits(16)}")

Note that due to limitations with numpy and Python, the solving process may be relatively slow (in the worst-case scenario, such as submitting 19937 numbers of 1 bit each, the prediction might take over an hour, so please be patient). Future optimizations with C-Python will be considered.

Custom Function Prediction for Submissions

The built-in solver only supports consecutive random number submissions, but attackers often face situations where known information is discontinuous. If the specific generation process is known—such as knowing which parts of the data are discontinuous and how many random bits are skipped in-between—the internal state can still be recovered. The predictor provides the set_generator_func function to allow users to define a custom function for processing non-continuous submissions.

from randcracker import RandCracker
import time
# Initialize the random number generator
rd = random.Random()
rd.seed(time.time())
# Initialize the predictor
rc = RandCracker(detail = False)
# First, generate 624 16-bit numbers
data16 = [rd.getrandbits(16) for _ in range(624)]
# Then drop one 16-bit random number
drop = rd.getrandbits(16)
# Then generate 624*2 8-bit numbers
data8 = [rd.getrandbits(8) for _ in range(624*2)]
for num in data16:
    # Submit 624 16-bit numbers
    rc.submit(num, 16)
for num in data8:
    # Submit 624*2 8-bit numbers
    rc.submit(num, 8)

# Define a custom function that takes a Random class as parameter. 
# The internal logic of the function must match the actual generation process, though the values can differ.
# In this example, first submit 624 16-bit numbers, then drop one 16-bit number,
# and finally submit 1248 8-bit numbers.
def getRows(rnd):
    rows = []
    for _ in range(624):
        # Note that list(map(int, (bin() )) is necessary,
        # and zfill must correspond with the specific bit-length,
        # i.e., generating 16 bits should use zfill with 16.
        rows += list(map(int, (bin(rnd.getrandbits(16))[2:].zfill(16)))) 
    drop = rnd.getrandbits(16)
    for _ in range(624*2):
        rows += list(map(int, (bin(rnd.getrandbits(8))[2:].zfill(8)))) 
    # Finally, return a list whose length is the total number of submitted bits, with each element being 0 or 1.
    return rows
# Pass the custom function to set_generator_func
rc.set_generator_func(getRows)
# Check if the solution is obtainable and automatically solve
rc.check()
print(f"next random number is {rd.getrandbits(16)}")
print(f"predict next random number is {rc.rnd.getrandbits(16)}")

Moving Your Generator

The predictor also provides an offset function so you can freely move your random number generator. However, note that the offset is calculated based on submissions of random numbers that are 32 bits or less. If you generate a 64-bit random number, you need to apply the offset function twice to get the same result.

# Assuming rc has successfully predicted the state
number = rc.getrandbits(32)
# Use offset(-1) to roll back to the previous prediction
rc.offset(-1)
print(f"random number is {number}")
print(f"after offset, random number is {rc.rnd.getrandbits(32)}")

Retaining the Original Generator

Sometimes, we may want the generator’s next random number to match exactly the first number we submitted, effectively preserving the original state. You can certainly achieve this goal using the offset method. However, if you have used the set_generator_func method with a complex function, the program might take significantly longer to restore the random number generator to its current state. This not only adds extra waiting time but also increases complexity.

To address this, the solve method provides an offset parameter (default is False). By setting offset = True, you can retrieve the original generator directly.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyrandcracker-0.3.1.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyrandcracker-0.3.1-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

Details for the file pyrandcracker-0.3.1.tar.gz.

File metadata

  • Download URL: pyrandcracker-0.3.1.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.10

File hashes

Hashes for pyrandcracker-0.3.1.tar.gz
Algorithm Hash digest
SHA256 84b7c1d7e2c7521aeaece593f6768ca48ac171d89f372a588ce272edc57a6b21
MD5 57d7e84d10063e14de022e77b111acf2
BLAKE2b-256 4f2059ae7958216fd8f97c4701e7769d20e4ed7cd8d222c0d3ccbf24c5925114

See more details on using hashes here.

File details

Details for the file pyrandcracker-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: pyrandcracker-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 9.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.10

File hashes

Hashes for pyrandcracker-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 17cf22ec3fed8452ab5841b06c60a42b6536ec454a49327ecb0e5e9abd787d83
MD5 2334a9e5e2385a8c2a963444f66dab2e
BLAKE2b-256 0999161e75cf3368496e306d4631b476bf05b29b3a6f3fc2982245867f278ea8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page