Gestion des mots de passe dans Django

La gestion des mots de passe est quelque chose qui ne devrait généralement pas être réinventé sans raison, et Django s’efforce de fournir un ensemble d’outils sécurisés et souples pour gérer les mots de passe des utilisateurs. Ce document présente la façon dont Django stocke les mots de passe, la configuration possible du hachage avant stockage et certains utilitaires pour manipuler les empreintes de mots de passe.

Voir aussi

Même si les utilisateurs utilisent des mots de passe forts, des attaquants peuvent arriver à écouter leurs connexions. Utilisez HTTPS afin d’éviter d’envoyer des mots de passe (ou d’autres données sensibles) sur des connexions HTTP simples car celles-ci sont vulnérables à l’interception de mots de passe.

Stockage des mots de passe par Django

Django fournit un système de stockage de mots de passe souple et emploie PBKDF2 par défaut.

L’attribut password d’un objet User est une chaîne dans ce format :

<algorithm>$<iterations>$<salt>$<hash>

Il s’agit des composants utilisés pour stocker le mot de passe d’un utilisateur, séparés par le caractère dollar et formés de : l’algorithme de hachage, le nombre d’itérations de l’algorithme (facteur travail), le sel aléatoire et l’empreinte de mot de passe résultante. L’algorithme correspond à l’un des algorithmes de stockage de mot de passe ou de hachage à sens unique que Django peut utiliser ; voir ci-dessous. Les itérations indiquent le nombre de fois que l’algorithme traite l’empreinte numérique. Le sel est la graine aléatoire utilisée et l’empreinte est le résultat de la fonction à sens unique.

Par défaut, Django utilise l’algorithme PBKDF2 avec une fonction de hachage SHA256, un mécanisme d’étirement de mot de passe recommandé par le NIST. Cela devrait suffire pour la plupart des utilisateurs : c’est un algorithme bien sécurisé et exigeant d’énormes quantités de puissance de calcul pour être cassé.

Cependant, en fonction de vos exigences, vous pouvez choisir un algorithme différent ou même utiliser un algorithme qui vous est propre pour répondre à votre situation spécifique en matière de sécurité. Encore une fois, la plupart des utilisateurs n’auront pas à le faire et si vous n’êtes pas sûr, c’est que vous n’en avez probablement pas besoin. Si vous en avez besoin, continuez cette lecture…

Django choisit l’algorithme à utiliser en consultant le réglage PASSWORD_HASHERS. Il s’agit d’une liste de classes d’algorithme de hachage que la présente installation de Django prend en charge.

Pour le stockage des mots de passe, Django utilise le premier algorithme de PASSWORD_HASHERS. Pour stocker les nouveaux mots de passe avec un algorithme différent, placez votre algorithme préféré en première position dans PASSWORD_HASHERS.

Pour la vérification des mots de passe, Django recherche dans la liste l’algorithme de hachage qui correspond au nom d’algorithme dans le mot de passe stocké. Dans le cas où un nom d’algorithme stocké dans un mot de passe n’est pas trouvé dans PASSWORD_HASHERS, sa vérification génère une exception ValueError.

La valeur par défaut de PASSWORD_HASHERS est :

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",
]

Cela signifie que Django utilisera PBKDF2 pour stocker tous les mots de passe, mais qu’il acceptera de vérifier des mots de passe stockés avec les algorithmes PBKDF2SHA1, argon2 et bcrypt.

Les sections qui suivent présentent quelques manières fréquentes que les utilisateurs avancés peuvent utiliser pour modifier ce réglage.

Utilisation de Argon2 avec Django

Argon2 est le gagnant du concours 2015 Password Hashing Competition, une compétition ouverte organisée par la communauté pour sélectionner un algorithme de hachage de nouvelle génération. Il est conçu pour ne pas être plus facile à calculer sur du matériel dédié qu’il ne l’est sur un processeur ordinaire. La variante par défaut du hacheur de mot de passe Argon2 et Argon2id.

