A Python port of the Invisible Internet Project (I2P)
at main 61 lines 1.8 kB view raw
1"""RandomSource — cryptographically secure random number generator. 2 3Ported from net.i2p.util.RandomSource. 4Wraps Python's secrets/os.urandom which use the OS CSPRNG. 5""" 6 7import os 8import secrets 9import threading 10 11 12class RandomSource: 13 """Cryptographically secure random source. 14 15 In Java, this extends SecureRandom and adds Fortuna PRNG. 16 In Python, we delegate to the OS CSPRNG via secrets module. 17 """ 18 19 _instance: "RandomSource | None" = None 20 _lock = threading.Lock() 21 22 def __init__(self) -> None: 23 pass 24 25 @classmethod 26 def get_instance(cls) -> "RandomSource": 27 if cls._instance is None: 28 with cls._lock: 29 if cls._instance is None: 30 cls._instance = RandomSource() 31 return cls._instance 32 33 def next_int(self, n: int) -> int: 34 """Return random int in [0, n).""" 35 if n <= 0: 36 raise ValueError("bound must be positive") 37 return secrets.randbelow(n) 38 39 def signed_next_int(self) -> int: 40 """Return random int across full 32-bit signed range.""" 41 return int.from_bytes(os.urandom(4), "big", signed=True) 42 43 def next_long(self, n: int) -> int: 44 """Return random long in [0, n).""" 45 if n <= 0: 46 raise ValueError("bound must be positive") 47 return secrets.randbelow(n) 48 49 def next_bytes(self, length: int) -> bytes: 50 """Return `length` random bytes.""" 51 return os.urandom(length) 52 53 def next_bytes_into(self, buf: bytearray, offset: int = 0, length: int = -1) -> None: 54 """Fill buffer with random bytes.""" 55 if length < 0: 56 length = len(buf) - offset 57 rand = os.urandom(length) 58 buf[offset : offset + length] = rand 59 60 def next_boolean(self) -> bool: 61 return bool(secrets.randbelow(2))