Crypter un mot de passe avec PHP

Crypter un mot de passe avec PHP

Tutoriel publié en novembre 2012 par Galdon dans la catégorie PHP

Nous allons voir ici comment crypter une information en PHP afin de pouvoir la stocker et l'échanger en toute sécurité.

Mais avant tout, on va faire un petit rappel sur la cryptographie symétrique (que l'on appelle aussi chiffrement en Français).

Le cryptage est une opération qui consiste à altérer une information pour la rendre totalement illisible, mais de manière réversible seulement pour une personne précise, qui est en mesure de décrypter cette information en lui redonnant sa forme originelle.

Comment fait on pour choisir qui peut décrypter et donc accéder à cette information ?

La réponse est simple : lorsqu'on crypte l'information, on va utiliser une clé de cryptage (une sorte de mot de passe, comme pour vous connecter à votre compte facebook). Et cette clé de cryptage permet aussi de faire l'opération inverse : décrypter l'information.

Cryptage AES avec PHP

Après ce petit rappel essentiel sur la cryptographie, on va pouvoir attaquer la pratique.

Nous allons voir comment crypter et décrypter avec l'algorithme Rijndael, qui est utilisé pour le standard AES ( depuis une dizaine d'années), grâce à la librairie libmcrypt.

Pour faire ça proprement, on va mettre tout dans une classe qui va servir de "Namespace" (même si ça n'est pas de la programmation orientée objet puisque tous les membres sont statiques) :

<?php
class Chiffrement {
	private static $cipher	= MCRYPT_RIJNDAEL_128;			// Algorithme utilisé pour le cryptage des blocs
	private static $key		= 'La blanquette est bonne';	// Clé de cryptage
	private static $mode	= 'cbc';						// Mode opératoire (traitement des blocs)

	public static function crypt($data){
		$keyHash = md5(self::$key);
		$key = substr($keyHash, 0,   mcrypt_get_key_size(self::$cipher, self::$mode) );
		$iv  = substr($keyHash, 0, mcrypt_get_block_size(self::$cipher, self::$mode) );

		$data = mcrypt_encrypt(self::$cipher, $key, $data, self::$mode, $iv);
		return base64_encode($data);
	}

	public static function decrypt($data){
		$keyHash = md5(self::$key);
		$key = substr($keyHash, 0,   mcrypt_get_key_size(self::$cipher, self::$mode) );
		$iv  = substr($keyHash, 0, mcrypt_get_block_size(self::$cipher, self::$mode) );

		$data = base64_decode($data);
		$data = mcrypt_decrypt(self::$cipher, $key, $data, self::$mode, $iv);
		return rtrim($data);
	}
}
?>

Et voilà comment ça s'utilise :

<?php
$clair   = "Salut !";
$crypt   = Chiffrement::crypt($clair);
$decrypt = Chiffrement::decrypt($crypt);
?>
<pre>
clair     : <?php echo $clair; ?>
crypt     : <?php echo $crypt; ?>
raw crypt : <?php echo base64_decode($crypt); ?>
decrypt   : <?php echo $decrypt; ?>
</pre>

Le fonctionnement est très simple : pour crypter et décrypter avec mcrypt, il suffit d'utiliser les fonctions mcrypt_encrypt et mcrypt_decrypt.

Mes ces fonctions prennent beaucoup de paramètres, qui sont en plus toujours les mêmes pour le chiffrement et pour le déchiffrement, à l'exception de $data bien sûr (qui contient la variable à crypter).

Du coup autant mutualiser tout ça dans des variables statiques, au sein d'une classe :

  • cipher
    Ce paramètre indique à mcrypt quel algorithme de chiffrement utiliser. En effet, libmcrypt propose de très nombreux algorithmes de cryptage (un peu plus de 40), dont vous trouverez la liste ici : mcrypt.ciphers.php
  • key
    Ce paramètre, c'est tout simplement la clé de cryptage dont je vous ai parlé plus haut, qui sert à crypter et à décrypter le message.
    Enfin ça n'est pas tout à fait vrai, on ne peut pas utiliser n'importe quelle chaine de caractère en clé, sa taille est fixe et dépend de l'algorithme utilisé.
    Plutôt que d'utiliser directement une clé, qui ressemblerait à ça : a7af2934b9c8ca2e346314bea5cf3a8f, on utilise une chaine de caractère beaucoup plus facile à retenir, faisant office de mot de passe.
    Ensuite on va générer une clé valide à partir de ce mot de passe avec une simple fonction de hachage, md5 en l'occurrence.
    Vous devez choisir votre propose mot de passe, et celui-ci doit rester secret
  • mode
    Le mode opératoire, une autre notion clé (si j'ose dire) de la cryptographie.
    On va voir plus bas à quoi ça correspond.

De plus, j'ai utilisé les fonctions base64_encode et base64_decode pour "encapsuler" les données cryptées (et pareil pour les décrypter, forcément, puisque c'est la réciproque). Ça n'est pas obligatoire, et ça n'est en aucun cas une mesure de sécurité supplémentaire. C'est juste pour éviter d'avoir des problèmes d'encodage (jeu de caractères) à cause des caractères spéciaux. Le base64 produit des chaines de caractère qui n'utilisent que 64 caractères ASCII qui sont compatibles avec tous les charset (latin1, utf8...).

Ça évite de bousiller les données cryptées en les stockant dans une base mysql ou dans un fichier mal adapté.

En testant le code ci-dessus, il se peut que vous rencontriez une erreur PHP :
Notice: Use of undefined constant MCRYPT_RIJNDAEL_128 - assumed 'MCRYPT_RIJNDAEL_128' in...
ou encore :
Fatal error: Call to undefined function mcrypt_get_key_size() in ...

Si c'est le cas, c'est tout simplement parce que vous n'avez pas activé l'extension mcrypt sur votre serveur PHP.

Comment activer l'extension mcrypt ?

Ouvrez le fichier de configuration php.ini et écrivez-y la ligne magique : extension=php_mcrypt.dll (sous Windows. Si vous êtes sur UNIX la syntaxe est différente, il n'y a pas de dll sous linux).

Si vous rencontrez cette erreur :
Warning: mcrypt_encrypt() [function.mcrypt-encrypt]: Module initialization failed
C'est que vous avez défini une mauvaise valeur pour l'un des paramètres de mcrypt (comme l'algorithme). Il faut impérativement utiliser une des constantes prédéfinies, ou alors une chaine de caractères parmi la liste des algorithmes qu'on obtient en appelant la fonction : mcrypt_list_algorithms().

Le plus important est fait, vous savez comment crypter et décrypter et vous pouvez réutiliser cette classe dans vos projets.

Pour ceux qui veulent en savoir plus, je vous conseille de lire ce qui suit, vous pourriez y trouver des trucs utiles ;)

Quelle est la différence entre cryptage et hachage ?

Beaucoup de développeurs ne connaissent pas bien la différence entre cryptage et hachage. Vous avez probablement déjà utilisé des fonctions de hachage, comme md5 ou sha1.

Une fonction de hachage génère une chaine de caractère ou un entier (que l'on appelle un hash). Ce hash est toujours identique pour une même information, du coup on s'en sert souvent pour stocker les mots de passe des utilisateurs dans une base de données de manière sécurisée.

La grande différence entre un algorithme de cryptage et de hachage, c'est que le hachage est irréversible. On ne peut pas reconstruire l'information originelle à partir de son hash md5 ou sha1, le hachage détruit l'information et ne conserve que sa "signature". C'est d'ailleurs là tout l'intérêt du cryptage qui lui est réversible.

Différence entre AES et Rijndael

Alors là, c'est une longue histoire. Je vous la raconte ?

Jusqu'à la fin des années 90, l'algorithme DES était l'algorithme de cryptage le plus utilisé, mais il commençait à montrer des signes de faiblesse face aux progrès de la cryptanalyse.

Du coup, une agence américaine nommée NIST a été chargée par le gouvernement des États-Unis de définir un nouveau standard pour le chiffrement, entre autres pour les besoins de l'état fédéral.

L'agence a sélectionné les 15 meilleurs algorithmes existants, et les a soumis à de nombreux tests, avec l'aide des meilleurs chercheurs du monde dans le domaine de la cryptographie. Ils ont réduit le nombre de candidats à 5, pour finalement n'en choisir qu'un : Rijndael.

Son nom bizarre vient de ses auteurs : Vincent Rijmen et Joan Daemen.

Donc AES et Rijndael c'est exactement la même chose. Enfin presque !

L'algorithme est identique, mais par contre la manière dont on l'utilise diffère par la taille des blocs (cf mode opératoire) :

Différences entre AES et Rijndael
Algorithme Taille de la clé Taille des blocs
AES-128 128 bit 128 bit
AES-192 128 bit 192 bit
AES-256 128 bit 256 bit
Rijndael-128 128 bit 128 bit
Rijndael-192 192 bit 192 bit
Rijndael-256 256 bit 256 bit

Si vous souhaitez en savoir plus, vous pouvez consulter les documents d'époque, dont ce rapport officiel du NIST : Report on the Development of the Advanced Encryption Standard (October 2, 2000).

Quel algorithme choisir ?

Comme je l'ai écrit plus haut, mcrypt propose plus de 40 algorithmes (cf mcrypt.ciphers.php) :

  • 3DES
  • ARCFOUR_IV
  • ARCFOUR
  • BLOWFISH
  • CAST_128
  • CAST_256
  • CRYPT
  • DES
  • DES_COMPAT
  • ENIGMA
  • GOST
  • IDEA (payant)
  • LOKI97
  • MARS
  • PANAMA
  • RIJNDAEL_128
  • RIJNDAEL_192
  • RIJNDAEL_256
  • RC2
  • RC4
  • RC6
  • RC6_128
  • RC6_192
  • RC6_256
  • SAFER64
  • SAFER128
  • SAFERPLUS
  • SERPENT
  • SERPENT_128
  • SERPENT_192
  • SERPENT_256
  • SKIPJACK
  • TEAN
  • THREEWAY
  • TRIPLEDES
  • TWOFISH
  • TWOFISH128
  • TWOFISH192
  • TWOFISH256
  • WAKE
  • XTEA

Pas facile de s'y retrouver dans le tas.

Mais il faut bien garder à l'esprit que beaucoup de ces algorithmes sont là uniquement pour des raisons de rétrocompatibilité, c'est "legacy" comme disent les Américains.

En pratique, je vous conseille d'utiliser Rijndael, ou alors un des 4 autres algorithmes finalistes candidats à l'AES sélectionnés par le NIST il y a 10 ans : MARS, RC6, Serpent et Twofish.

Choisir un mode opératoire

Les algorithmes de chiffrement cités ci-dessus peuvent fonctionner de 2 manières différentes :

  • Chiffrement par bloc (block cipher)
    C'est le mode le plus souvent utilisé, on s'en sert pour crypter des données de taille connue (comme une chaine de caractère, un fichier...)
    L'information à crypter n'est pas traitée comme un tout, elle est découpée en blocs de données, et chaque bloc est chiffré à l'aide de l'algorithme de cryptage.
  • Chiffrement par flot (bit par bit)
    Le chiffrement par bloc ne fonctionne pas lorsqu'on ignore la taille des données à crypter, ce qui est le cas lorsqu'on veut crypter un flux (exemple : les paquets dans un jeu vidéo comme Battlefield).

Dans la plupart des cas, en PHP, vous aurez besoin d'un chiffrement par bloc (pour chiffrer des informations à stocker dans une base de données, ou autre).

Le mode opératoire, c'est tout simplement la manière donc les blocs sont traités.
Mcrypt propose différents modes qui sont listés dans la documentation : mcrypt.constants.php, voici les 3 principaux :

  • ECB (electronic codebook)
    Dans ce mode, les blocs sont traités indépendamment, et c'est ce qui fait sa faiblesse. Même, si les données sont cryptées de manière parfaitement sécurisée à l'intérieur des blocs, on peut quand même parvenir à comprendre l'information contenue dans l'information chiffrée.
    Pour comprendre ça, on prend souvent l'exemple du cryptage d'une image : http://fr.wikipedia.org/wiki/Mode_opératoire_(cryptographie)
    Même si les blocs (petits morceaux d'image) sont cryptés (ce qui altère les couleurs), on devine encore très facilement les contours de l'image.
    Évitez d'utiliser ce mode, il n'est pas du tout sécurisé
  • CBC (cipher block chaining)
    La solution au problème mentionné ci-dessus, c'est de s'arranger pour qu'un bloc ne soit pas toujours chiffré de la même manière. Et ça se fait en injectant des informations d'un bloc dans le bloc suivant avant le cryptage. Du coup on obtient une sorte de perturbation qui se propage de bloc en bloc (effet papillon), et qui résout le problème.

    Sauf qu'il y a un hic : comment fait-on pour le premier bloc, puisque celui-ci n'a pas de prédécesseur ?
    C'est là qu'intervient ce qu'on appelle le vecteur d'initialisation (abrégé IV). L'IV c'est tout simplement l'information (ex: chaine de caractère) qui est injectée dans le premier bloc et qui "oriente" la perturbation.
    Contrairement à la clé, le vecteur d'initialisation n'a pas besoin d'être gardé secret (sa connaissance ne permet pas de décrypter plus facilement), même si c'est mieux de ne pas le divulguer.
    Par contre on en a besoin pour décrypter l'information, donc il ne faut surtout pas le perde !

    CBC est un très bon choix pour crypter des fichiers, des mots de passe...
    D'ailleurs c'est celui qui est utilisé dans l'exemple au début du tuto.
  • CFB (cipher feedback)
    CFB est sécurisé, il est particulièrement indiqué pour chiffrer des flux (chiffrement par flot)

Allez donc jeter un oeil sur cette page : texte en papier plié.

Tags
Tutoriaux similaires
14 commentaires :
commentaire n°2411 par macgamesandmore.com
macgamesandmore.com samedi 17 novembre 2012, 18:18
Quand on a perdu la clef est il possible de récuperer des textes encryptés ou c'est cuit à tout jamais ?
commentaire n°2412 par Galdon
Galdon dimanche 18 novembre 2012, 01:19
En pratique c'est perdu à tout jamais.

La seule "solution" serait le brute force : essayer toutes les combinaisons possible. Mais avec la technologie actuelle ça n'est pas possible dans un temps raisonnable (c'est ce qu'on appelle la sécurité calculatoire).
commentaire n°2535 par big_sou
big_sou jeudi 11 avril 2013, 11:54
Bonjour, pour les algorithmes de chiffrement par bloc, comme l'AES ou le TDEA, est ce qu'ils peuvent travailler avec n'importe quel mode opératoire (ECB, CBC, ..), ou bien chaque algorithme possède son propre mode ?
commentaire n°2537 par Galdon
Galdon jeudi 11 avril 2013, 19:33
Le mode opératoire est indépendant de l'algorithme.

L'algorithme sert juste à chiffrer un bloc, il est exécuté pour chaque bloc.

Le mode opératoire influe sur les paramètres qu'on donne à cet algorithme pour chaque bloc.
commentaire n°2578 par Fred-101
Fred-101 mardi 11 juin 2013, 14:46
Bonjour, je m'intéresse de près au cryptage et j'aimerais connaitre la robustesse de l’algorithme Rijndael.
commentaire n°2584 par Galdon
Galdon dimanche 16 juin 2013, 22:28
Euh, sur l'échelle de Chuck Norris ?
commentaire n°2590 par Fred-101
Fred-101 mardi 18 juin 2013, 12:15
;-)
Y a rien au dessus de Chuck !!!

Je voulais juste savoir si Rijndael était réputé pour sa fiabilité ou si l’algorithme se casse facilement.
Je n'ai pas trouvé d'article là dessus
commentaire n°2690 par Idleman
Idleman mardi 3 décembre 2013, 09:00
Salut Galdon, merci pour cette excellente explication.

Cependant je me suis heurté à un problème étrange que je ne m'explique pas:

Avec ton code chacune de mes variables encryptées ressortaient, au décryptage, avec des caractères non interprétés en fin de chaine (cf le screenshot: http://up.idleman.fr/php/action.php?action=openFile&;file=../uploads/Screenshots/cryptage_cbc.PNG).

Ça fonctionne en revanche si j'utilise le mode cfb au lieu de cbc, pourquoi? Mystère de l'informatique... si tu as un début de réponse.



commentaire n°2695 par Galdon
Galdon mercredi 4 décembre 2013, 12:12
Je ne vois pas l'image, ça affiche "Fichier privé, accès interdit".
commentaire n°2770 par Galdon
Galdon lundi 17 mars 2014, 12:07

J'ai compris d'où vient le problème. Comme je l'ai écrit, le chiffrement par bloc découpe le message en blocs de taille fixe.

Mais la taille du message n'est pas forcément un multiple de la taille du bloc, autrement dit la découpe ne tombe pas forcément pile-poil, donc on se retrouve potentiellement avec le dernier bloc partiellement rempli.

Or, certains modes opératoires (comme cbc et ecb) ne peuvent travailler qu'avec des blocs pleins, alors ils vont remplir arbitrairement le dernier bloc. On appelle ça le padding (ou remplissage en français, on dit aussi bourrage mais c'est moins classe...).

En général le remplissage se fait avec l'octet 0x00, qui correspond au caractère NUL. Et ce sont ces fameux octets de padding qu'on retrouve au décryptage à la fin du message.

Pour résoudre ce "bug" qui n'en n'est pas un, il suffit d'utiliser la fonction rtrim pour supprimer les caractères NUL à la fin de la chaine, comme l'a indiqué @seb en commentaire, et ça marche alors avec tous les modes opératoires.

J'ai corrigé le code PHP de la classe Chiffrement dans le tuto.

commentaire n°2718 par jerem98
jerem98 mardi 7 janvier 2014, 16:56
moi aussi j'ai le même problème Galdon,
ça donne ça:
texte a crypter : jeremy
texte crypter en aes 256: G8T4QTnd1UEn404iRMTyaqLzlnsxNr6ib0K788FT8KQ=
texte decrypter: jeremy��������������������������
En cfb ça fonctionne bien c'est bizarre ... une idée Galdon ?
commentaire n°2726 par cuisto44000
cuisto44000 samedi 25 janvier 2014, 10:46
En voilà un joli tuto dont le mdp est inspiré du magnifique film oss 117 :p haha
commentaire n°2733 par Galdon
Galdon jeudi 30 janvier 2014, 16:44
Ah je vois qu'on a les mêmes références ^^
commentaire n°2741 par seb
seb lundi 10 février 2014, 15:39
Il date un peu ce tuto, mais il m'a bien aidé !
Merci beaucoup pour le temps pris à expliquer !!!!
J'ai eu le même problème que jerem98.

J'ai ajouté un trim avant le return de decrypt.
Histoire de nettoyer.


facultatif
Facebook Twitter RSS Email
Forum Excel
Venez découvrir le nouveau forum excel question/réponse à la stackoverflow.com !
Forum Excel
hit parade n'en a rien a foutre du W3C Positionnement et Statistiques Gratuites Vincent Paré