Argon2 n’est pas l’algorithme utilisé par défaut dans Django car il nécessite une bibliothèque tierce. Les experts de Password Hashing Competition recommandent cependant l’utilisation immédiate de Argon2 plutôt que les autres algorithmes pris en charge par Django.

Pour utiliser Argon2id comme algorithme de stockage par défaut, faites ceci :

  1. Installez le paquet argon2-cffi. Cela peut se faire en lançant python -m pip install django[argon2], ce qui est équivalent à python -m pip install argon2-cffi (en installant aussi toute version de paquet exigée par le fichier setup.cfg de Django).

  2. Modifiez PASSWORD_HASHERS pour que Argon2PasswordHasher apparaisse en premier. Voici ce qui devrait apparaître dans votre fichier de réglages :

    PASSWORD_HASHERS = [
        "django.contrib.auth.hashers.Argon2PasswordHasher",
        "django.contrib.auth.hashers.PBKDF2PasswordHasher",
        "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
        "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
        "django.contrib.auth.hashers.ScryptPasswordHasher",
    ]
    

    Conservez ou ajoutez d’autres éléments dans cette liste si vous avez besoin que Django puisse mettre à jour des mots de passe.

Utilisation de bcrypt avec Django

Bcrypt est un algorithme de stockage de mot de passe répandu qui est spécifiquement conçu comme stockage de mots de passe à long terme. Ce n’est pas l’algorithme par défaut de Django car il dépend de l’installation de bibliothèques tierces. Mais comme il y a beaucoup d’utilisateurs potentiels, Django prend en charge bcrypt avec un minimum d’effort.

Pour utiliser Bcrypt comme algorithme de stockage par défaut, faites ceci :

  1. Installez le paquet bcrypt. Cela peut se faire en lançant python -m pip install django[bcrypt], ce qui est équivalent à python -m pip install bcrypt (en installant aussi toute version de paquet exigée par le fichier setup.cfg de Django).

  2. Modifiez PASSWORD_HASHERS pour que BCryptSHA256PasswordHasher apparaisse en premier. Voici ce qui devrait apparaître dans votre fichier de réglages :

    PASSWORD_HASHERS = [
        "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
        "django.contrib.auth.hashers.PBKDF2PasswordHasher",
        "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
        "django.contrib.auth.hashers.Argon2PasswordHasher",
        "django.contrib.auth.hashers.ScryptPasswordHasher",
    ]
    

    Conservez ou ajoutez d’autres éléments dans cette liste si vous avez besoin que Django puisse mettre à jour des mots de passe.

Et voilà, votre installation Django utilise dorénavant Bcrypt comme algorithme de stockage par défaut.

Utilisation de scrypt avec Django

scrypt est semblable à PBKDF2 et à bcrypt dans le sens où il utilise un nombre d’itérations pour ralentir les attaques par force brute. Cependant, comme PBKDF2 et bcrypt ne nécessitent pas beaucoup de mémoire, des attaquants avec suffisamment de ressources peuvent lancer des attaques en parallèle et à large échelle afin d’accélérer le processus d’attaque. scrypt est spécifiquement conçu pour utiliser plus de mémoire en comparaison d’autres fonctions de dérivation de clé basées sur des mots de passe, afin de limiter la capacité de parallélisme d’un éventuel attaquant. Voir RFC 7914 pour plus de détails.

