Testing Password Security
Testing Password Security
Security testing must encompass both automated and manual approaches. Unit tests verify individual components work correctly, while integration tests ensure the complete authentication flow remains secure. Penetration testing provides real-world validation of security controls, identifying weaknesses that theoretical analysis might miss.
import pytest
import concurrent.futures
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class TestPasswordSecurity:
"""Comprehensive password security test suite"""
def test_timing_attack_resistance(self):
"""Ensure password verification is constant-time"""
ph = PasswordHasher()
correct_password = "CorrectPassword123!"
hash_value = ph.hash(correct_password)
# Measure timing for correct password
correct_times = []
for _ in range(100):
start = time.perf_counter()
try:
ph.verify(hash_value, correct_password)
except:
pass
correct_times.append(time.perf_counter() - start)
# Measure timing for incorrect passwords
incorrect_times = []
for i in range(100):
wrong_password = f"WrongPassword{i}"
start = time.perf_counter()
try:
ph.verify(hash_value, wrong_password)
except VerifyMismatchError:
pass
incorrect_times.append(time.perf_counter() - start)
# Statistical analysis
import statistics
correct_mean = statistics.mean(correct_times)
incorrect_mean = statistics.mean(incorrect_times)
# Times should be statistically similar
difference = abs(correct_mean - incorrect_mean)
assert difference < 0.001, f"Timing difference detected: {difference}"
def test_concurrent_hashing_safety(self):
"""Ensure thread safety under concurrent load"""
ph = PasswordHasher()
passwords = [f"Password{i}" for i in range(100)]
def hash_password(pwd):
return ph.hash(pwd)
# Concurrent hashing
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(hash_password, pwd) for pwd in passwords]
hashes = [f.result() for f in futures]
# Verify all hashes are unique and valid
assert len(set(hashes)) == len(passwords)
for pwd, hash_val in zip(passwords, hashes):
assert ph.verify(hash_val, pwd)
def test_dos_resistance(self):
"""Test resistance to denial-of-service attacks"""
validator = PasswordValidator(max_length=128)
# Test oversized password
huge_password = "A" * 10000
valid, msg = validator.validate(huge_password)
assert not valid
assert "too long" in msg.lower()
# Test null byte injection
null_password = "Password\x00Injection"
valid, msg = validator.validate(null_password)
assert not valid
assert "invalid characters" in msg.lower()
def test_unicode_handling(self):
"""Test proper Unicode password handling"""
ph = PasswordHasher()
unicode_passwords = [
"Pāsswōrd123!", # Macrons
"Пароль123!", # Cyrillic
"密码123!", # Chinese
"🔐🔑💻", # Emoji
"Café_Führung", # Mixed scripts
]
for pwd in unicode_passwords:
# Should hash without error
hash_val = ph.hash(pwd)
# Should verify correctly
assert ph.verify(hash_val, pwd)
# Different normalization should fail
import unicodedata
normalized = unicodedata.normalize('NFD', pwd)
if normalized != pwd:
with pytest.raises(VerifyMismatchError):
ph.verify(hash_val, normalized)