ORM

Djangoのパスワードハッシュ化入門|安全にパスワードを保存する方法

Django ORM

Djangoのパスワードハッシュ化入門
安全にパスワードを保存する方法

Djangoでパスワードをハッシュ化して安全に保存する方法を解説します。標準のUserモデルやmake_password関数の使い方を紹介します。

こんな人向けの記事です

  • Djangoでパスワードの安全な保存方法を学びたい人
  • パスワードのハッシュ化と検証の仕組みを理解したい人
  • カスタムUserモデルでパスワード管理をしたい人

Step 1パスワードハッシュ化とは

パスワードのハッシュ化とは、元のパスワードを不可逆な文字列に変換して保存することです。万が一データベースが漏洩しても、元のパスワードを復元できないようにします。

Python
# Djangoのハッシュ化の仕組み
# 元のパスワード: "mypassword123"
# ↓ ハッシュ化
# 保存される値: "pbkdf2_sha256$600000$salt$hash..."

# Djangoのデフォルトハッシュアルゴリズム(PBKDF2)
# - アルゴリズム名: pbkdf2_sha256
# - イテレーション回数: 600000(Django 5.x)
# - ソルト: ランダムに生成される文字列
# - ハッシュ値: SHA-256で計算された値

# なぜハッシュ化が必要か
# 1. データベース漏洩時にパスワードが読めない
# 2. 管理者もユーザーのパスワードを知れない
# 3. 同じパスワードでもソルトにより異なるハッシュ値になる

Step 2Djangoの標準Userモデル

Djangoの標準Userモデルは、パスワードのハッシュ化を自動的に行います。

Python
from django.contrib.auth.models import User

# ユーザー作成(パスワードは自動的にハッシュ化される)
user = User.objects.create_user(
    username="tanaka",
    email="tanaka@example.com",
    password="mypassword123"
)
print(user.password)
# pbkdf2_sha256$600000$xxxx$yyyy... (ハッシュ化済み)

# スーパーユーザー作成
admin = User.objects.create_superuser(
    username="admin",
    email="admin@example.com",
    password="adminpass123"
)
実行結果
pbkdf2_sha256$600000$aBcDeFgHiJkL$mNoPqRsTuVwXyZ1234567890abcdefghijklmnop==
create()ではなくcreate_user()を使う
User.objects.create(password="...")を使うとパスワードが平文で保存されてしまいます。必ずcreate_user()を使ってください。

Step 3make_passwordとcheck_password

Djangoにはパスワードのハッシュ化と検証のためのユーティリティ関数があります。

Python
from django.contrib.auth.hashers import make_password, check_password

# パスワードをハッシュ化
hashed = make_password("mypassword123")
print(hashed)
# pbkdf2_sha256$600000$xxxx$yyyy...

# パスワードの検証
is_valid = check_password("mypassword123", hashed)
print(f"正しいパスワード: {is_valid}")  # True

is_valid = check_password("wrongpassword", hashed)
print(f"間違ったパスワード: {is_valid}")  # False

# Userモデルでの検証
user = User.objects.get(username="tanaka")
print(user.check_password("mypassword123"))  # True
print(user.check_password("wrongpass"))       # False

# パスワードの変更
user.set_password("newpassword456")
user.save()
print(user.check_password("newpassword456"))  # True
実行結果
pbkdf2_sha256$600000$aBcDeFgH$iJkLmNoPqRsT...
正しいパスワード: True
間違ったパスワード: False
set_password()の後にsave()が必要
set_password()はインスタンスのpasswordフィールドを変更するだけで、データベースには保存しません。必ずsave()を呼んでください。

Step 4カスタムUserモデルでのパスワード管理

AbstractBaseUserを使ったカスタムUserモデルでも、パスワードハッシュ化の機能を利用できます。

Python
# models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("メールアドレスは必須です")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)  # ハッシュ化して保存
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        return self.create_user(email, password, **extra_fields)

class CustomUser(AbstractBaseUser):
    email = models.EmailField(unique=True)
    name = models.CharField(max_length=100)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = CustomUserManager()
    USERNAME_FIELD = "email"

    def __str__(self):
        return self.email
Python
# 使い方
user = CustomUser.objects.create_user(
    email="tanaka@example.com",
    password="securepass123",
    name="田中太郎"
)

# AbstractBaseUserが提供するメソッドがそのまま使える
print(user.check_password("securepass123"))  # True
user.set_password("newpass456")
user.save()

Step 5パスワードバリデーション

Djangoには標準のパスワードバリデーターが用意されています。

Python
# settings.py
AUTH_PASSWORD_VALIDATORS = [
    {   # 他のユーザー属性と似ていないか
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {   # 最小文字数
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
        "OPTIONS": {"min_length": 8},
    },
    {   # よく使われるパスワードでないか
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {   # 数字だけでないか
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]
Python
# バリデーションを手動で実行
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError

try:
    validate_password("123")  # 短すぎ、数字のみ
except ValidationError as e:
    for error in e.messages:
        print(f"エラー: {error}")

# ビューでのパスワード変更処理
from django.contrib.auth import update_session_auth_hash

def change_password(request):
    if request.method == "POST":
        old_pass = request.POST["old_password"]
        new_pass = request.POST["new_password"]

        if not request.user.check_password(old_pass):
            messages.error(request, "現在のパスワードが正しくありません")
            return redirect("change_password")

        try:
            validate_password(new_pass, request.user)
        except ValidationError as e:
            for error in e.messages:
                messages.error(request, error)
            return redirect("change_password")

        request.user.set_password(new_pass)
        request.user.save()
        update_session_auth_hash(request, request.user)  # セッション維持
        messages.success(request, "パスワードを変更しました")
        return redirect("profile")

まとめ

  • Djangoは標準でPBKDF2アルゴリズムによるパスワードハッシュ化を提供
  • create_user()でユーザー作成すると自動的にハッシュ化される
  • make_password()check_password()でハッシュ化と検証ができる
  • set_password()でパスワードを変更し、必ずsave()を呼ぶ
  • AUTH_PASSWORD_VALIDATORSでパスワードの強度を検証できる