Pour utiliser scrypt comme algorithme de stockage par défaut, faites ceci :

  1. Modifiez PASSWORD_HASHERS pour que ScryptPasswordHasher apparaisse en premier. Voici ce qui devrait apparaître dans votre fichier de réglages :

    PASSWORD_HASHERS = [
        "django.contrib.auth.hashers.ScryptPasswordHasher",
        "django.contrib.auth.hashers.PBKDF2PasswordHasher",
        "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
        "django.contrib.auth.hashers.Argon2PasswordHasher",
        "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    ]
    

    Conservez ou ajoutez d’autres éléments dans cette liste si vous avez besoin que Django puisse mettre à jour des mots de passe.

Note

scrypt nécessite OpenSSL 1.1+.

Augmentation de l’entropie du sel

La plupart de hacheurs de mots de passe incluent un sel dans l’empreinte du mot de passe afin de protéger contre les attaques par table arc-en-ciel. Le sel est lui-même une valeur aléatoire qui augmente la taille et donc le coût de la table arc-en-ciel ; elle possède actuellement 128 bits par la valeur salt_entropy dans BasePasswordHasher. Comme les coûts de calcul et de stockage diminuent avec le temps, cette valeur doit augmenter. Si vous implémentez votre propre hacheur de mots de passe, vous êtes libre de surcharger cette valeur pour atteindre le niveau d’entropie souhaité pour votre hacheur de mots de passe. salt_entropy est une mesure en bits.

Détails d’implémentation

En raison de la méthode avec laquelle les valeurs de sel sont stockées, la valeur salt_entropy est effectivement une valeur minimale. Par exemple, une valeur de 128 produit un sel qui contient en réalité 131 bits d’entropie.

Augmentation du facteur travail

PBKDF2 et bcrypt

Les algorithmes PBKDF2 et bcrypt utilisent un certain nombre d’itérations ou de passes de hachage. Cela ralentit volontairement les attaques, rendant plus difficiles les attaques contre les empreintes de mots de passe. Cependant, au fur et à mesure de l’augmentation des puissances de calcul, le nombre d’itérations nécessaires augmente également. Nous avons opté pour une valeur par défaut raisonnable (et nous l’augmenterons avec chaque nouvelle publication de Django), mais vous pouvez l’augmenter ou la réduire en fonction de vos besoins de sécurité et de vos capacités en puissance de calcul. Pour cela, vous pouvez créer une sous-classe de l’algorithme approprié et surcharger le paramètre iterations (utilisez le paramètre rounds lors d’un héritage du hacheur bcrypt). Par exemple, pour augmenter le nombre d’itérations utilisé par l’algorithme par défaut PBKDF2 :

  1. Créez une sous-classe de django.contrib.auth.hashers.PBKDF2PasswordHasher:

    from django.contrib.auth.hashers import PBKDF2PasswordHasher
    
    
    class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher):
        """
        A subclass of PBKDF2PasswordHasher that uses 100 times more iterations.
        """
    
        iterations = PBKDF2PasswordHasher.iterations * 100
    

    Enregistrez-la quelque part dans votre projet. Par exemple, vous pourriez la placer dans un fichier nommé myproject/hashers.py.

  2. Ajoutez votre nouvelle classe de hachage en premier dans PASSWORD_HASHERS:

    PASSWORD_HASHERS = [
        "myproject.hashers.MyPBKDF2PasswordHasher",
        "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",
    ]
    

Et voilà, votre installation Django utilise dorénavant plus d’itérations lorsqu’elle stockera les mots de passe avec PBKDF2.

Note

Le paramètre rounds de bcrypt est un facteur de travail logarithmique, par ex. 12 rounds signifie 2 ** 12 itérations.

Argon2

Argon2 possède les attributs suivants qui peuvent être personnalisés :

  1. time_cost contrôle le nombre d’itérations pour l’empreinte.
  2. memory_cost contrôle la taille mémoire utilisée lors du calcul de l’empreinte.
  3. parallelism contrôle le nombre de processeurs pouvant être mis à contribution pour le calcul de l’empreinte.

