CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG) - Python
Overview
Use of Cryptographically Weak PRNG in Python applications occurs when developers use non-cryptographic random number generators like random module for security-sensitive operations. The random module uses the Mersenne Twister algorithm, which is predictable and unsuitable for cryptographic purposes. Attackers can exploit this weakness to predict session tokens, API keys, passwords, or other sensitive values.
Primary Defence: Use secrets module (Python 3.6+) for all security-sensitive random value generation including tokens, passwords, and security keys.
Common Vulnerable Patterns
Using random for Session Token Generation
import random
import string
from flask import Flask, session, request, jsonify
app = Flask(__name__)
app.secret_key = 'dev_secret_key'
def generate_session_token():
"""VULNERABLE: Uses predictable random for session tokens"""
# Mersenne Twister is predictable after observing enough outputs
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(32))
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
# Authenticate user (simplified)
if authenticate_user(username, password):
# VULNERABLE - Weak session token
session_token = generate_session_token()
session['token'] = session_token
session['username'] = username
return jsonify({
'status': 'success',
'session_token': session_token
})
return jsonify({'error': 'Invalid credentials'}), 401
def authenticate_user(username, password):
# Placeholder
return True
Why this is vulnerable:
random.choice()uses Mersenne Twister (predictable)- Attacker can predict future tokens after observing ~624 outputs
- Session hijacking becomes possible
- PRNG state can be recovered from outputs
Weak Password Reset Token
import random
import hashlib
from datetime import datetime, timedelta
from django.core.mail import send_mail
from django.http import JsonResponse
from django.views import View
class PasswordResetView(View):
def post(self, request):
email = request.POST.get('email')
# VULNERABLE - Weak reset token generation
reset_token = self.generate_reset_token()
# Store token in database
PasswordResetToken.objects.create(
email=email,
token=reset_token,
expires_at=datetime.now() + timedelta(hours=1)
)
# Send email
send_mail(
'Password Reset',
f'Reset token: {reset_token}',
'noreply@example.com',
[email]
)
return JsonResponse({'status': 'sent'})
def generate_reset_token(self):
"""VULNERABLE: Weak token generation"""
# Using time and random is predictable
timestamp = str(datetime.now().timestamp())
random_part = str(random.randint(100000, 999999))
# Even hashing doesn't fix the weak randomness
token = hashlib.sha256(
(timestamp + random_part).encode()
).hexdigest()
return token
Why this is vulnerable:
random.randint()is predictable- Timestamp is guessable (clock synchronization)
- Hashing weak random doesn't improve security
- Attacker can brute-force or predict tokens
Weak API Key Generation
import random
import string
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class APIKeyRequest(BaseModel):
user_id: int
description: str
def generate_api_key():
"""VULNERABLE: Uses random for API key generation"""
# Weak: random module is not cryptographically secure
chars = string.ascii_uppercase + string.digits
prefix = 'sk_'
key = prefix + ''.join(random.choice(chars) for _ in range(32))
return key
@app.post("/api/keys")
async def create_api_key(request: APIKeyRequest):
# VULNERABLE - Weak API key
api_key = generate_api_key()
# Store in database
await db.api_keys.insert({
'user_id': request.user_id,
'key': api_key,
'description': request.description
})
return {
'api_key': api_key,
'warning': 'Store this key securely'
}
Why this is vulnerable:
- API keys generated with
random.choice()are predictable - Attacker can predict keys for other users
- Key space is effectively reduced
- Enables unauthorized API access
Predictable PRNG Seeding
import random
import time
import os
class VulnerableTokenGenerator:
def __init__(self):
# VULNERABLE - Seeding with predictable values
seed = int(time.time()) + os.getpid()
random.seed(seed)
def generate_token(self):
"""Generates predictable tokens due to weak seeding"""
return random.getrandbits(128)
def generate_password(self, length=12):
"""VULNERABLE: Weak password generation"""
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
return ''.join(random.choice(chars) for _ in range(length))
# Flask route with vulnerable token generator
from flask import Flask, request, jsonify
app = Flask(__name__)
token_gen = VulnerableTokenGenerator()
@app.route('/api/register', methods=['POST'])
def register():
username = request.json.get('username')
# VULNERABLE - Predictable temporary password
temp_password = token_gen.generate_password()
# Store user with temp password
user = create_user(username, temp_password)
return jsonify({
'username': username,
'temporary_password': temp_password,
'message': 'Please change your password'
})
def create_user(username, password):
# Placeholder
pass
Why this is vulnerable:
- Time-based seeding is predictable (synchronized clocks)
- PID is easily guessable
- Attacker can recreate PRNG state
- All subsequent random values become predictable
Weak CSRF Token Generation
import random
from flask import Flask, render_template, session, request
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.secret_key = 'secret_key'
def generate_csrf_token():
"""VULNERABLE: Weak CSRF token"""
# Using random instead of secrets
token = ''.join(
random.choice('0123456789abcdef') for _ in range(32)
)
return token
@app.route('/form')
def show_form():
# VULNERABLE - Weak CSRF token
csrf_token = generate_csrf_token()
session['csrf_token'] = csrf_token
return render_template('form.html', csrf_token=csrf_token)
@app.route('/submit', methods=['POST'])
def submit_form():
user_token = request.form.get('csrf_token')
session_token = session.get('csrf_token')
if user_token != session_token:
return 'CSRF token invalid', 403
# Process form
return 'Success'
Why this is vulnerable:
- CSRF tokens must be unpredictable
randomallows token prediction- Enables CSRF attacks
- Bypasses CSRF protection
Weak Salt Generation
import random
import hashlib
class VulnerablePasswordHasher:
def hash_password(self, password):
"""VULNERABLE: Weak salt generation"""
# WRONG: Using random for salt generation
salt = str(random.randint(1000000, 9999999))
# Even with salt, weak randomness compromises security
salted = salt + password
hashed = hashlib.sha256(salted.encode()).hexdigest()
return f"{salt}:{hashed}"
def verify_password(self, password, stored):
salt, stored_hash = stored.split(':')
salted = salt + password
computed_hash = hashlib.sha256(salted.encode()).hexdigest()
return computed_hash == stored_hash
# Django view with weak password hashing
from django.http import JsonResponse
from django.views import View
class RegisterView(View):
def post(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
hasher = VulnerablePasswordHasher()
# VULNERABLE - Weak salt means rainbow tables still work
password_hash = hasher.hash_password(password)
# Store in database
User.objects.create(
username=username,
password_hash=password_hash
)
return JsonResponse({'status': 'registered'})
Why this is vulnerable:
- Salt with only 7 digits (10 million possibilities)
- Predictable salt enables pre-computation attacks
- Defeats the purpose of salting
- Rainbow tables remain effective
Weak Initialization Vector (IV)
import random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
class VulnerableEncryption:
def __init__(self, key):
self.key = key
def encrypt(self, plaintext):
"""VULNERABLE: Weak IV generation"""
# WRONG: Using random for IV generation
iv = bytes([random.randint(0, 255) for _ in range(16)])
cipher = AES.new(self.key, AES.MODE_CBC, iv)
padded = pad(plaintext.encode(), AES.block_size)
ciphertext = cipher.encrypt(padded)
# Return IV + ciphertext
return base64.b64encode(iv + ciphertext).decode()
def decrypt(self, encrypted):
data = base64.b64decode(encrypted)
iv = data[:16]
ciphertext = data[16:]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(ciphertext), AES.block_size)
return decrypted.decode()
# FastAPI endpoint
from fastapi import FastAPI
app = FastAPI()
encryptor = VulnerableEncryption(b'sixteen_byte_key')
@app.post("/api/encrypt")
async def encrypt_data(data: str):
# VULNERABLE - Weak IV compromises encryption
encrypted = encryptor.encrypt(data)
return {"encrypted": encrypted}
Why this is vulnerable:
- IV must be unpredictable for CBC mode
- Weak IV allows plaintext recovery attacks
- Predictable IVs compromise confidentiality
- Enables chosen-plaintext attacks
Weak UUID Generation
import random
import uuid
def generate_weak_uuid():
"""VULNERABLE: Custom UUID generation with weak randomness"""
# WRONG: Building UUID with random
random_bytes = bytes([random.randint(0, 255) for _ in range(16)])
return uuid.UUID(bytes=random_bytes)
def generate_user_id():
"""VULNERABLE: Predictable user IDs"""
# Using random to generate user IDs
return f"user_{random.randint(100000, 999999)}"
# Django model with weak IDs
from django.db import models
class User(models.Model):
# VULNERABLE - Weak UUID as primary key
id = models.CharField(max_length=36, primary_key=True)
username = models.CharField(max_length=100)
@classmethod
def create_user(cls, username):
# VULNERABLE - Predictable IDs enable enumeration
user_id = str(generate_weak_uuid())
return cls.objects.create(id=user_id, username=username)
Why this is vulnerable:
- Predictable UUIDs enable user enumeration
- Attackers can guess valid user IDs
- Privacy violation
- Enables unauthorized access
Secure Patterns
Using secrets Module for Token Generation
import secrets
import string
from flask import Flask, session, request, jsonify
from datetime import datetime, timedelta
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
def generate_session_token(length=32):
"""Secure: Uses secrets module for cryptographically secure tokens"""
# secrets.token_urlsafe() generates URL-safe tokens
return secrets.token_urlsafe(length)
def generate_session_id():
"""Alternative: Using token_hex for hex tokens"""
return secrets.token_hex(32) # 32 bytes = 256 bits
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
if authenticate_user(username, password):
# SECURE - Cryptographically secure session token
session_token = generate_session_token()
# Store in database with expiration
SessionToken.objects.create(
token=session_token,
username=username,
created_at=datetime.now(),
expires_at=datetime.now() + timedelta(hours=24)
)
session['token'] = session_token
session['username'] = username
return jsonify({
'status': 'success',
'session_token': session_token
})
return jsonify({'error': 'Invalid credentials'}), 401
@app.route('/api/verify-session', methods=['POST'])
def verify_session():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
session_obj = SessionToken.objects.filter(
token=token,
expires_at__gt=datetime.now()
).first()
if not session_obj:
return jsonify({'error': 'Invalid or expired session'}), 401
return jsonify({'status': 'valid', 'username': session_obj.username})
def authenticate_user(username, password):
# Actual authentication logic
pass
Why this works:
secretsmodule designed for security: Usesos.urandom()internally, not predictable Mersenne Twister likerandommodule- OS cryptographic sources: Reads from
/dev/urandom(Unix) orCryptGenRandom(Windows) for true randomness - 256 bits prevents brute-force: 2^256 possible values makes attacks computationally impossible
- URL-safe Base64 encoding: Automatically replaces
+with-,/with_, removes=padding for web contexts - Suitable for all security tokens: Session tokens, API keys, password reset, CSRF tokens, any unpredictable identifier
Secure Password Reset Token
import secrets
from datetime import datetime, timedelta
from django.core.mail import send_mail
from django.http import JsonResponse
from django.views import View
from django.utils import timezone
class SecurePasswordResetView(View):
def post(self, request):
email = request.POST.get('email')
# SECURE - Cryptographically secure reset token
reset_token = secrets.token_urlsafe(32)
# Store token in database with expiration
PasswordResetToken.objects.create(
email=email,
token=reset_token,
expires_at=timezone.now() + timedelta(hours=1)
)
# Send email with reset link
reset_url = f"https://example.com/reset?token={reset_token}"
send_mail(
'Password Reset Request',
f'Click here to reset your password: {reset_url}',
'noreply@example.com',
[email],
fail_silently=False
)
return JsonResponse({'status': 'sent'})
def verify_and_reset(self, request):
token = request.POST.get('token')
new_password = request.POST.get('new_password')
# Verify token and expiration
reset_obj = PasswordResetToken.objects.filter(
token=token,
expires_at__gt=timezone.now(),
used=False
).first()
if not reset_obj:
return JsonResponse({'error': 'Invalid or expired token'}, status=400)
# Reset password
user = User.objects.get(email=reset_obj.email)
user.set_password(new_password)
user.save()
# Mark token as used
reset_obj.used = True
reset_obj.save()
return JsonResponse({'status': 'password_reset'})
Why this works:
- High entropy:
secrets.token_urlsafe(32)provides 256 bits entropy - attackers can't guess valid tokens even with email, timing info, or previous tokens - One-time use:
used=Truemarking prevents replay attacks where intercepted email exploited multiple times - Time-limited exposure: Short expiration (typically 1 hour) creates limited attack window - compromised email becomes worthless quickly
- Database enforcement: Query filters (
expires_at__gt=timezone.now(), used=False) automatically reject stale/consumed tokens during verification - Safer than temp passwords: Token doesn't grant access until user proves email ownership by clicking link and actively setting new password
Secure API Key Generation
import secrets
import hashlib
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from datetime import datetime
app = FastAPI()
class APIKeyRequest(BaseModel):
user_id: int
description: str
permissions: list[str]
class APIKeyService:
@staticmethod
def generate_api_key(prefix='sk_live_'):
"""Generate cryptographically secure API key"""
# Generate 32 bytes of random data
random_part = secrets.token_urlsafe(32)
# Add prefix for key identification
return f"{prefix}{random_part}"
@staticmethod
def hash_api_key(api_key: str) -> str:
"""Hash API key for secure storage"""
# Store hash, not plaintext
return hashlib.sha256(api_key.encode()).hexdigest()
@staticmethod
async def create_api_key(user_id: int, description: str, permissions: list):
"""Create and store API key"""
# Generate secure key
api_key = APIKeyService.generate_api_key()
# Hash for storage
key_hash = APIKeyService.hash_api_key(api_key)
# Store in database
await db.api_keys.insert({
'user_id': user_id,
'key_hash': key_hash, # Store hash, not plaintext
'description': description,
'permissions': permissions,
'created_at': datetime.utcnow(),
'last_used': None
})
# Return plaintext key only once
return api_key
@staticmethod
async def verify_api_key(api_key: str) -> dict:
"""Verify API key and return associated data"""
key_hash = APIKeyService.hash_api_key(api_key)
key_record = await db.api_keys.find_one({
'key_hash': key_hash,
'revoked': False
})
if not key_record:
return None
# Update last used timestamp
await db.api_keys.update({'_id': key_record['_id']}, {
'$set': {'last_used': datetime.utcnow()}
})
return key_record
@app.post("/api/keys")
async def create_api_key(request: APIKeyRequest):
"""Create new API key"""
api_key = await APIKeyService.create_api_key(
request.user_id,
request.description,
request.permissions
)
return {
'api_key': api_key,
'warning': 'Store this key securely. It will not be shown again.'
}
@app.get("/api/protected")
async def protected_endpoint(api_key: str = Depends(get_api_key_from_header)):
"""Protected endpoint requiring API key"""
key_record = await APIKeyService.verify_api_key(api_key)
if not key_record:
raise HTTPException(status_code=401, detail="Invalid API key")
return {"message": "Access granted", "user_id": key_record['user_id']}
def get_api_key_from_header(authorization: str = Header(None)):
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail="Missing API key")
return authorization.replace('Bearer ', '')
Why this works:
- High entropy:
secrets.token_urlsafe(48)provides 384 bits of entropy - globally unique and cryptographically unpredictable even with billions of keys - Hash storage: Stores SHA-256 hashes (not plaintext) via
hashlib.sha256()- if database compromised, attackers get irreversible hashes - One-way verification:
hash_api_key()allows authentication by hashing provided key and comparing to stored hash without storing original - One-time display: Returns plaintext key only during creation; enforces "copy now or lose forever" - no "show me my key again" functionality
- Conventional naming:
sk_live_prefix (Stripe-style) makes keys identifiable in logs and enables secret scanning tools to detect accidental commits
Secure CSRF Token with Django
from django.middleware.csrf import get_token
from django.views.decorators.csrf import csrf_protect
from django.http import JsonResponse
from django.shortcuts import render
@csrf_protect
def show_form(request):
"""Django automatically generates secure CSRF tokens"""
# Django uses secrets module internally for CSRF tokens
csrf_token = get_token(request)
return render(request, 'form.html', {
'csrf_token': csrf_token
})
@csrf_protect
def submit_form(request):
"""CSRF protection is automatic with decorator"""
if request.method == 'POST':
# Django validates CSRF token automatically
# Process form data
return JsonResponse({'status': 'success'})
return JsonResponse({'error': 'Method not allowed'}, status=405)
# For API endpoints with custom CSRF handling
import secrets
class CustomCSRFProtection:
@staticmethod
def generate_csrf_token():
"""Generate secure CSRF token"""
return secrets.token_urlsafe(32)
@staticmethod
def verify_csrf_token(token: str, stored_token: str) -> bool:
"""Timing-safe CSRF token verification"""
if not token or not stored_token:
return False
# Use secrets.compare_digest for timing-safe comparison
return secrets.compare_digest(token, stored_token)
Why this works:
- Framework integration: Django uses
secretsmodule internally for cryptographically strong CSRF tokens preventing Cross-Site Request Forgery - Automatic injection:
{% csrf_token %}template tag inserts hidden field with unique, unpredictable token for all state-changing requests (POST/PUT/DELETE) - Timing-safe validation: Middleware validates with
constant_time_compare()preventing timing attacks where attackers infer token characters via response time measurements - Session binding: Token bound to user's session (session storage or signed cookie); only readable by same-origin JavaScript prevents cross-site theft
- Built-in protection: Enabled by default in middleware stack;
@csrf_protectenforces on views,@csrf_exemptallows opt-out; auto-rotates after login preventing session fixation
Secure Salt and IV Generation
import secrets
import hashlib
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
class SecurePasswordHasher:
@staticmethod
def hash_password(password: str) -> str:
"""Hash password with secure salt"""
# Generate cryptographically secure salt
salt = secrets.token_bytes(32) # 256 bits
# Use PBKDF2 with secure salt
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
from cryptography.hazmat.primitives import hashes
kdf = PBKDF2(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=600_000
)
key = kdf.derive(password.encode())
# Encode salt and hash for storage
salt_b64 = base64.b64encode(salt).decode()
key_b64 = base64.b64encode(key).decode()
return f"pbkdf2_sha256$600000${salt_b64}${key_b64}"
@staticmethod
def verify_password(password: str, stored: str) -> bool:
"""Verify password against stored hash"""
parts = stored.split('$')
iterations = int(parts[1])
salt = base64.b64decode(parts[2])
stored_key = base64.b64decode(parts[3])
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
from cryptography.hazmat.primitives import hashes
kdf = PBKDF2(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=iterations
)
computed_key = kdf.derive(password.encode())
# Timing-safe comparison
return secrets.compare_digest(computed_key, stored_key)
class SecureEncryption:
def __init__(self, key: bytes):
self.key = key
@staticmethod
def generate_key():
"""Generate secure encryption key"""
return secrets.token_bytes(32) # 256 bits for AES-256
def encrypt(self, plaintext: str) -> str:
"""Encrypt with secure IV"""
# Generate cryptographically secure nonce/IV
nonce = secrets.token_bytes(12) # 96 bits for GCM
aesgcm = AESGCM(self.key)
ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
# Combine nonce and ciphertext
combined = base64.b64encode(nonce + ciphertext).decode()
return combined
def decrypt(self, encrypted: str) -> str:
"""Decrypt data"""
combined = base64.b64decode(encrypted)
nonce = combined[:12]
ciphertext = combined[12:]
aesgcm = AESGCM(self.key)
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
return plaintext.decode()
# Flask application using secure encryption
from flask import Flask, request, jsonify
app = Flask(__name__)
# Generate and store master key securely (environment variable)
MASTER_KEY = base64.b64decode(os.environ.get('MASTER_ENCRYPTION_KEY'))
encryptor = SecureEncryption(MASTER_KEY)
@app.route('/api/encrypt', methods=['POST'])
def encrypt_data():
data = request.json.get('data')
# SECURE - Uses secrets for IV generation
encrypted = encryptor.encrypt(data)
return jsonify({'encrypted': encrypted})
Why this works:
- Unique salts:
secrets.token_bytes(32)ensures 256-bit salt uniqueness per password - even identical passwords get different hashes, preventing rainbow tables and forcing per-hash cracking - Salt properties: Doesn't need secrecy (stored with hash) but requires uniqueness and randomness
- Nonce uniqueness critical: AES-GCM's
os.urandom(12)generates 96-bit nonce that must never repeat with same key - reuse catastrophically breaks confidentiality via XOR analysis - Timing-safe verification:
secrets.compare_digest()takes constant time regardless of match, preventing timing attacks during login - Authenticated encryption: AES-GCM generates 128-bit auth tag detecting tampering; pattern demonstrates KDF salting (Argon2/PBKDF2), unique nonces, timing-safe comparisons
Secure UUID Generation
import uuid
import secrets
def generate_secure_uuid():
"""Generate UUID4 (uses os.urandom internally)"""
return uuid.uuid4()
def generate_secure_id():
"""Alternative: Custom secure ID generation"""
return secrets.token_urlsafe(16)
# Django model with secure UUIDs
from django.db import models
import uuid
class User(models.Model):
# SECURE - UUID4 uses cryptographically secure random
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.username
class APIKey(models.Model):
# SECURE - Custom secure ID
key_id = models.CharField(max_length=32, primary_key=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
@classmethod
def create_api_key(cls, user):
key_id = secrets.token_urlsafe(16)
return cls.objects.create(key_id=key_id, user=user)
Why this works:
- Strong entropy: Uses
os.urandom()internally for 122 bits of randomness (6 bits reserved for version/variant) - 2^122 possible values prevents collisions and brute-force - Prevents enumeration: Unpredictability stops attackers from incrementing sequential IDs to discover resources (unlike auto-incrementing integers)
- Ideal use cases: Public REST API identifiers (
/api/users/{uuid}), database primary keys exposed externally, job/task IDs, file upload identifiers - Universal compatibility: Standardized format (8-4-4-4-12 hex) recognized by databases (PostgreSQL native type), APIs, programming languages
- Security trade-off: For session tokens/API secrets, prefer 256-bit
secrets.token_urlsafe(); UUIDs balance security, uniqueness, and interoperability
Secure Random for Security Operations
import secrets
import os
class SecureRandomService:
"""Service for all cryptographically secure random operations"""
@staticmethod
def generate_token(nbytes=32):
"""Generate random token (URL-safe base64)"""
return secrets.token_urlsafe(nbytes)
@staticmethod
def generate_hex_token(nbytes=32):
"""Generate hex token"""
return secrets.token_hex(nbytes)
@staticmethod
def generate_bytes(nbytes=32):
"""Generate random bytes"""
return secrets.token_bytes(nbytes)
@staticmethod
def generate_password(length=16, use_symbols=True):
"""Generate secure random password"""
import string
alphabet = string.ascii_letters + string.digits
if use_symbols:
alphabet += string.punctuation
# Use secrets.choice() instead of random.choice()
password = ''.join(secrets.choice(alphabet) for _ in range(length))
# Ensure password meets complexity requirements
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
if not (has_upper and has_lower and has_digit):
# Regenerate if doesn't meet requirements
return SecureRandomService.generate_password(length, use_symbols)
return password
@staticmethod
def generate_otp(length=6):
"""Generate numeric OTP"""
# Use secrets.randbelow() for random integers
otp = ''.join(str(secrets.randbelow(10)) for _ in range(length))
return otp
@staticmethod
def select_random_item(items):
"""Securely select random item from list"""
return secrets.choice(items)
# FastAPI application using secure random
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
random_service = SecureRandomService()
class PasswordResetRequest(BaseModel):
email: str
@app.post("/api/reset-password")
async def request_password_reset(request: PasswordResetRequest):
# SECURE - Generate OTP with secrets
otp = random_service.generate_otp(6)
# Store OTP with expiration
await db.otps.insert({
'email': request.email,
'otp': otp,
'created_at': datetime.utcnow(),
'expires_at': datetime.utcnow() + timedelta(minutes=10)
})
# Send OTP via email
send_otp_email(request.email, otp)
return {"message": "OTP sent to email"}
@app.post("/api/generate-temp-password")
async def generate_temp_password():
# SECURE - Generate temporary password with secrets
temp_password = random_service.generate_password(16, use_symbols=True)
return {
"temporary_password": temp_password,
"expires_in": "24 hours"
}
Why this works:
- Use-case optimized:
token_bytes(n)for raw bytes (keys, salts, IVs),token_hex(n)for human-readable hex (2n chars),token_urlsafe(n)for URL-safe Base64 (URLs, headers, cookies),randbelow(n)for integers [0, n) - Consistent security: All methods use
os.urandom()internally, ensuring cryptographic strength regardless of convenience function - Drop-in replacement:
SystemRandomclass usesos.urandom(), allowing code usingrandom.choice()orrandom.shuffle()to upgrade by changing random instance - Comprehensive API: Makes cryptographically secure randomness easy to use appropriately across different scenarios
Verification
After implementing the recommended secure patterns, verify the fix through multiple approaches:
- Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
- Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
- Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
- Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
- Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
- Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
- Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
- Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced