Northern_Explorer
Northern_Explorer

Reputation: 151

How to get numbers from /dev/random using Python?

I'm trying to write a python script to test the randomness of /dev/random, but I can't get it to give me any number. My code looks like this

with open("/dev/random", 'rb') as file:
     print f.read(10)

which I believe is supposed to print out 10 bytes from /dev/random, but instead of numbers, it prints out weird characters (non-standard letters and no numbers). Any idea what I'm doing wrong?

Upvotes: 12

Views: 15975

Answers (4)

Jon Clements
Jon Clements

Reputation: 142206

Python has a builtin function for this (which will also use the appropriate method on other OS's as well)...

import os
print os.urandom(10)
# '\xf1\x11xJOl\xab\xcc\xf0\xfd'

From the docs at http://docs.python.org/2/library/os.html#os.urandom

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, NotImplementedError will be raised.

If you then wanted those bytes to be a number, you can do so by converting as such:

>>> rand = os.urandom(10)
>>> int(binascii.hexlify(rand), 16)
1138412584848598544216317L

Or using Python 2:

>>> int(rand.encode('hex'), 16)
1138412584848598544216317L

Although, /dev/random and /dev/urandom are slightly different, so you can use your existing .read() op and just do the int conversion if the difference is significant to you.

Upvotes: 20

Joachim Wagner
Joachim Wagner

Reputation: 1003

In Python 3.2 and higher, the following is shorter and probably faster than the solutions in the older answers:

with open("/dev/random", 'rb') as f:
    print(int.from_bytes(f.read(10), 'big'))

This prints a single 80-bit number (range 0 to 2^80-1 inclusive).

Upvotes: 1

Josh Buell
Josh Buell

Reputation: 606

It's printing random characters, so just convert them to ints using the ord() function. Something like:

with open("/dev/random", 'rb') as file: print [ord(x) for x in file.read(10)]

This will print a list of 10 random ints from 0 to 255. (I got: [117, 211, 225, 24, 134, 145, 51, 234, 153, 89]).

Upvotes: 1

steveha
steveha

Reputation: 76725

You are getting 10 bytes. Python won't automatically turn them into numbers.

I recommend you grab the bytes in multiples of 4, then turn them into 32-bit unsigned integers, then scale them to whatever you need.

EDIT: the old code showed the idea but was poorly divided into functions. Here is the same basic idea but now conveniently packaged into functions.

import os
import struct

_random_source = open("/dev/random", "rb")

def random_bytes(len):
    return _random_source.read(len)

def unpack_uint32(bytes):
    tup = struct.unpack("I", bytes)
    return tup[0]

UINT32_MAX = 0xffffffff
def randint(low, high):
    """
    Return a random integer in the range [low, high], including
    both endpoints.
    """
    n = (high - low) + 1
    assert n >= 1
    scale_factor = n / float(UINT32_MAX + 1)
    random_uint32 = unpack_uint32(random_bytes(4))
    result = int(scale_factor * random_uint32) + low
    return result

def randint_gen(low, high, count):
    """
    Generator that yields random integers in the range [low, high],
    including both endpoints.
    """
    n = (high - low) + 1
    assert n >= 1
    scale_factor = n / float(UINT32_MAX + 1)
    for _ in range(count):
        random_uint32 = unpack_uint32(random_bytes(4))
        result = int(scale_factor * random_uint32) + low
        yield result

if __name__ == "__main__":
    # roll 3 dice individually with randint()
    result = [randint(1, 6) for _ in range(3)]
    print(result)

    # roll 3 dice more efficiently with randint_gen()
    print(list(randint_gen(1, 6, 3)))

Upvotes: 9

Related Questions