Les valeurs par défaut de ces attributs sont probablement raisonnables. Si vous déterminez que le hachage des mots de passe est trop rapide ou trop lent, vous pouvez les ajuster comme suit :

  1. Définissez parallelism au nombre de fils d’exécution à exploiter pour le calcul de l’empreinte.
  2. Définissez memory_cost au nombre de Kio de mémoire à exploiter.
  3. Ajustez time_cost et mesurez le temps passé à hacher un mot de passe. Choisissez une valeur time_cost qui consomme un temps acceptable pour vous. Si time_cost défini à 1 est encore trop lent, baissez la valeur memory_cost.

Interprétation de memory_cost

L’utilitaire en ligne de commande argon2 et certaines autres bibliothèques interprètent le paramètre memory_cost différemment de la valeur que Django utilise. La conversion est donnée par memory_cost == 2 ** memory_cost_en_ligne_de_commande.

scrypt

scrypt possède les attributs suivants qui peuvent être personnalisés :

  1. work_factor contrôle le nombre d’itérations pour l’empreinte.
  2. block_size
  3. parallelism contrôle le nombre de fils d’exécution lancés en parallèle.
  4. maxmem limite la taille mémoire maximale pouvant être utilisée lors du calcul de l’empreinte. La velur par défaut est 0, ce qui indique que c’est la valeur par défaut de la bibliothèque OpenSSL qui sera utilisée.

Nous avons choisi des valeurs par défaut raisonnable, mais vous pouvez les ajuster vers le haut ou vers le bas en fonction de vox exigences de sécurité et de votre puissance de calcul disponible.

Estimation de l’utilisation de la mémoire

L’exigence minimale de mémoire pour scrypt est

work_factor * 2 * block_size * 64

ce qui implique que vous pourriez devoir ajuster maxmem lorsque vous modifiez les valeurs de work_factor ou block_size.

Mise à niveau des mots de passe

Lorsque les utilisateurs se connectent, si leur mot de passe est stocké avec un autre algorithme que celui qui est défini comme le principal, Django met automatiquement à jour l’algorithme pour utiliser ce dernier. Cela signifie que d’anciennes installations de Django deviennent automatiquement plus sécurisées au fur et à mesure que les utilisateurs se connectent, et cela signifie également que vous pouvez passer à de nouveaux (et meilleurs) algorithmes de stockage au moment où ils deviennent disponibles.

Cependant, Django peut uniquement mettre à jour les mots de passe utilisant des algorithmes mentionnés dans PASSWORD_HASHERS, il est donc important de ne pas enlever les anciens algorithmes de cette liste quand vous passez à de nouveaux systèmes. Si vous le faites, les utilisateurs utilisant des algorithmes non mentionnés ne pourront pas mettre à jour leur mot de passe. Les mots de passe hachés sont mis à jour lorsque le nombre d’itérations PBKDF2, de tours bcrypt ou d’attributs argon2 est augmenté (ou réduit).

Soyez conscients que si tous les mots de passe de votre base de données se sont pas codés avec l’algorithme de hachage par défaut, il est possible que vous soyez vulnérable à une attaque de type énumération de compte basée sur le temps en raison d’une différence entre la durée d’une requête de connexion pour un utilisateur avec mot de passe codé dans un algorithme différent et la durée d’une requête de connexion d’un utilisateur non existant (qui exécute l’algorithme par défaut). Il est possible de relativiser ce problème en mettant à jour les anciennes empreintes de mot de passe.

Mise à jour des mots de passe sans exiger de connexion

Si une base de données existante comporte des empreintes de mots de passe anciennes et vulnérables de type MD5, il peut être souhaitable de mettre à jour ces empreintes sans devoir attendre que les utilisateurs concernés se connectent (ce qui pourrait ne jamais arriver si ces utilisateurs ne reviennent plus sur le site). Dans ce cas, vous pouvez utiliser un hacheur de mots de passe « enveloppé ».

