Contactez-nous

Sécurité des mots de passe (stockage, politiques)

Apprenez les meilleures pratiques pour la sécurité des mots de passe dans Spring Boot : stockage sécurisé avec hachage adaptatif (Bcrypt), et mise en place de politiques de mot de passe robustes.

Le maillon faible : pourquoi la sécurité des mots de passe est-elle critique ?

Les mots de passe restent le mécanisme d'authentification le plus courant, mais ils sont aussi souvent le maillon le plus faible de la chaîne de sécurité. Les utilisateurs choisissent fréquemment des mots de passe faibles, les réutilisent sur plusieurs sites, ou les notent de manière non sécurisée. De plus, les bases de données d'identifiants sont des cibles privilégiées pour les attaquants.

Si une base de données contenant des mots de passe est compromise, la manière dont ces mots de passe sont stockés détermine l'ampleur des dégâts. Stocker des mots de passe en clair est absolument inacceptable. Même un simple hachage (comme MD5 ou SHA-1, désormais obsolètes pour cet usage) sans précautions supplémentaires est insuffisant face aux attaques modernes (tables arc-en-ciel, force brute sur GPU).

Une gestion sécurisée des mots de passe implique deux aspects principaux : un stockage robuste qui rend difficile la récupération des mots de passe originaux même en cas de fuite de données, et la mise en place de politiques de mots de passe qui encouragent ou forcent les utilisateurs à choisir des mots de passe plus forts et à les gérer de manière plus sûre.

Stockage sécurisé : hachage adaptatif avec Bcrypt

La méthode standard et recommandée pour stocker les mots de passe de manière sécurisée est d'utiliser une fonction de hachage cryptographique forte et adaptative, combinée à un sel (salt) unique pour chaque utilisateur.

  • Hachage : Une fonction de hachage transforme une donnée d'entrée (le mot de passe) en une chaîne de caractères de longueur fixe (le hash) de manière unidirectionnelle. Il est théoriquement impossible de retrouver le mot de passe original à partir du hash.
  • Sel (Salt) : C'est une chaîne de caractères aléatoire unique générée pour chaque mot de passe avant le hachage. Le sel est ensuite concaténé au mot de passe, et le tout est haché. Le sel est stocké en clair à côté du hash dans la base de données. L'utilisation d'un sel unique par mot de passe empêche les attaquants d'utiliser des tables arc-en-ciel précalculées et garantit que deux utilisateurs ayant le même mot de passe auront des hashs différents.
  • Adaptatif (Coût configurable) : Les fonctions de hachage modernes comme Bcrypt, Scrypt, ou Argon2 sont conçues pour être volontairement lentes et gourmandes en ressources (CPU/mémoire). Elles intègrent un "facteur de coût" (ou "work factor") qui peut être ajusté. Plus le coût est élevé, plus le calcul du hash est lent. Cela rend les attaques par force brute (essayer toutes les combinaisons possibles) extrêmement coûteuses et longues pour un attaquant, même avec du matériel puissant. L'aspect adaptatif permet d'augmenter le coût au fil du temps, à mesure que la puissance de calcul augmente, sans avoir à changer d'algorithme.

Bcrypt est l'algorithme le plus largement adopté et recommandé dans l'écosystème Spring Security. Il intègre nativement le sel et un facteur de coût configurable. Il est considéré comme une option très robuste pour le hachage de mots de passe.

Implémentation avec `PasswordEncoder` dans Spring Security

Spring Security abstrait la logique de hachage et de vérification derrière l'interface `org.springframework.security.crypto.password.PasswordEncoder`. L'implémentation recommandée est `BCryptPasswordEncoder`.

Vous devez déclarer un bean `PasswordEncoder` dans votre configuration Spring. Spring Security le détectera et l'utilisera automatiquement lors des processus d'authentification (par exemple, avec `UserDetailsService`) et potentiellement pour encoder les mots de passe avant de les stocker.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityBeansConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        // Utilisation de BCryptPasswordEncoder
        // Le constructeur peut prendre un paramètre optionnel pour la force (log rounds, 4-31, défaut 10)
        // return new BCryptPasswordEncoder(12); // Coût plus élevé
        return new BCryptPasswordEncoder(); 
    }
}

Utilisation pour le stockage : Lorsque vous enregistrez un nouvel utilisateur ou mettez à jour son mot de passe, vous devez utiliser ce bean `PasswordEncoder` pour hacher le mot de passe en clair avant de le sauvegarder en base de données.

@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    // Injection de dépendances
    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public void registerUser(String username, String rawPassword) {
        UserEntity newUser = new UserEntity();
        newUser.setUsername(username);
        // Hacher le mot de passe avant de le stocker
        newUser.setPassword(passwordEncoder.encode(rawPassword)); 
        // ... définir les rôles, etc. ...
        userRepository.save(newUser);
    }
}

Le hash produit par `passwordEncoder.encode()` contiendra le sel et le facteur de coût intégrés, prêt à être stocké dans une colonne de type chaîne de caractères (typiquement VARCHAR(60) ou plus pour Bcrypt).

