The Purpose and Properties of Salt
The Purpose and Properties of Salt
Salt serves multiple security purposes beyond defeating rainbow tables. By ensuring unique hashes for identical passwords, salts prevent attackers from identifying users with the same password, eliminate the ability to determine if a user has the same password across different systems, and force attackers to crack each password individually rather than in bulk. These properties transform password cracking from a wholesale operation to a retail one, dramatically increasing attack costs.
Effective salts must be cryptographically random, making them unpredictable to attackers. Using predictable values like usernames, email addresses, or timestamps as salts provides minimal security improvement. The randomness ensures that even if an attacker knows the salt generation algorithm, they cannot predict specific salt values without access to the random source.
import secrets
import uuid
import hashlib
from datetime import datetime
def demonstrate_salt_quality():
"""Compare different salt generation methods"""
print("Salt Generation Methods - Security Analysis\n")
# BAD: Predictable salts
username = "alice"
email = "[email protected]"
bad_salt_1 = username
bad_salt_2 = email.split('@')[0]
bad_salt_3 = str(datetime.now().timestamp())
bad_salt_4 = str(uuid.uuid4())[:8] # Too short!
print("❌ BAD Salt Examples:")
print(f"Username as salt: '{bad_salt_1}' - Predictable!")
print(f"Email prefix: '{bad_salt_2}' - Predictable!")
print(f"Timestamp: '{bad_salt_3}' - Predictable and reused!")
print(f"Short UUID: '{bad_salt_4}' - Too short (only 8 chars)!")
# GOOD: Cryptographically secure random salts
print("\n✅ GOOD Salt Examples:")
# Method 1: Using secrets (Python 3.6+)
good_salt_1 = secrets.token_bytes(16)
print(f"secrets.token_bytes(16): {good_salt_1.hex()}")
# Method 2: Using os.urandom
good_salt_2 = os.urandom(16)
print(f"os.urandom(16): {good_salt_2.hex()}")
# Method 3: Using secrets.token_hex
good_salt_3 = secrets.token_hex(16) # 32 hex characters = 16 bytes
print(f"secrets.token_hex(16): {good_salt_3}")
# Demonstrate salt length importance
print("\n\nSalt Length Analysis:")
for length in [4, 8, 16, 32]:
salt_space = 256 ** length
print(f"{length:2d} bytes: {salt_space:.2e} possible salts")
# Time to generate rainbow table for each salt
# Assuming 1 billion hashes/second
hashes_per_second = 1_000_000_000
passwords_to_try = 1_000_000 # 1 million common passwords
time_per_table = passwords_to_try / hashes_per_second
total_time = salt_space * time_per_table
years = total_time / (365 * 24 * 3600)
print(f" Time to generate all tables: {years:.2e} years")
demonstrate_salt_quality()
Salt length significantly impacts security. While 8 bytes provides 2^64 possible salts, making rainbow tables impractical, modern standards recommend 16 bytes (128 bits) or more. This length ensures security even against well-resourced attackers with massive storage capabilities. The salt must be stored alongside the password hash, as it's required for verification, but this doesn't compromise security since salts aren't secret—they're simply unique.