Pour cet exemple, nous allons migrer une série d’empreintes MD5 afin qu’elles utilisent PBKDF2(MD5(mot_de_passe)) et ajouter le hacheur de mot de passe correspondant qui se chargera de vérifier le mot de passe de l’utilisateur lors d’une connexion. Nous partons du principe que c’est le modèle d’utilisateur par défaut qui est utilisé et que le projet possède une application accounts. Vous pouvez modifier cet exemple pour qu’il fonctionne avec tout autre algorithme ou avec un modèle d’utilisateur personnalisé.

Nous allons d’abord ajouter le hacheur personnalisé :

accounts/hashers.py
from django.contrib.auth.hashers import (
    PBKDF2PasswordHasher,
    MD5PasswordHasher,
)


class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
    algorithm = "pbkdf2_wrapped_md5"

    def encode_md5_hash(self, md5_hash, salt, iterations=None):
        return super().encode(md5_hash, salt, iterations)

    def encode(self, password, salt, iterations=None):
        _, _, md5_hash = MD5PasswordHasher().encode(password, salt).split("$", 2)
        return self.encode_md5_hash(md5_hash, salt, iterations)

La migration de données pourrait ressembler à ceci :

accounts/migrations/0002_migrate_md5_passwords.py
from django.db import migrations

from ..hashers import PBKDF2WrappedMD5PasswordHasher


def forwards_func(apps, schema_editor):
    User = apps.get_model("auth", "User")
    users = User.objects.filter(password__startswith="md5$")
    hasher = PBKDF2WrappedMD5PasswordHasher()
    for user in users:
        algorithm, salt, md5_hash = user.password.split("$", 2)
        user.password = hasher.encode_md5_hash(md5_hash, salt)
        user.save(update_fields=["password"])


class Migration(migrations.Migration):
    dependencies = [
        ("accounts", "0001_initial"),
        # replace this with the latest migration in contrib.auth
        ("auth", "####_migration_name"),
    ]

    operations = [
        migrations.RunPython(forwards_func),
    ]

Il faut savoir que cette migration va prendre plusieurs minutes pour quelques milliers d’utilisateurs, en fonction de la performance de votre matériel.

Pour terminer, nous allons ajouter un réglage PASSWORD_HASHERS:

mysite/settings.py
PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "accounts.hashers.PBKDF2WrappedMD5PasswordHasher",
]

Incluez tout autre hacheur utilisé par votre site dans cette liste.

Hacheurs de mots de passe intégrés

La liste complète de hacheurs de mots de passe inclus dans Django est :

[
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.BCryptPasswordHasher",
    "django.contrib.auth.hashers.ScryptPasswordHasher",
    "django.contrib.auth.hashers.MD5PasswordHasher",
]

Les noms d’algorithmes correspondants sont :

  • pbkdf2_sha256
  • pbkdf2_sha1
  • argon2
  • bcrypt_sha256
  • bcrypt
  • scrypt
  • md5

Écriture de son propre algorithme de hachage

Si vous écrivez votre propre algorithme de hachage de mot de passe doté d’un facteur de travail tel qu’un nombre d’itérations, vous devriez implémenter une méthode harden_runtime(self, password, encoded) pour compenser la différence de temps d’exécution entre le facteur de travail fourni dans le mot de passe encoded et le facteur de travail par défaut de l’algorithme. Ceci empêche les attaques de type énumération de compte basée sur le temps qui utilise la différence entre les requêtes de connexion d’un utilisateur dont le mot de passe est codé avec un ancien nombre d’itérations et un utilisateur inexistant (qui fait appel au nombre d’itérations par défaut de l’algorithme).

En prenant PBKDF2 comme exemple, si encoded contient 20’000 itérations et que le nombre d’itérations par défaut de l’algorithme est de 30’000, la méthode devrait faire passer password à travers 10’000 itérations supplémentaires de PBKDF2.

Si votre algorithme de hachage ne possède pas de facteur de travail, implémentez la méthode comme fictive (pass).

Gestion manuelle des mots de passe

Le module django.contrib.auth.hashers fournit un ensemble de fonctions pour créer et valider les empreintes de mots de passe. Vous pouvez les utiliser indépendamment du modèle User.

