Understanding Salt: Defeating Precomputation Attacks

Understanding Salt: Defeating Precomputation Attacks

Salt revolutionized password storage by adding random data to passwords before hashing, ensuring identical passwords produce different hash values. This simple addition defeats rainbow table attacks and prevents attackers from identifying users with identical passwords. However, salt implementation details matter significantly—predictable or reused salts undermine the security benefits.

Proper salt generation requires cryptographically secure random number generators, with salt length sufficient to ensure uniqueness. Modern standards recommend at least 16 bytes of random data for salts, though longer salts provide additional security margin. Salts must be stored alongside password hashes since they're required for password verification, but they don't need encryption since their security value comes from uniqueness, not secrecy.

# Example: Proper salt generation and usage
import os
import hashlib
import hmac
from base64 import b64encode, b64decode

class SecurePasswordStorage:
    def __init__(self):
        # Minimum salt length in bytes
        self.SALT_LENGTH = 32
        
    def generate_salt(self):
        """Generate cryptographically secure random salt"""
        return os.urandom(self.SALT_LENGTH)
    
    def hash_password_pbkdf2(self, password, salt=None, iterations=600000):
        """Hash password using PBKDF2-SHA256"""
        if salt is None:
            salt = self.generate_salt()
        
        # PBKDF2 with SHA-256
        dk = hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt,
            iterations,
            dklen=32  # 256-bit derived key
        )
        
        # Format: algorithm$iterations$salt$hash
        return f"pbkdf2_sha256${iterations}${b64encode(salt).decode()}${b64encode(dk).decode()}"
    
    def verify_password_pbkdf2(self, password, stored_hash):
        """Verify password against stored hash"""
        try:
            algorithm, iterations, salt_b64, hash_b64 = stored_hash.split('$')
            
            if algorithm != 'pbkdf2_sha256':
                return False
            
            salt = b64decode(salt_b64)
            stored_dk = b64decode(hash_b64)
            
            # Recompute hash with same parameters
            test_dk = hashlib.pbkdf2_hmac(
                'sha256',
                password.encode('utf-8'),
                salt,
                int(iterations),
                dklen=32
            )
            
            # Constant-time comparison to prevent timing attacks
            return hmac.compare_digest(stored_dk, test_dk)
            
        except Exception:
            return False
    
    def upgrade_hash_iterations(self, stored_hash, current_iterations=600000):
        """Check if password hash needs iteration upgrade"""
        try:
            _, iterations, _, _ = stored_hash.split('$')
            return int(iterations) < current_iterations
        except Exception:
            return True  # Upgrade on any parsing error