Utilisation pour la vérification : Lors de l'authentification, Spring Security utilisera la méthode `matches(CharSequence rawPassword, String encodedPassword)` du `PasswordEncoder`. Cette méthode prend le mot de passe fourni par l'utilisateur (`rawPassword`) et le hash stocké en base (`encodedPassword`). `BCryptPasswordEncoder` extrait automatiquement le sel et le facteur de coût du hash stocké, re-hache le mot de passe fourni avec ces mêmes paramètres, et compare les résultats.

// Logique interne de Spring Security (simplifiée)
String rawPasswordInput = "password123";
String encodedPasswordFromDb = "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"; // Exemple de hash Bcrypt

boolean passwordsMatch = passwordEncoder.matches(rawPasswordInput, encodedPasswordFromDb); // Retourne true ou false

Politiques de mot de passe : renforcer la qualité

Un stockage sécurisé est essentiel, mais il est également important d'encourager ou d'imposer aux utilisateurs la création de mots de passe robustes. Les politiques de mots de passe définissent les règles que les mots de passe doivent respecter lors de leur création ou modification.

Les éléments courants d'une politique de mot de passe incluent :

  • Longueur minimale : Exiger une longueur minimale (par exemple, 12 ou 15 caractères) augmente considérablement la complexité.
  • Complexité des caractères : Imposer la présence de différentes classes de caractères (majuscules, minuscules, chiffres, symboles spéciaux).
  • Interdiction de mots de passe courants : Vérifier si le mot de passe proposé fait partie d'une liste de mots de passe fréquemment utilisés ou compromis (par exemple, via l'API "Have I Been Pwned").
  • Historique des mots de passe : Empêcher les utilisateurs de réutiliser leurs N derniers mots de passe.
  • Expiration des mots de passe : Forcer les utilisateurs à changer leur mot de passe périodiquement (bien que cette pratique soit de plus en plus débattue si une MFA robuste est en place).
  • Verrouillage de compte : Bloquer temporairement un compte après plusieurs tentatives de connexion infructueuses pour prévenir les attaques par force brute.

Implémentation : Spring Security ne fournit pas de mécanisme direct pour imposer ces politiques lors de la saisie du mot de passe (cela se fait généralement via la logique métier dans votre contrôleur ou service lors de l'enregistrement ou de la modification du mot de passe, souvent avec des bibliothèques de validation comme `passay`). Cependant, vous pouvez implémenter le verrouillage de compte en écoutant les événements d'échec d'authentification (`AuthenticationFailureBadCredentialsEvent`) et en mettant à jour l'état du compte utilisateur (`isAccountNonLocked` dans `UserDetails`).

Migration et mise à niveau du hachage

Que faire si votre application utilise actuellement un algorithme de hachage plus ancien (par exemple, SHA-256 avec un sel simple) et que vous souhaitez migrer vers Bcrypt ? Ou si vous souhaitez augmenter le facteur de coût de Bcrypt à mesure que la puissance de calcul évolue ?

Spring Security offre une solution élégante avec `DelegatingPasswordEncoder`. Ce `PasswordEncoder` spécial peut gérer plusieurs algorithmes de hachage simultanément. Vous le configurez avec un algorithme "par défaut" (celui utilisé pour les nouveaux mots de passe, par exemple Bcrypt) et une liste d'algorithmes "hérités".

Les hashs stockés en base doivent être préfixés par un identifiant indiquant l'algorithme utilisé (par exemple, `{bcrypt}$2a$...`, `{sha256}abcdef...`). Lorsque `DelegatingPasswordEncoder.matches()` est appelé, il examine le préfixe du hash stocké, délègue la vérification au `PasswordEncoder` approprié, et si la vérification réussit ET que l'algorithme utilisé n'est pas celui par défaut, il encode à nouveau le mot de passe avec l'algorithme par défaut et recommande de mettre à jour le hash stocké.

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; // Exemple d'autre encodeur
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; // Exemple d'autre encodeur
import java.util.HashMap;
import java.util.Map;

@Configuration
public class PasswordEncoderConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        String idForEncode = "bcrypt"; // Algorithme par défaut pour les nouveaux mots de passe
        Map encoders = new HashMap<>();
        encoders.put(idForEncode, new BCryptPasswordEncoder());
        // Ajouter ici les anciens encodeurs si nécessaire pour la migration
        // encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
        // encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
        // encoders.put("SHA-256", new StandardPasswordEncoder()); // Exemple (à éviter si possible)

        DelegatingPasswordEncoder delegatingEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
        // Optionnel: traiter les mots de passe sans préfixe comme étant encodés avec un algo spécifique
        // delegatingEncoder.setDefaultPasswordEncoderForMatches(encoders.get("SHA-256")); 
        
        return delegatingEncoder;
    }
}

Cette approche permet une migration progressive et transparente des algorithmes de hachage sans interrompre l'authentification des utilisateurs existants.

Conclusion : Une défense multicouche

La sécurité des mots de passe est un processus continu qui nécessite une attention constante. L'utilisation d'un hachage adaptatif fort comme Bcrypt via le `PasswordEncoder` de Spring Security est la pierre angulaire du stockage sécurisé. Combiner cela avec des politiques de mot de passe judicieuses et, idéalement, avec une authentification multi-facteurs (MFA), crée une défense multicouche robuste contre les compromissions de comptes.

En tant que développeur Spring Boot, maîtriser ces concepts et les implémenter correctement est essentiel pour protéger vos utilisateurs et maintenir l'intégrité de votre application.