check_password(password, encoded, setter=None, preferred='default')
acheck_password(password, encoded, asetter=None, preferred='default')

Version asynchrone: acheck_password()

Si vous souhaitez authentifier manuellement un utilisateur en comparant un mot de passe en clair à l’empreinte du mot de passe dans la base de données, utilisez la fonction check_password(). Elle prend deux arguments obligatoires : le mot de passe en clair à vérifier et la valeur complète du champ password d’un utilisateur dans la base de données avec laquelle la vérification sera faite. Si le mot de passe est correcte, la fonction renvoie True, sinon False. Il est possible de transmettre en option un objet exécutable setter qui prend un mot de passe en paramètre et qui sera appelé au moment de le régénérer. Vous pouvez aussi transmettre preferred pour modifier l’algorithme de hachage si vous ne souhaitez pas utiliser l’algorithme par défaut (première ligne du réglage PASSWORD_HASHERS). Voir Hacheurs de mots de passe intégrés pour le nom d’algorithme de chaque hacheur.

Changed in Django 5.0:

La méthode acheck_password() a été ajoutée.

make_password(password, salt=None, hasher='default')

Crée une empreinte de mot de passe au format utilisé par cette application. Elle demande un paramètre obligatoire : le mot de passe en clair (chaîne ou octets). Il est aussi possible de fournir un sel et un algorithme de hachage à utiliser, si vous ne souhaitez pas utiliser les valeurs par défaut (première ligne du réglage PASSWORD_HASHERS). Voir Hacheurs de mots de passe intégrés pour le nom d’algorithme de chaque hacheur de mots de passe. Si le paramètre du mot de passe vaut None, un mot de passe inutilisable est renvoyé (un mot de passe qui fera que check_password() renvoie toujours False) .

is_password_usable(encoded_password)

Renvoie False si le mot de passe a été produit par User.set_unusable_password().

Validation des mots de passe

Les utilisateurs choisissent souvent des mots de passe faibles. Pour aider à résoudre ce problème, Django offre une validation des mots de passe à options. Vous pouvez configurer plusieurs validateurs de mots de passe en parallèle. Quelques-uns sont inclus dans Django, mais vous pouvez aussi écrire votre propre validateur.

Chaque validateur de mot de passe doit fournir un texte d’aide pour expliquer les exigences aux utilisateurs, la validation d’un mot de passe donné et un message d’erreur si le mot de passe ne respecte pas les exigences. Facultativement, il peut définir une fonction de rappel afin d’être averti lorsque le mot de passe d’un utilisateur a été changé. Les validateurs peuvent également proposer des réglages facultatifs pour spécialiser leur comportement.

La validation est contrôlée par le réglage AUTH_PASSWORD_VALIDATORS. La liste de ce réglage est vide par défaut, ce qui signifie qu’aucun validateur n’est appliqué. Dans les nouveaux projets créés par le modèle startproject par défaut, un ensemble de validateurs sont activés.

Par défaut, les validateurs sont utilisés dans les formulaires pour réinitialiser ou modifier les mots de passe, ainsi que dans les commandes d’administration createsuperuser et changepassword. Les validateurs ne sont pas appliqués au niveau des modèles, par exemple dans User.objects.create_user() et create_superuser(), parce qu’à ce niveau ce sont les développeurs et non les utilisateurs qui interagissent avec Django et également parce que la validation des modèles n’est pas automatiquement exécutée dans le contexte de la création de modèles.

Note

La validation des mots de passe peut éviter l’emploi de différents types de mots de passe faibles. Cependant, même si un mot de passe est validé par tous les validateurs, cela ne garantit pas forcément un mot de passe solide. Il existe de nombreux facteurs affaiblissant la solidité d’un mot de passe et qui ne peuvent pas être détectés par les validateurs de mots de passe même les plus avancés.

Activation de la validation des mots de passe

