2023-05-28 22:49:23 +03:00
|
|
|
import requests
|
2025-09-14 14:00:55 +03:00
|
|
|
import random
|
|
|
|
|
import secrets
|
|
|
|
|
import base64
|
|
|
|
|
import os
|
2023-05-28 22:49:23 +03:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from urllib.parse import urlencode
|
|
|
|
|
except ImportError:
|
|
|
|
|
from urllib import urlencode
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
from urllib3.exceptions import InsecureRequestWarning
|
|
|
|
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|
|
|
|
|
2023-06-02 00:58:58 +03:00
|
|
|
URL = 'https://qrng.randonautica.com/api/json/'
|
2023-05-28 22:49:23 +03:00
|
|
|
typeS = {
|
2025-09-14 14:00:55 +03:00
|
|
|
'int32' : 'randint32',
|
|
|
|
|
'hex16' : 'randhex',
|
|
|
|
|
'uniform': 'randuniform',
|
2023-05-28 22:49:23 +03:00
|
|
|
'normal' : 'randnormal',
|
|
|
|
|
'base64' : 'randbase64'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEVICE_ID = 'QWR70154'
|
|
|
|
|
|
2025-09-14 14:00:55 +03:00
|
|
|
def get(length=10, type='hex16', timeout=1.5):
|
2023-06-03 10:33:45 +03:00
|
|
|
"""
|
2025-09-14 14:00:55 +03:00
|
|
|
Fetch data from the Randonautica Quantum Random Numbers JSON API.
|
|
|
|
|
Falls back to local randomness if the API is unavailable.
|
2023-06-03 10:33:45 +03:00
|
|
|
|
2025-09-14 14:00:55 +03:00
|
|
|
length (int): number of values/bytes to get
|
|
|
|
|
type (str): one of {'hex16','int32','uniform','normal','base64'}
|
|
|
|
|
timeout (float): HTTP timeout in seconds
|
2023-06-03 10:33:45 +03:00
|
|
|
"""
|
2025-09-14 14:00:55 +03:00
|
|
|
if type not in typeS:
|
|
|
|
|
raise Exception("type must be one of %s" % list(typeS.keys()))
|
|
|
|
|
|
|
|
|
|
# Try QRNG first
|
2023-05-28 22:49:23 +03:00
|
|
|
url = URL + typeS[type] + '?' + urlencode({
|
|
|
|
|
'device_id': DEVICE_ID,
|
|
|
|
|
'length': length,
|
|
|
|
|
})
|
2025-09-14 14:00:55 +03:00
|
|
|
try:
|
|
|
|
|
data = __get_json(url, timeout=timeout)
|
|
|
|
|
# Validate expected shape minimally
|
|
|
|
|
if isinstance(data, dict) and "data" in data and isinstance(data["data"], list):
|
|
|
|
|
return data
|
|
|
|
|
# If shape unexpected, fall back
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Fallback path: generate locally with best available primitives
|
|
|
|
|
return _fallback_response(type=type, length=length)
|
|
|
|
|
|
|
|
|
|
def __get_json(url, timeout=1.5):
|
|
|
|
|
resp = requests.get(url, verify=False, timeout=timeout)
|
|
|
|
|
resp.raise_for_status()
|
|
|
|
|
return resp.json()
|
|
|
|
|
|
|
|
|
|
def _fallback_response(type: str, length: int):
|
|
|
|
|
"""
|
|
|
|
|
Produce a response object that mirrors the QRNG API:
|
|
|
|
|
{
|
|
|
|
|
"type": <type>,
|
|
|
|
|
"length": <length>,
|
|
|
|
|
"data": [...],
|
|
|
|
|
"success": true,
|
|
|
|
|
"source": "fallback"
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
if type == 'uniform':
|
|
|
|
|
# Uniform floats in [0.0, 1.0)
|
|
|
|
|
data = [random.random() for _ in range(length)]
|
|
|
|
|
|
|
|
|
|
elif type == 'normal':
|
|
|
|
|
# Standard normal N(0,1)
|
|
|
|
|
data = [random.gauss(0.0, 1.0) for _ in range(length)]
|
|
|
|
|
|
|
|
|
|
elif type == 'int32':
|
|
|
|
|
# 32-bit signed ints (match typical randint32 range: [-2^31, 2^31-1])
|
|
|
|
|
# secrets is preferable to random for stronger entropy.
|
|
|
|
|
data = []
|
|
|
|
|
for _ in range(length):
|
|
|
|
|
u = secrets.randbits(32)
|
|
|
|
|
# Convert to signed 32-bit
|
|
|
|
|
if u & (1 << 31):
|
|
|
|
|
u = u - (1 << 32)
|
|
|
|
|
data.append(u)
|
|
|
|
|
|
|
|
|
|
elif type == 'hex16':
|
|
|
|
|
# Hex string of length*2 bytes? The API name suggests 16-bit hex bytes.
|
|
|
|
|
# Historically this endpoint returns hex bytes; emulate by returning a single hex string
|
|
|
|
|
# of 2*length characters (each byte -> 2 hex chars). If API returns list, adapt as needed.
|
|
|
|
|
# Here we return a list of hex byte strings to match "data": [...]
|
|
|
|
|
data = [secrets.token_hex(1) for _ in range(length)]
|
|
|
|
|
|
|
|
|
|
elif type == 'base64':
|
|
|
|
|
# Return base64-encoded random bytes; match as a list of base64 strings
|
|
|
|
|
data = []
|
|
|
|
|
for _ in range(length):
|
|
|
|
|
b = os.urandom(16) # 16 bytes per element; adjust if your API uses a different size
|
|
|
|
|
data.append(base64.b64encode(b).decode('ascii'))
|
2023-05-28 22:49:23 +03:00
|
|
|
|
2025-09-14 14:00:55 +03:00
|
|
|
else:
|
|
|
|
|
# Should never hit due to earlier check
|
|
|
|
|
data = []
|
2023-05-28 22:49:23 +03:00
|
|
|
|
2025-09-14 14:00:55 +03:00
|
|
|
return {
|
|
|
|
|
"type": type,
|
|
|
|
|
"length": length,
|
|
|
|
|
"data": data,
|
|
|
|
|
"success": True,
|
|
|
|
|
"source": "pseudo"
|
|
|
|
|
}
|