Django Password Storage
Introduction
When building web applications, one of the most critical security aspects is how you handle user passwords. Poorly implemented password storage can lead to devastating consequences if your database is compromised. Django, as a security-focused web framework, provides robust mechanisms for securely storing user credentials.
In this tutorial, we'll explore:
- Why plain text password storage is dangerous
- How Django hashes passwords
- The password hashers Django offers
- How to customize Django's password hashing
- Best practices for password management in Django applications
Why Password Security Matters
Imagine storing user passwords as plain text in your database:
# NEVER DO THIS!
user.password = "myplainpassword123" # Extremely insecure
user.save()
If someone gains access to your database, they immediately have everyone's passwords. This is disastrous because:
- Users often reuse passwords across multiple sites
- Attackers can immediately log in as any user
- Your application's credibility will be destroyed
How Django Stores Passwords
Django never stores passwords in plain text. Instead, it uses a technique called password hashing.
What is Password Hashing?
Password hashing converts a password into a fixed-length string of characters through a one-way mathematical function. Key properties include:
- One-way transformation: You cannot derive the original password from the hash
- Deterministic: The same input always produces the same output hash
- Unique: Different inputs should produce different output hashes (minimizing "collisions")
Here's a simplified illustration:
Password: "mypassword123"
↓ (Hashing algorithm)
Hash: "$pbkdf2_sha256$390000$hIiUQLiVgOeBDSaMBgnNKo$..."
Django's Password Hashing System
Django uses a sophisticated password hashing system with the following components:
- Password Hashers: Algorithms that convert passwords to hashes
- Salt: Random data added to passwords before hashing to prevent rainbow table attacks
- Iterations: Multiple rounds of hashing to slow down brute force attacks
The resulting hash format looks like:
algorithm$iterations$salt$hash
Django's Available Password Hashers
Django provides several built-in password hashers, listed in order of preference:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.ScryptPasswordHasher',
]
Let's explore the main ones:
1. PBKDF2 (Default)
PBKDF2 (Password-Based Key Derivation Function 2) is Django's default hasher. It applies a pseudorandom function multiple times to the password.
Pros:
- Widely accepted and well-researched
- Adjustable computational cost (iterations)
- Built into Django without additional dependencies
Cons:
- Not as memory-intensive as newer algorithms
2. Argon2
Argon2 is the winner of the 2015 Password Hashing Competition and is particularly resistant to GPU-based attacks.
Pros:
- Highly secure against various attacks
- Configurable memory, time, and thread parameters
- Modern algorithm designed specifically for password hashing
Cons:
- Requires additional dependency installation
3. BCrypt
BCrypt is a password hashing function designed to be slow and resource-intensive.
Pros:
- Strong against brute-force attacks
- Adaptive - can be made slower as computers get faster
Cons:
- Requires additional dependency installation
- Limited to 72 bytes of password data
Password Validation in Action
When a user registers or sets a password, Django follows this process:
- The password is validated against password validators
- The password is hashed using the first hasher in
PASSWORD_HASHERS
- The hashed password is stored in the database
When a user logs in:
- Django takes the submitted password and the stored hash
- It determines which algorithm was used from the hash format
- It hashes the submitted password with the same algorithm and settings
- It compares the newly generated hash with the stored hash
- If they match, authentication succeeds
Configuring Password Hashers in Django
Using Argon2 (Recommended)
To use Argon2, first install the dependency:
pip install django[argon2]
Then configure your settings.py
:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
Using BCrypt
To use BCrypt:
pip install django[bcrypt]
Then update your settings.py
:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
]
Increasing Security with Password Validators
Django also provides password validators to enforce stronger passwords:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 10,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
Password Upgrade System
One of Django's clever security features is its automatic password upgrading. If a user authenticates successfully and their password was hashed with an outdated algorithm, Django automatically rehashes it with the preferred algorithm.
For example, if a user's password was hashed with PBKDF2 but you've configured Argon2 as your preferred algorithm, the next time they log in successfully, their password will be rehashed using Argon2.
This feature makes it easy to upgrade your hashing algorithms without requiring users to reset their passwords.
Real-World Example: User Registration and Authentication
Let's implement a simple user registration and login system:
# views.py
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
def register(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
# Django handles the password hashing automatically
user = User.objects.create_user(username=username, password=password)
user.save()
return redirect('login')
return render(request, 'register.html')
def user_login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
# Django handles password verification against the stored hash
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('home')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
return render(request, 'login.html')
The registration form template:
<!-- register.html -->
<form method="post">
{% csrf_token %}
<label for="username">Username:</label>
<input type="text" name="username" required>
<label for="password">Password:</label>
<input type="password" name="password" required>
<button type="submit">Register</button>
</form>
Common Pitfalls to Avoid
- Never store passwords in plain text
- Never implement your own password hashing algorithm
- Don't access or set
user.password
directly - useset_password()
instead:
# Correct way to change a user's password
user = User.objects.get(username='example')
user.set_password('new_password')
user.save()
# NOT like this:
user.password = 'new_password' # WRONG! This will store in plain text!
user.save()
- Don't remove old hashers from your
PASSWORD_HASHERS
list, or users with passwords hashed with those algorithms won't be able to log in
Best Practices for Django Password Security
- Use Argon2 if possible - It offers the best security against modern attacks
- Keep Django updated - Security patches often involve password hashing improvements
- Implement strong password validators - Encourage or require complex passwords
- Consider Two-Factor Authentication - For additional security beyond passwords
- Set secure password reset policies - Don't send passwords via email
Summary
Django provides an excellent password security system that:
- Never stores plain text passwords
- Uses strong hashing algorithms with salts
- Automatically upgrades password hashes when better algorithms are available
- Handles all the complex cryptography for you safely
By using Django's built-in authentication system and following best practices, you can ensure your users' passwords remain secure even if your database is compromised.
Additional Resources
- Django Documentation on Password Management
- OWASP Password Storage Cheat Sheet
- Django Two-Factor Authentication
Exercises
- Configure your Django project to use Argon2 as the primary password hasher
- Create a custom password validator that requires at least one special character
- Build a password strength meter that gives users feedback as they type
- Create a management command to check if any passwords in your system are still using outdated hash algorithms
- Implement a password expiry policy that requires users to change passwords every 90 days
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)