La validation des mots de passe est configurée par le réglage AUTH_PASSWORD_VALIDATORS:

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
        "OPTIONS": {
            "min_length": 9,
        },
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]

Cet exemple active les quatre validateurs intégrés :

  • UserAttributeSimilarityValidator, qui vérifie la similitude entre le mot de passe et certains attributs de l’utilisateur.
  • MinimumLengthValidator, qui vérifie la longueur minimale du mot de passe. Ce validateur est configuré avec une option personnalisée : il exige une longueur minimale de 9 caractères, au lieu des 8 par défaut.
  • CommonPasswordValidator, qui vérifie si le mot de passe se trouve dans une liste de mots de passe courants. Par défaut, cette comparaison se fait avec une liste de 20’000 mots de passe courants.
  • NumericPasswordValidator, qui vérifie que le mot de passe n’est pas entièrement numérique.

Pour UserAttributeSimilarityValidator et CommonPasswordValidator, nous utilisons les réglages par défaut dans cet exemple. NumericPasswordValidator ne contient pas de réglages.

Les textes d’aide et les messages d’erreur des validateurs de mots de passe sont toujours renvoyés dans l’ordre de leur apparition dans AUTH_PASSWORD_VALIDATORS.

Validateurs intégrés

Django fournit quatre validateurs intégrés :

class MinimumLengthValidator(min_length=8)

Valide une longueur minimale des mots de passe. Cette longueur minimale peut être personnalisée avec le paramètre min_length.

class UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7)

Valide la non-similitude du mot de passe avec plusieurs attributs de l’utilisateur.

Le paramètre user_attributes correspond à une itération de noms d’attributs utilisateur avec lesquels la comparaison sera effectuée. Si ce paramètre est omis, la liste par défaut contient : 'username', 'first_name', 'last_name', 'email'. Les attributs qui n’existent pas sont ignorés.

La similarité maximale autorisée des mots de passe peut être définie sur une échelle de 0,1 à 1,0 avec le paramètre max_similarity. Cette valeur est comparée au résultat de difflib.SequenceMatcher.quick_ratio(). Une valeur de 0,1 rejette les mots de passe qui ne sont pas clairement différents des attributs user_attributes, alors qu’une valeur de 1,0 ne rejette que les mots de passe qui sont identiques à une valeur d’attribut.

Changed in Django 2.2.26:

Le paramètre max_similarity était limité à une valeur minimale de 0,1.

class CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)

Valide que le mot de passe ne correspond pas à un mot de passe courant. Le mot de passe est converti en minuscules (pour effectuer une comparaison insensible à la casse) puis est comparé à une liste de 20’000 mots de passe courants créée par Royce Williams.

password_list_path peut contenir un chemin vers un fichier personnalisé de mots de passe courants. Ce fichier doit contenir un mot de passe en minuscules par ligne et peut être soit compressé gzip, soit du texte pur.

Changed in Django 4.2:

La liste des 20 000 mots de passe les plus courants a été mise à jour à sa version la plus récente.

class NumericPasswordValidator

Vérifie que le mot de passe n’est pas entièrement numérique.

Intégration de la validation

Quelques fonctions dans django.contrib.auth.password_validation peuvent être appelées depuis vos propres formulaires ou votre code pour intégrer la validation des mots de passe. Cela peut être utile par exemple si vous utilisez des formulaires personnalisés pour la définition des mots de passe ou si vous écrivez des appels d’API permettant la définition de mots de passe.

validate_password(password, user=None, password_validators=None)

Valide un mot de passe. Si tous les validateurs estiment que le mot de passe est valide, None sera renvoyé. Si un ou plusieurs validateurs rejettent le mot de passe, une exception ValidationError est produite contenant tous les messages d’erreur des validateurs.

L’objet user est facultatif : s’il n’est pas présent, certains validateurs pourraient ne pas être capable de procéder à la validation et accepteront donc n’importe quel mot de passe.

