Suite à mon billet sur la salaison des mots de passe et sur les Rainbow tables, j’ai appris que calculer une empreinte de mot de passe avec un algorithme tel que SHA-1 ou MD5, même avec du sel, n’est plus très sûr.
le problème est que ces algorithmes de hachage ont été conçus dans le but d’être rapides car ils sont au cœurs de nombreux système cryptographiques. Ils ont également été conçus pour pouvoir tourner sur du matériel spécialisé. Le résultat c’est que mon PC pour calculer un peu plus d’un million d’empreintes SHA-1 par seconde, et presque deux millions par seconde avec MD5. Il existe également des circuits imprimés spécialisés qui peuvent calculer une centaine de millions d’empreintes SHA-1/MD5 par seconde, mettez-en 10.000 en parallèle et vous pouvez casser tout mot de passe de 1 à 10 caractères en une seule journée.
La conclusion, c’est qu’un algorithme rapide est exactement ce que l’on ne veut pas lorsqu’on calcule une empreinte de mot de passe. Utiliser un algorithme rapide comme SHA-1 ou MD5 pour calculer l’empreinte de mots de passe est aussi stupide que de ne pas mettre de sel avant de calculer l’empreinte. Ne le donc faites pas!
Utilisez un algorithme de hachage lent. Un tel algorithme demande énormément de temps pour calculer son empreinte, et n’est pas adapté pour tourner sur du matériel spécialisé. Il ne pourra donc jamais être cassé par des attaques par force brute.
L’état de l’art dans ce domaine aujourd’hui s’appelle BCrypt. Il a été mis au point en 1999 pour OpenBSD. Son principal avantage est que c’est vous, le programmeur, qui décidez combien de temps prend le calcul d’une empreinte. Mais attention, BCrypt est fait pour être lent. Sur mon PC, avec le réglage le plus rapide, le calcul d’une empreinte demande 2 msec, soit 500 empreintes par seconde. On est loin du million d’empreinte obtenu avec SHA-1 ou MD5. Avec le réglage par défaut, mon PC ne génère pas plus de 15 empreintes par seconde.
En conclusion, voici quelques règles à suivre pour stocker des mots de passe:
Règle numéro 1: Stockez l’empreinte des mot de passe, pas les mot de passe eux-même.
- Ne stockez pas les mot de passe en clair
- Ne stockez pas non plus une version cryptée des mot de passe. Les algorithmes de cryptage comme AES, DES, 3DES, BlowFish, RC4 ou RSA sont donc à bannir car ils sont réversibles par nature.
Règle numéro 2: Utilisez un algorithme de calcul d’empreinte si possible lent et éprouvé
Utilisez si possible BCrypt. Il est disponible pour Java, .NET, PHP, et bien d’autres langages encore.
Si vous ne pouvez vraiment pas, essayez d’utiliser SHA-512/384/256. Sinon SHA-1. En dernier recours MD5.
A savoir: MD5 a été cracké et SHA-1 n’est plus considéré comme sûr.
Règle numéro 3: Ajoutez du sel avant de calculer les empreintes.
- Le sel ajouté doit avoir été calculé avec une fonction cryptographique de génération de nombre aléatoire. Il faut donc le stocker à coté de l’empreinte.
- Le sel doit également être différent pour tous les utilisateurs (pour éviter les attaques sur un lot d’empreintes).
- Le sel doit être suffisamment long (12 caractères ou 24 octects au minimum).
La bonne méthode est la suivante (en pseudo Java)
// Calcule l'empreinte d'un mot de passe
public String computePasswordHash(String password) {
// Génère un tableau aléatoire de 32 octets
byte[] salt = CryptographicRandomNumberGenerator.getBytes(32);
// Calcule l'empreinte du mot de passe en utilisant le sel généré (le sel est inclus dans l'empreinte retourné)
return hashPassword(password, salt);
}
// Verifie que le mot de passe fourni correspond à celui qui a servir à calculer l'empreinte fournie.
public boolean checkPassword(String text, String hash) {
// Récupère le sel contenu dans l'empreinte.
String[] hashAndSalt = String.split(hash, "$");
byte[] salt = Base64.decode(hashAndSalt[1]);
// Calcule l'empreinte du mot de passe fourni avec le sel récupéré ci-dessus.
String hashedText = hashPassword(text, salt);
// Regarde si les empreintes sont indentiques.
return hashedText.equals(hash);
}
private String hashPassword(String password, byte[] salt) {
byte[] pwd = password.getBytes("UTF-8");
byte[] saltedPwd = Array.join(pwd, salt);
byte[] hash = hashFunction(saltedPwd);
return Base64.encode(hash) + "$" + Base64.encode(salt);
}
Enfin sachez qu’il existe aujourd’hui une méthode pour ne plus avoir à stocker les mots de passe nommée SRP. Si vous pouvez l’utiliser, c’est encore mieux que de stocker les mots de passe.