Libsodium pour les nuls, ou la cryptographie en PHP
(Read the 🇬🇧 version here)
La cryptographie, plus communément appelé crypto, est une des disciplines de la cryptologie s’attachant à protéger des messages (assurant confidentialité, authenticité et intégrité) en s’aidant souvent de secrets ou clés.
Avez-vous déjà eu besoin :
- de protéger l’accès à une ressource sans pour autant stocker d’information en base ?
- de stocker des informations sensibles de manière sécurisée ?
- de vérifier l’authenticité d’un message ?
Après la lecture de cet article, vous saurez comment résoudre ces problématiques de manière sécurisée.
Section intitulée qu-est-ce-que-libsodiumQu’est ce que libsodium ?
Libsodium est une librairie écrite en C pour faire de la cryptographie. Elle est moderne (v1 en 2014), portable, écrite par Frank Denis 🇫🇷 et surtout très simple d’utilisation.
L’extension libsodium est packagée par défaut depuis PHP 7.2. Pour les versions de PHP antérieures à 7.2, il faudra installer une extension packagée en PECL. À la place de la PECL, il est aussi possible d’utiliser le polyfill sodium_compat.
L’extension libsodium va exposer une série de fonctions dans PHP afin de faciliter nos usages cryptographiques. Vous pouvez vérifier que l’extension est bien installée grâce à php --re sodium
qui va lister toutes les fonctions, classes, et constantes définies par l’extension.
Section intitulée prerequi-a-la-crypto-generer-des-valeurs-aleatoiresPrérequi à la crypto : Générer des valeurs aléatoires
La génération de valeurs aléatoires est quelque chose de très compliqué en informatique. À tel point que certaines entreprises comme Cloudflare utilisent des LavaLamp pour en générer. La génération de valeurs est primordiale lorsque l’on fait de la crypto. Un générateur de nombres (pseudo) aléatoires est ce que l’on appelle un Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).
libsodium expose plusieurs fonctions pour en générer. Cependant depuis PHP 7.0, il existe deux nouvelles fonctions random_bytes()
et random_int()
qui ont un niveau pseudo aléatoire acceptable. C’est pourquoi ces fonctions n’ont pas été portées lors de l’intégration de libsodium à PHP 7.2.
Section intitulée des-cas-d-usagesDes cas d’usages ?
À travers une série de use cases concrets, nous allons voir comment utiliser cette extension.
Section intitulée proteger-l-acces-a-une-ressourceProtéger l’accès à une ressource
Prenons l’exemple d’un site web qui affiche la position géographique d’un arrêt de bus sur une carte. Le fournisseur des cartes (mapbox, google maps, …) nous propose une API, mais cette API est authentifiée et rate limitée (nous ne pouvons l’appeler que X fois par jour).
Nous avons besoin de protéger notre point d’entrée d’API qui retourne une carte en fonction de certains paramètres. Nous pourrions stocker en base de données toutes les possibilités, mais cela nécessitera du code, de la maintenance, et de la place en base de données.
Une alternative possible consiste à signer les requêtes :
- le serveur génère du HTML contenant une balise image :
<img src="/image-map?lat=xxx&long=yyy&signature=zzzz">
- le navigateur demande l’image au serveur ;
- le serveur va vérifier que la signature de
xxx
etyyy
est bienzzzz
; - le serveur va chercher l’image chez le fournisseur de cartes ;
- le serveur renvoie l’image ;
Il est maintenant impossible de demander une carte qui n’a pas été émise par le serveur. Autrement dit, personne ne peut utiliser vos credentials d’API pour générer ses propres cartes qu’il utilisera sur son site.
Il faut commencer par générer une clé qu’il faudra bien garder au chaud et ne jamais perdre :
$key = sodium_crypto_generichash_keygen();
Puis il faut générer une signature pour une carte :
// Construire un message que l'on va signer
$msg = "lat=XXXX;long=YYYY";
// Générer la signature
$signature = sodium_crypto_generichash($msg, $key);
$signatureAsTxt = bin2hex($signature);
dump(['signature' => $signature, 'signatureAsTxt' => $signatureAsTxt]);
Ce qui va nous afficher quelque chose comme :
array:2 [
"signature" => b"\x19íOSÉý\x11YßÜ>B╩ûÐrõl\x02>^n b>ª©[ÖìÈ"
"signatureAsTxt" => "19a14f5390ec1159e19a3e42ca96d172e46c023e5e6eb6623ea6b8b25b998dd4"
]
que nous pourrons envoyer au client :
<img src="/image-map?lat=XXXX&long=YYYY&signature=19a14f5390ec1159e19a3e42ca96d172e46c023e5e6eb6623ea6b8b25b998dd4">
Lorsque une requête HTTP arrive, nous allons refaire le même processus puis vérifier que les deux signatures sont identiques :
$lat = $_GET['lat'];
$long = $_GET['long'];
$signatureAsTxt2 = $_GET['signature'];
$msg2 = "lat=$lat;long=$long";
$signature2 = sodium_crypto_generichash($msg2, $key);
// Nous vérifions les deux signatures :
$ok = hash_equals($signature2, hex2bin($signatureAsTxt2));
if (!$ok) {
throw new \Exception('Cette requête n\'est pas valide');
}
La génération du message ($msg
) peut être une tâche fastidieuse s’il y a beaucoup de paramètres. Dans ce cas, il sera plus simple d’utiliser les fonctions sodium_crypto_generichash_*()
:
$hashState = sodium_crypto_generichash_init();
sodium_crypto_generichash_update($hashState, 'XXXX');
sodium_crypto_generichash_update($hashState, 'YYYY');
$msg = sodium_crypto_generichash_final($hashState);
//...
Section intitulée stocker-des-informations-dans-un-fichier-via-un-mot-de-passeStocker des informations dans un fichier via un mot de passe
Prenons l’exemple d’un projet où certains développeurs peuvent se connecter via SSH aux serveurs de production. Vous ne voulez pas avoir un fichier avec tous les mots de passe sur votre réseau (ou chez Google Drive, Dropbox, Github, …) en clair, et vous avez raison. Cependant, stocker un fichier chiffré dans le dépôt Git que l’on ne peut « ouvrir » qu’avec un mot de passe vous convient ? Alors cette section est faite pour vous.
Il faut commencer par générer une clé qu’il faudra bien garder au chaud et ne jamais perdre :
$key = sodium_crypto_secretbox_keygen();
Ensuite il faut générer un nonce
. Contrairement à la clé, le nonce n’a pas besoin d’être privé. Nous pourrons le sauvegarder à côté de la valeur chiffrée.
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
Et enfin, nous pouvons chiffrer le message :
$encrypted = sodium_crypto_secretbox('my super secret data', $nonce, $key);
Et pour déchiffrer le message chiffré :
$decrypted = sodium_crypto_secretbox_open($encrypted, $nonce, $key);
Section intitulée authentifier-un-documentAuthentifier un document
Pour ce cas d’usage, prenons l’exemple du serveur qui génère des factures au format PDF. Il est assez facile de modifier un PDF, et donc vous voulez garantir l’intégrité du document et de son contenu. Concrètement, vous ne voulez pas que quelqu’un de mal intentionné fabrique de fausses factures qui auraient été émises par votre site web. Vous voulez également que n’importe qui puisse valider que le document a bien été émis par votre site web. Par exemple, qu’une assurance puisse vérifier qu’un client a bel et bien acheté tel ou tel article.
Grâce à ce système, les assureurs n’auront pas besoin de communiquer avec vous pour authentifier la facture.
La première étape consiste en la génération d’une clé qu’il faudra conserver secrètement :
$keyPair = sodium_crypto_sign_keypair();
À partir de cette clé, nous allons extraire une partie privée qui va nous servir à signer des documents et une partie publique que nous pourrons diffuser à tout le monde :
$keyPairSecret = sodium_crypto_sign_secretkey($keyPair);
$keyPairPublic = sodium_crypto_sign_publickey($keyPair);
Grâce à cette clé, nous pouvons maintenant signer le document :
$message = 'Hello';
$signature = sodium_crypto_sign_detached($message, $keyPairSecret);
Maintenant, nous pouvons envoyer le document avec sa signature au client. Celui-ci et l’assureur pourront authentifier le document de la manière suivante :
$ok = sodium_crypto_sign_verify_detached($signature, $message, $keyPairPublic);
if (!$ok) {
throw new \Exception('Le document a été modifié');
}
Section intitulée envoyer-un-message-super-securise-a-un-utilisateurEnvoyer un message super sécurisé à un utilisateur
Dans certains cas, nous voulons un niveau de sécurité maximal où même HTTPS ne suffit pas. HTTPS peut être compromis à différents niveaux : une faille dans SSL, une entreprise qui fait un Man In The Middle, etc… Pour palier ce problème, il est possible de générer un message que seule une personne pourra lire. Pour ce faire, nous allons utiliser un chiffrement asymétrique (comme dans le chapitre précédent) mais avec plus de clés.
Un cas concret d’utilisation est un client mail, où la sécurité doit être maximale.
Le principe est le suivant :
- Alice crée une paire de clés asymétriques (publique et privée) ;
- Bob crée une paire de clés asymétriques (publique et privée) ;
- Ils s’échangent leurs clés publiques ;
- Alice chiffre son message avec sa clé privée puis avec la clé publique de Bob ;
- Alice envoie alors le message chiffré à Bob ;
- Bob reçoit le message chiffré d’Alice ;
- Bob va alors déchiffrer le message avec sa clé privée, puis avec la clé publique d’Alice.
Grâce à ce double chiffrement :
- Le message n’est déchiffrable que par Bob ;
- Même Alice ne peut pas déchiffrer le message qu’elle a elle-même chiffré ;
- Bob peut s’assurer que ce soit Alice qui a émis le message.
Pour réaliser ceci, il va falloir exécuter les étapes suivantes :
Sur l’ordinateur d’Alice (en général, un serveur) :
// Cette clé est à garder secrète
$aliceKey = sodium_crypto_box_keypair();
$aliceKeySecret = sodium_crypto_box_secretkey($aliceKey);
// Cette clé est diffusée à Bob
$aliceKeyPublic = sodium_crypto_box_publickey($aliceKey);
Sur l’ordinateur de Bob (en général, en Javascript dans le navigateur, ou sur un autre serveur dans le cas d’API) :
// Cette clé est à garder secrète
$bobKey = sodium_crypto_box_keypair();
$bobKeySecret = sodium_crypto_box_secretkey($bobKey);
// Cette clé est diffusée à Alice
$bobKeyPublic = sodium_crypto_box_publickey($bobKey);
Alice va chiffrer son message et l’envoyer à Bob :
$message = 'hello';
$nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
$encryptionKey = sodium_crypto_box_keypair_from_secretkey_and_publickey($aliceKeySecret, $bobKeyPublic);
$encrypted = sodium_crypto_box($message, $nonce, $encryptionKey);
Bob n’a plus qu’à déchiffrer le message d’Alice :
$decryptionKey = sodium_crypto_box_keypair_from_secretkey_and_publickey($bobKeySecret, $aliceKeyPublic);
$decrypted = sodium_crypto_box_open($encrypted, $nonce, $decryptionKey);
Section intitulée utilitairesUtilitaires
libsodium expose aussi quelques utilitaires qui sont plus sécurisés que les fonctions natives de PHP. En effet, libsodium est moins sensible au Side Channel Attack :
-
sodium_bin2hex()
/sodium_hex2bin()
: Conversion binaire <-> hexa ; -
sodium_bin2base64()
/sodium_base642bin()
: Conversion binaire <-> base64 ; -
sodium_increment()
: Incrémente un nombre (très grand); -
sodium_compare()
/sodium_memcmp()
: Compare deux valeurs en temps constant. Cependant depuis PHP 5.6hash_equals()
est suffisant ; -
sodium_pad()
/sodium_unpad()
: Permet de compléter une string par des 0 pour obtenir une taille fixe. Cela permet d’éviter à un attaquant de trouver la taille du message. -
sodium_memzero()
: Permet de supprimer la valeur d’une variable de la mémoire.
Cependant, ne vous inquiétez pas, les fonctions de base de PHP sont très bien. PHP, de part sa nature (langage pour faire du web), est peu sensible aux SCA.
Section intitulée conclusionConclusion
L’extension libsodium est native à PHP depuis la 7.2. Elle est à connaître dès que l’on veut améliorer la sécurité de son application. Son API est simple et ne nécessite pas de grosses connaissances en cryptographie. Cependant, elle est très mal documentée sur php.net. Il ne faut pas hésiter à aller voir la documentation de la lib C sous-jacente qui est assez complète et pleine d’exemples.
Enfin, Paragonie, une entreprise spécialisée dans la sécurité informatique, a publié halite, une surcouche à libsodium. Son but est de simplifier l’utilisation de libsodium, de mettre en avant les bonnes pratiques, et d’ajouter encore une couche de sécurité.
Commentaires et discussions
What libsodium can do for you? An Introduction to Cryptography in PHP
(Read the 🇫🇷 version here) Cryptography or cryptology is the practice and study of techniques for secure communication in the presence of third parties called adversaries. More generally, cryptography is about constructing and analyzing protocols that prevent third parties or the…
Lire la suite de l’article What libsodium can do for you? An Introduction to Cryptography in PHP
Nos articles sur le même sujet
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !
Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise
Dans le cadre d’une refonte complète de son architecture Web, Expertissim a sollicité l’expertise de JoliCode afin de tenir les délais et le niveau de qualité attendus. Le domaine métier d’Expertissim n’est pas trivial : les spécificités du marché de l’art apportent une logique métier bien particulière et un processus complexe. La plateforme propose…
L’équipe de Finarta a fait appel à JoliCode pour le développement de leur plateforme Web. Basée sur le framework Symfony 2, l’application est un réseau privé de galerie et se veut être une place de communication et de vente d’oeuvres d’art entre ses membres. Pour cela, de nombreuses règles de droits ont été mises en places et une administration poussée…
Axiatel à fait appel à JoliCode afin d’évaluer les développements effectués par la société. A l’issue d’un audit court, nous avons présenté un bilan de la situation actuelle, une architecture applicative cible, et nos recommandations en vue d’une migration en douceur vers le modèle applicatif présenté.