password_changed(password, user=None, password_validators=None)

Informe tous les validateurs que le mot de passe a été changé. Cela peut être utilisé par certains validateurs, par exemple pour éviter de réutiliser un même mot de passe. Cette méthode doit être appelée après que le mot de passe a été modifié avec succès.

Pour les sous-classes de AbstractBaseUser, le champ mot de passe sera marqué « dirty » lors de l’appel à set_password() qui déclenche un appel à password_changed() après l’enregistrement de l’utilisateur.

password_validators_help_texts(password_validators=None)

Renvoie une liste des textes d’aide de tous les validateurs. Ceux-ci expliquent les exigences du mot de passe aux utilisateurs.

password_validators_help_text_html(password_validators=None)

Renvoie une chaîne HTML avec tous les textes d’aide dans une balise <ul>. C’est pratique lors de l’ajout de la validation des mots de passe dans les formulaires, car cela permet de passer directement ce contenu au paramètre help_text d’un champ de formulaire.

get_password_validators(validator_config)

Renvoie un ensemble d’objets validateurs en fonction du paramètre validator_config. Par défaut, toutes les fonctions utilisent les validateurs définis dans AUTH_PASSWORD_VALIDATORS, mais en appelant cette fonction avec un ensemble différent de validateurs et en passant le résultat dans le paramètre password_validators des autres fonctions, c’est cet ensemble de validateurs qui sera alors utilisé. C’est pratique lorsqu’un certain groupe de validateurs s’applique à la majorité des scénarios, mais qu’il existe certains cas qui demandent un autre ensemble. Si vous utilisez toujours les mêmes validateurs, cette fonction n’est pas utile car la configuration provenant de AUTH_PASSWORD_VALIDATORS est utilisée par défaut.

La structure de validator_config est identique à la structure de AUTH_PASSWORD_VALIDATORS. La valeur renvoyée par cette fonction peut être transmise dans le paramètre password_validators des fonctions présentées précédemment.

Notez que là où le mot de passe est passé en paramètre d’une de ces fonctions, il s’agit toujours du mot de passe en clair, pas de la version hachée.

Écriture de son propre validateur

Si les validateurs intégrés à Django ne suffisent pas, vous pouvez écrire votre propre validateur de mot de passe. Les validateurs ont une interface assez restreinte. Elles doivent implémenter deux méthodes :

  • validate(self, password, user=None): valide un mot de passe. Renvoie None si le mot de passe est valide, ou génère une erreur ValidationError avec un message d’erreur si le mot de passe n’est pas valide. La méthode doit pouvoir gérer un utilisateur user valant None; si cela signifie que le validateur ne peut pas s’appliquer, renvoyez None pour signifier qu’il n’y a pas d’erreur.
  • get_help_text(): fournit un texte d’aide pour expliquer les exigences à l’utilisateur.

Tout ce qui est dans OPTIONS dans AUTH_PASSWORD_VALIDATORS pour votre validateur sera transmis au constructeur. Tous les paramètres du constructeur doivent posséder une valeur par défaut.

Voici un exemple basique d’un validateur, avec un réglage facultatif :

from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _


class MinimumLengthValidator:
    def __init__(self, min_length=8):
        self.min_length = min_length

    def validate(self, password, user=None):
        if len(password) < self.min_length:
            raise ValidationError(
                _("This password must contain at least %(min_length)d characters."),
                code="password_too_short",
                params={"min_length": self.min_length},
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least %(min_length)d characters."
            % {"min_length": self.min_length}
        )

Vous pouvez aussi implémenter password_changed(password, user=None), qui sera appelée à la suite d’un changement de mot de passe réussi. Cela peut être utilisé pour éviter la réutilisation de mot de passe, par exemple. Cependant, si vous décidez de stocker les mots de passe passés d’un utilisateur, vous ne devriez jamais le faire avec les mots de passe en clair.

Back to Top