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)