Quel est le but ?

Il s’agit de stocker des informations permettant d’authentifier une personne sur une application. Il faut garder à l’esprit que ces données sont sensibles et doivent donc être sécurisées.

On voit souvent des news indiquant que des mots de passe ont fuité. Cela est arrivé à Sony, LinkedIn et malheureusement a beaucoup d’autres . Il faut donc faire en sorte que ce genre d’incidents ne puisse pas arriver dans votre entreprise. En effet vous êtes responsable de vos données. De plus, que vont penser vos clients si votre entreprise fait la une de l’actualité pour fuite de comptes utilisateurs. Ce n’est donc pas un sujet à prendre à la légère.

Voyons comment gérer les mots de passe dans vos applications ?

Stocker le mot de passe en clair

Il s’agit de la méthode préconisée par les personnes qui vous veulent du mal.

Allez, fini de rigoler, passons aux choses sérieuses.

Chiffrer le mot de passe

Cette méthode consiste à chiffrer le mot de passe avec une clé. On peut ensuite déchiffrer le mot de passe pour le comparer avec celui saisi par l’utilisateur.

Cette méthode ne protège pas correctement le mot de passe puisque si votre base de données est compromise il y a de forte chance que la clé de chiffrement le soit aussi. L’attaquant pourra donc déchiffrer les mots de passe.

Hasher le mot de passe

Il faut donc trouver une méthode pour ne pas stocker le mot de passe en clair ni de façon réversible. Heureusement des mathématiciens ont mis au point des algorithmes pour nous. Ces algorithmes vont nous permettre d’obtenir une empreinte à partir du mot de passe. L’empreinte générée par deux mots de passe identiques est identique. C’est cette empreinte que nous allons stocker dans la base de données. En effet pour valider que le mot de passe est identique il suffit de calculer l’empreinte du mot de passe saisi par l’utilisateur et de le comparer à l’empreinte stockée dans la base de données.

Il existe plusieurs algorithmes tels que MD5, SHA1, SHA2, SHA3… Cependant plusieurs algorithmes sont considérés comme non sûrs : MD5, SHA1. Il ne faut donc pas les utiliser. Renseignez-vous sur la sécurité des différents algorithmes au moment de l’implémentation de votre application. En effet cela peut évoluer entre l’écriture de cet article et le moment où vous le lisez.

L’intérêt de ces algorithmes est de “ fonctionner ” dans un seul sens. C’est-à-dire qu’à partir du mot de passe on peut calculer l’empreinte mais qu’à partir de l’empreinte on ne peut pas retrouver le mot de passe. Ça c’est pour la théorie. Dans la pratique il existe des Rainbow Table. Il s’agit de table pré-calculée contenant l’association mot de passe empreinte. Si l’empreinte de notre mot de passe se trouve dans la table, nous pouvons donc retrouver le mot de passe.

public static string HashPassword(string password, string algorithm = "sha512")
{ 
   return Hash(Encoding.UTF8.GetBytes(password), algorithm); 
} 
 
public static string Hash(byte[] input, string algorithm = "sha512") 
{ 
   if (input == null) throw new ArgumentNullException("input"); 
   using (HashAlgorithm hashAlgorithm = HashAlgorithm.Create(algorithm)) 
   { 
   if (hashAlgorithm != null) 
   return Convert.ToBase64String(hashAlgorithm.ComputeHash(input)); 
   throw new ArgumentException("invalid algorithm", "algorithm"); 
   } 
}

Hasher le mot de passe avec un sel

Nous avons donc un problème avec les Rainbow Tables. La solution consiste à concaténer une valeur aléatoire au mot de passe avant de la hasher. Cette valeur s’appelle un “ sel ”. Par exemple si le mot de passe est “ toto ” nous allons générer une chaine de caractères aléatoire “ krghjéàùhvjbgUYf42575gdj ” que nous allons concaténer au mot de passe “ toto krghjéàùhvjbgUYf42575gdj ”. C’est cette dernière chaine de caractères que nous allons hasher. Si nous utilisons un sel différent pour chaque utilisateur, l’attaquant devra brute-forcer chaque mot de passe.

Le sel doit être suffisamment long pour qu’il ne soit plus possible d’utiliser une Rainbow Table (trop long à générer et nécessitant trop d’espace disque pour la sauvegarder). Un sel de 128 bits semble suffisant. L’attaquant ne peut plus pré-calculer des Rainbow Tables. Il doit donc brute-forcer le mot de passe pour chaque utilisateur. Là encore notre but est de lui compliquer la tâche. Il faut donc choisir un algorithme long à calculer. Si vous voulez comprendre pourquoi il est important d’utiliser un algorithme ayant cette propriété, je vous laisse essayer de brute forcer une empreinte par vous-même grâce à hashcat. Vous pourrez ainsi voir à quelle vitesse ça calcule avec une machine normale.

Voici un exemple permettant de générer un sel

public static byte[] GenerateSalt(int byteLength) 
{ 
   byte[] data = new byte[byteLength]; 
   using (RNGCryptoServiceProvider cryptoServiceProvider = new RNGCryptoServiceProvider()) 
   { 
   cryptoServiceProvider.GetBytes(data); 
   return data; 
   } 
}

Algorithmes spécialisés

Des algorithmes adaptés pour les mots de passe ont été mis au point tels que bcrypt, scrypt ou PBKDF2. Ce sont des algorithmes plus longs que MD5 ou SHA1 pour calculer une empreinte (Ils sont donc plus long à brute-forcer). En fait leur coût (work factor) est représenté par un nombre d’itérations. Ce nombre est paramétrable et doit être adapté au besoin. Plus il est faible, plus il sera facile de trouver le mot de passe. Plus il est élevé, plus la puissance de calcul nécessaire sera importante. Il faut donc être au-dessus des recommendations actuelles mais pas trop non plus pour ne pas dégrader l’expérience utilisateur, ni nécessiter des serveurs surpuissants. Bref il faut faire des expérimentations.

public static string HashPassword(string password, int saltSize, int iterations) 
{ 
   if (password == null) throw new ArgumentNullException("password"); 
   byte[] salt; 
   byte[] bytes; 
   using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, saltSize, iterations)) 
   { 
   salt = rfc2898DeriveBytes.Salt; 
   bytes = rfc2898DeriveBytes.GetBytes(32); 
   } 
   byte[] inArray = new byte[saltSize + 32]; 
   Buffer.BlockCopy((Array)salt, 0, (Array)inArray, 0, saltSize); 
   Buffer.BlockCopy((Array)bytes, 0, (Array)inArray, saltSize, 32); 
   return Convert.ToBase64String(inArray); 
}

Last but not least : choisir le bon mot de passe

Toutes ces méthodes ne servent à rien si les utilisateurs utilisent “123456” comme mot de passe (En gros il faut éviter tout mot de passe se trouvant dans la liste des mots de passe les plus utilisés). Vous pouvez donc introduire une fonction de vérification de la complexité du mot de passe (longueur, diverité des caractères).

Conclusion

En résumer il faut utiliser une méthode à sens unique qui transforme le mot de passe en une empreinte. Il faut bien sûr utiliser un sel différent pour chaque utilisateur. Cette transformation doit être longue pour éviter les attaques de type brute force.

Mais n’oubliez pas que la sécurité concerne toute la chaine, du PC du client au serveur. Il y a donc bien d’autres choses auxquelles il faut penser : Chiffrement des connexions, Sécurité de la base de données, Protection du serveur, …

Ne ratez plus aucune actualité avec la newsletter mensuelle de SoftFluent

Newsletter SoftFluent