Skip to main content

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:

python
# 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:

  1. Users often reuse passwords across multiple sites
  2. Attackers can immediately log in as any user
  3. 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:

  1. One-way transformation: You cannot derive the original password from the hash
  2. Deterministic: The same input always produces the same output hash
  3. 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:

  1. Password Hashers: Algorithms that convert passwords to hashes
  2. Salt: Random data added to passwords before hashing to prevent rainbow table attacks
  3. 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:

python
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:

  1. The password is validated against password validators
  2. The password is hashed using the first hasher in PASSWORD_HASHERS
  3. The hashed password is stored in the database

When a user logs in:

  1. Django takes the submitted password and the stored hash
  2. It determines which algorithm was used from the hash format
  3. It hashes the submitted password with the same algorithm and settings
  4. It compares the newly generated hash with the stored hash
  5. If they match, authentication succeeds

Configuring Password Hashers in Django

To use Argon2, first install the dependency:

bash
pip install django[argon2]

Then configure your settings.py:

python
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:

bash
pip install django[bcrypt]

Then update your settings.py:

python
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:

python
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:

python
# 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:

html
<!-- 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

  1. Never store passwords in plain text
  2. Never implement your own password hashing algorithm
  3. Don't access or set user.password directly - use set_password() instead:
python
# 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()
  1. 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

  1. Use Argon2 if possible - It offers the best security against modern attacks
  2. Keep Django updated - Security patches often involve password hashing improvements
  3. Implement strong password validators - Encourage or require complex passwords
  4. Consider Two-Factor Authentication - For additional security beyond passwords
  5. 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

Exercises

  1. Configure your Django project to use Argon2 as the primary password hasher
  2. Create a custom password validator that requires at least one special character
  3. Build a password strength meter that gives users feedback as they type
  4. Create a management command to check if any passwords in your system are still using outdated hash algorithms
  5. 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! :)