Dans un monde où nos données circulent constamment entre serveurs, navigateurs et applications, la sécurité des échanges est devenue une priorité. Pourtant, peu de développeurs débutants comprennent vraiment ce que signifie « chiffrer un message ». L’objectif de ce chapitre est simple : vous apprendre à créer pas à pas une mini-application de messagerie chiffrée, en PHP et JavaScript, sans connaissance préalable en cryptographie.
Vous allez apprendre à protéger les messages que vos utilisateurs s’envoient, grâce à des techniques simples de cryptage et décryptage. À la fin, vous disposerez d’un petit système de messagerie web fonctionnel, où chaque message sera illisible pour toute personne non autorisée, même si elle accède à la base de données.
Ce chapitre s’inscrit dans le grand tutoriel du Créa-Blog sur la cryptographie, et constitue une étape pratique pour mettre en œuvre les notions vues précédemment.
- Qu’est-ce qu’une messagerie chiffrée ?
- Le principe de chiffrement : clé symétrique ou asymétrique
- Mise en place du projet
- Le formulaire d’envoi et d’affichage (index.php)
- Le chiffrement côté client (JavaScript)
- Envoi du message vers le serveur
- Côté serveur : réception et stockage du message
- Mise en place d’un chiffrement sécurisé avec AES
- Comprendre le chiffrement AES
- Génération d’une clé AES
- Chiffrer un message avec AES-GCM
- Déchiffrer un message chiffré
- Adaptation du code existant
- Le côté PHP reste identique
- Limites du chiffrement côté client
- Chiffrer côté serveur (option avancée)
- Améliorer la sécurité globale
- L’échange de clés et le chiffrement de bout en bout
- Générer un couple de clés RSA en JavaScript
- Exporter et stocker les clés
- Enregistrement de la clé publique côté serveur
- Envoi d’un message chiffré à un destinataire
- Réception et déchiffrement du message
- Gestion pratique des utilisateurs
- Test complet du flux sécurisé
- Ce que vous venez de créer
Qu’est-ce qu’une messagerie chiffrée ?
Avant de plonger dans le code, il est essentiel de comprendre le concept. Une messagerie chiffrée est une application qui transforme les messages lisibles (appelés messages en clair) en une suite de caractères incompréhensibles (appelés messages chiffrés) avant de les envoyer ou de les stocker.
L’objectif est simple : seul le destinataire prévu doit pouvoir lire le message. Même si un pirate accède à la base de données ou intercepte la communication, il ne pourra rien comprendre sans la clé de déchiffrement.
Pour mieux visualiser, imaginez :
Vous envoyez le message « Bonjour ».
Après chiffrement, la base de données enregistre : « X9df!a$2@#8z ».
Quand le destinataire ouvre la conversation, le script déchiffre ce message et réaffiche « Bonjour ».
Ce processus repose sur deux actions fondamentales :
- Le chiffrement : transformer le texte en clair en texte chiffré.
- Le déchiffrement : faire l’opération inverse à l’aide d’une clé.
Le principe de chiffrement : clé symétrique ou asymétrique
Il existe deux grandes familles de chiffrement :
- Le chiffrement symétrique : la même clé sert à chiffrer et déchiffrer.
Exemple : l’algorithme AES. - Le chiffrement asymétrique : deux clés distinctes, une publique (pour chiffrer) et une privée (pour déchiffrer).
Exemple : l’algorithme RSA.
Pour simplifier, nous allons d’abord utiliser le chiffrement symétrique, plus simple à comprendre et à implémenter.
Mise en place du projet
Nous allons créer une petite application web structurée comme suit :
messagerie_chiffree/
│
├── index.php → Page principale avec le formulaire et l’affichage des messages
├── send_message.php → Script pour chiffrer et enregistrer un message
├── get_messages.php → Script pour récupérer et déchiffrer les messages
├── assets/
│ ├── script.js → JavaScript pour l’envoi et la récupération des messages
│ └── style.css → Feuille de style
└── db.php → Connexion à la base de donnéesBase de données
Créez une base de données messagerie et une table messages :
CREATE TABLE messages (
id INT AUTO_INCREMENT PRIMARY KEY,
pseudo VARCHAR(100) NOT NULL,
message TEXT NOT NULL,
date_envoi DATETIME DEFAULT CURRENT_TIMESTAMP
);Cette table contiendra les messages chiffrés, pas les versions lisibles.
Le formulaire d’envoi et d’affichage (index.php)
Nous allons commencer par le cœur de la page.
<!-- index.php -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mini messagerie chiffrée</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<h1>Messagerie chiffrée en PHP</h1>
<form id="formMessage">
<input type="text" id="pseudo" placeholder="Votre pseudo" required>
<textarea id="message" placeholder="Votre message" required></textarea>
<button type="submit">Envoyer</button>
</form>
<div id="messages"></div>
<script src="assets/script.js"></script>
</body>
</html>Ce code crée une interface très simple : un champ pour le pseudo, une zone de texte pour le message, et une zone d’affichage des messages reçus. Le JavaScript s’occupera de chiffrer le texte avant de l’envoyer au serveur.
Le chiffrement côté client (JavaScript)
Pour commencer doucement, utilisons une méthode de chiffrement simple avec l’API Web Crypto intégrée à JavaScript. Cette API est moderne, sécurisée et disponible sur tous les navigateurs récents.
Nous allons générer une clé secrète en mémoire, utilisée pour chiffrer et déchiffrer les messages dans la session. L’objectif ici n’est pas de faire une messagerie mondiale, mais de comprendre les bases du chiffrement local avant de passer au côté serveur.
// assets/script.js
const secretKey = "ma_cle_super_secrete"; // clé de test (à améliorer plus tard)
// Fonction simple de chiffrement (exemple pédagogique)
function chiffrer(message, cle) {
let resultat = "";
for (let i = 0; i < message.length; i++) {
resultat += String.fromCharCode(message.charCodeAt(i) ^ cle.charCodeAt(i % cle.length));
}
return btoa(resultat); // encodage base64
}
// Fonction inverse pour le déchiffrement
function dechiffrer(texteChiffre, cle) {
const data = atob(texteChiffre);
let resultat = "";
for (let i = 0; i < data.length; i++) {
resultat += String.fromCharCode(data.charCodeAt(i) ^ cle.charCodeAt(i % cle.length));
}
return resultat;
}Ce chiffrement XOR est très basique et n’est pas sécurisé dans un contexte réel, mais il permet de comprendre le concept. Dans la deuxième partie, nous remplacerons cette méthode par un vrai algorithme de chiffrement (AES).
Envoi du message vers le serveur
Nous allons maintenant intercepter le formulaire, chiffrer le message et l’envoyer à PHP via une requête AJAX.
// Suite de assets/script.js
const form = document.getElementById("formMessage");
const messagesDiv = document.getElementById("messages");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const pseudo = document.getElementById("pseudo").value;
const message = document.getElementById("message").value;
const messageChiffre = chiffrer(message, secretKey);
const response = await fetch("send_message.php", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `pseudo=${encodeURIComponent(pseudo)}&message=${encodeURIComponent(messageChiffre)}`
});
document.getElementById("message").value = "";
chargerMessages();
});
async function chargerMessages() {
const response = await fetch("get_messages.php");
const data = await response.json();
messagesDiv.innerHTML = "";
data.forEach(msg => {
const texte = dechiffrer(msg.message, secretKey);
messagesDiv.innerHTML += `<p><strong>${msg.pseudo} :</strong> ${texte}</p>`;
});
}
// Chargement automatique des messages toutes les 2 secondes
setInterval(chargerMessages, 2000);Ici, chaque message est chiffré avant d’être envoyé au serveur. Même si vous regardez la base de données, vous ne verrez que du texte illisible.
Côté serveur : réception et stockage du message
Créons maintenant le script send_message.php :
<?php
require_once "db.php";
$pseudo = $_POST['pseudo'] ?? '';
$message = $_POST['message'] ?? '';
if (!empty($pseudo) && !empty($message)) {
$stmt = $pdo->prepare("INSERT INTO messages (pseudo, message) VALUES (:pseudo, :message)");
$stmt->execute([
':pseudo' => htmlspecialchars($pseudo),
':message' => htmlspecialchars($message)
]);
}Et le fichier get_messages.php :
<?php
require_once "db.php";
$stmt = $pdo->query("SELECT pseudo, message FROM messages ORDER BY date_envoi DESC LIMIT 20");
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
header('Content-Type: application/json');
echo json_encode($messages);Enfin, la connexion db.php :
<?php
$pdo = new PDO("mysql:host=localhost;dbname=messagerie;charset=utf8", "root", "", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);Et voilà !
Vous venez de créer la base d’une messagerie chiffrée en PHP et JavaScript.
Elle n’est pas encore totalement sécurisée, mais elle pose les fondations d’un système où les données ne circulent jamais en clair.
Mise en place d’un chiffrement sécurisé avec AES
Dans la première partie, vous avez découvert les bases d’une messagerie chiffrée. Vous avez compris ce qu’est le chiffrement, mis en place un petit système PHP/JS et expérimenté un algorithme très simple pour visualiser le fonctionnement global.
Cependant, vous avez probablement remarqué une faiblesse majeure : notre chiffrement était purement pédagogique, donc facile à casser.
Dans cette deuxième partie, nous allons franchir une étape essentielle : utiliser un vrai algorithme de chiffrement moderne et sécurisé. Pour cela, nous allons introduire AES (Advanced Encryption Standard). Cet algorithme est utilisé dans de nombreux systèmes professionnels, des banques aux applications de messagerie comme Signal ou WhatsApp.
L’objectif est de rendre votre mini-messagerie réellement sécurisée, tout en conservant une approche simple et accessible à tous.
Comprendre le chiffrement AES
L’algorithme AES est un chiffrement symétrique. Cela signifie qu’une seule clé secrète permet à la fois de chiffrer et de déchiffrer les messages. Si quelqu’un découvre cette clé, il pourra tout lire — c’est pourquoi sa protection est primordiale.
En pratique, AES fonctionne en transformant les données (votre message) en blocs mathématiques manipulés à l’aide de cette clé. Le résultat est un texte totalement illisible.
Par exemple :
Message : Bonjour
Clé : 8hGk29Pl…
Message chiffré (AES-256) : bZ91nC1…U9p=
Personne ne peut retrouver “Bonjour” sans posséder la clé exacte.
Génération d’une clé AES
Pour simplifier notre apprentissage, nous allons générer une clé en JavaScript, à l’aide de l’API Web Crypto, puis l’utiliser pour chiffrer et déchiffrer les messages côté client.
Le serveur PHP, lui, se contentera de stocker les messages chiffrés sans jamais connaître la clé.
Ainsi, même le serveur ne peut pas lire les messages, ce qui est un concept fondamental de la cryptographie moderne : le chiffrement de bout en bout.
Créons une fonction JavaScript pour générer et sauvegarder cette clé dans la session du navigateur :
// assets/script.js
let cryptoKey;
// Génère une clé AES 256 bits et la stocke en mémoire
async function genererCle() {
cryptoKey = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
console.log("Clé AES générée !");
}Cette fonction crée une clé AES-GCM (un mode sécurisé et moderne) de 256 bits.
Nous pourrions la sauvegarder dans le localStorage du navigateur pour la réutiliser plus tard, mais pour le moment, restons simples et gardons-la uniquement en mémoire.
Chiffrer un message avec AES-GCM
Maintenant que nous avons une clé, nous allons l’utiliser pour chiffrer un message.
Le chiffrement AES-GCM nécessite également un IV (Initialisation Vector), une valeur aléatoire générée pour chaque message, afin d’éviter qu’un même message produise toujours le même résultat.
// Fonction de chiffrement AES-GCM
async function chiffrerMessage(message) {
const enc = new TextEncoder();
const data = enc.encode(message);
// Génération d’un IV unique pour chaque message
const iv = crypto.getRandomValues(new Uint8Array(12));
const chiffrement = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv
},
cryptoKey,
data
);
// On concatène IV + message chiffré pour l’envoyer au serveur
const bufferComplet = new Uint8Array(iv.byteLength + chiffrement.byteLength);
bufferComplet.set(iv, 0);
bufferComplet.set(new Uint8Array(chiffrement), iv.byteLength);
return btoa(String.fromCharCode(...bufferComplet));
}Ici, on encode le texte, on le chiffre, puis on combine l’IV et le texte chiffré dans un seul message encodé en base64. Le serveur n’aura besoin que de cette chaîne de caractères pour stocker le message.
Déchiffrer un message chiffré
Pour déchiffrer, nous devons extraire l’IV et le texte chiffré avant de lancer l’opération inverse.
async function dechiffrerMessage(texteChiffre) {
const data = Uint8Array.from(atob(texteChiffre), c => c.charCodeAt(0));
const iv = data.slice(0, 12); // Les 12 premiers octets
const messageChiffre = data.slice(12);
const dechiffrement = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
cryptoKey,
messageChiffre
);
const dec = new TextDecoder();
return dec.decode(dechiffrement);
}Ce code récupère la partie IV et la partie chiffrée, puis utilise la clé pour retrouver le texte d’origine. Résultat : le navigateur de l’expéditeur chiffre le message, celui du destinataire le déchiffre. Le serveur ne voit rien en clair.
Adaptation du code existant
Modifions maintenant la soumission du formulaire pour utiliser AES plutôt que notre chiffrement XOR de la première partie.
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (!cryptoKey) await genererCle();
const pseudo = document.getElementById("pseudo").value;
const message = document.getElementById("message").value;
const messageChiffre = await chiffrerMessage(message);
await fetch("send_message.php", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `pseudo=${encodeURIComponent(pseudo)}&message=${encodeURIComponent(messageChiffre)}`
});
document.getElementById("message").value = "";
chargerMessages();
});Et dans la fonction chargerMessages() :
async function chargerMessages() {
const response = await fetch("get_messages.php");
const data = await response.json();
messagesDiv.innerHTML = "";
for (const msg of data) {
try {
const texte = await dechiffrerMessage(msg.message);
messagesDiv.innerHTML += `<p><strong>${msg.pseudo} :</strong> ${texte}</p>`;
} catch (e) {
messagesDiv.innerHTML += `<p><strong>${msg.pseudo} :</strong> [Message illisible]</p>`;
}
}
}Le côté PHP reste identique
Aucune modification n’est nécessaire côté serveur : le PHP continue simplement de recevoir des chaînes de caractères chiffrées et de les enregistrer. Cependant, pour améliorer la sécurité, vous pouvez nettoyer les entrées plus rigoureusement :

Des formations informatique pour tous !
Débutant ou curieux ? Apprenez le développement web, le référencement, le webmarketing, la bureautique, à maîtriser vos appareils Apple et bien plus encore…
Formateur indépendant, professionnel du web depuis 2006, je vous accompagne pas à pas et en cours particulier, que vous soyez débutant ou que vous souhaitiez progresser. En visio, à votre rythme, et toujours avec pédagogie.
Découvrez mes formations Qui suis-je ?$pseudo = htmlspecialchars($_POST['pseudo'] ?? '');
$message = $_POST['message'] ?? '';
if ($pseudo && $message) {
$stmt = $pdo->prepare("INSERT INTO messages (pseudo, message) VALUES (:pseudo, :message)");
$stmt->execute([
':pseudo' => $pseudo,
':message' => $message
]);
}Le message chiffré n’a pas besoin d’être échappé avec htmlspecialchars() puisqu’il n’est pas interprété comme du HTML. Mais garder une validation basique reste une bonne pratique.
Limites du chiffrement côté client
Ce système, bien que fonctionnel, présente une limite importante : les utilisateurs doivent partager la même clé secrète pour pouvoir lire leurs messages mutuellement.
Dans notre exemple, la clé est générée localement et ne sort jamais du navigateur. Si vous envoyez la page à quelqu’un d’autre, il génèrera une autre clé et ne pourra donc pas lire les anciens messages.
Il existe deux solutions possibles :
- soit utiliser un système d’échange de clé sécurisé (par exemple avec RSA) ;
- soit permettre aux utilisateurs de saisir eux-mêmes une clé commune connue d’avance (par exemple un mot de passe partagé).
Nous aborderons la première solution dans la prochaine partie, car elle permet d’automatiser l’échange sans que les utilisateurs aient à se communiquer la clé manuellement.
Chiffrer côté serveur (option avancée)
Dans certains cas, vous voudrez peut-être chiffrer les messages côté serveur. Par exemple, pour protéger les données même si quelqu’un accède au disque dur.
Voici comment chiffrer en PHP avec OpenSSL :
<?php
$cle = 'ma_cle_secrete_256bits';
$iv = openssl_random_pseudo_bytes(16);
$message = "Bonjour !";
$chiffre = openssl_encrypt($message, 'aes-256-cbc', $cle, 0, $iv);
echo "Message chiffré : " . base64_encode($chiffre) . "\n";
echo "IV : " . base64_encode($iv);Et pour le déchiffrer :
<?php
$cle = 'ma_cle_secrete_256bits';
$chiffre = base64_decode($_POST['message']);
$iv = base64_decode($_POST['iv']);
$message = openssl_decrypt($chiffre, 'aes-256-cbc', $cle, 0, $iv);
echo "Message déchiffré : " . $message;L’idée est la même : vous combinez un message chiffré et un vecteur d’initialisation (IV). Ce type de chiffrement serveur peut être utilisé pour sécuriser des sauvegardes, mais il ne remplace pas un vrai chiffrement de bout en bout.
Améliorer la sécurité globale
Pour que votre messagerie devienne plus robuste, vous pouvez ajouter :
- une authentification des utilisateurs (login/mot de passe) ;
- un stockage chiffré des clés via un mot de passe maître ;
- l’utilisation du HTTPS pour toutes les communications ;
- un nettoyage des entrées plus rigoureux ;
- un hachage des mots de passe avec
password_hash()si vous ajoutez un système de comptes.
Ces améliorations feront de votre application un environnement sécurisé et professionnel.
Vous venez d’apprendre à implémenter un chiffrement réel et sécurisé dans votre application de messagerie. Grâce à AES-GCM, vos messages ne sont plus simplement brouillés, mais réellement protégés. Vous avez franchi un cap vers la compréhension du fonctionnement des messageries modernes et posé les bases du chiffrement de bout en bout.
L’échange de clés et le chiffrement de bout en bout
Dans la partie précédente, nous avons mis en place un chiffrement AES-GCM côté client, garantissant que les messages ne circulent plus jamais en clair. Mais un problème demeure : les utilisateurs doivent partager la même clé secrète, ce qui n’est ni pratique ni sûr.
Imaginez une vraie messagerie : chaque utilisateur doit pouvoir envoyer un message chiffré qu’un seul destinataire peut lire, sans avoir à se partager une clé au préalable. C’est exactement ce que permet la cryptographie asymétrique — un pilier essentiel de la sécurité moderne.
Dans cette troisième partie, vous allez apprendre à associer le chiffrement RSA (asymétrique) au chiffrement AES (symétrique) afin d’obtenir une messagerie de bout en bout :
- chaque utilisateur possède une clé publique (pour chiffrer) et une clé privée (pour déchiffrer) ;
- les messages sont chiffrés avec une clé unique AES, elle-même protégée par RSA ;
- le serveur ne peut toujours pas lire les échanges.
Principe du chiffrement asymétrique
La cryptographie asymétrique repose sur un couple de clés :
- Clé publique : elle peut être partagée librement et sert à chiffrer un message.
- Clé privée : elle reste secrète et sert à déchiffrer.
Ainsi, si Alice envoie un message à Bob :
- Elle récupère la clé publique de Bob.
- Elle chiffre son message avec une clé AES aléatoire.
- Elle chiffre ensuite cette clé AES à l’aide de la clé publique de Bob.
- Bob, de son côté, utilise sa clé privée pour déchiffrer la clé AES, puis le message.
Le serveur n’intervient que pour transporter les données chiffrées.
C’est ce même mécanisme qui protège vos messages sur Signal, ProtonMail ou WhatsApp.
Générer un couple de clés RSA en JavaScript
Nous allons permettre à chaque utilisateur de générer un couple de clés RSA dans son navigateur, au moment où il se connecte.
// assets/script.js
let userKeyPair;
async function genererCleRSA() {
userKeyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
console.log("Clés RSA générées !");
}Cette fonction crée une paire de clés de 2048 bits, un standard solide. Nous utiliserons la clé publique pour chiffrer des données et la clé privée pour les déchiffrer.
Exporter et stocker les clés
Pour que la clé publique soit disponible pour les autres utilisateurs, nous devons pouvoir l’envoyer au serveur et la sauvegarder. Quant à la clé privée, elle doit rester strictement dans le navigateur de l’utilisateur.
Ajoutons deux fonctions pour convertir les clés en chaînes base64 afin de les stocker :
async function exporterClePublique() {
const cleExportee = await crypto.subtle.exportKey("spki", userKeyPair.publicKey);
return btoa(String.fromCharCode(...new Uint8Array(cleExportee)));
}
async function exporterClePrivee() {
const cleExportee = await crypto.subtle.exportKey("pkcs8", userKeyPair.privateKey);
return btoa(String.fromCharCode(...new Uint8Array(cleExportee)));
}Et inversement, pour réimporter une clé publique reçue d’un autre utilisateur :
async function importerClePublique(base64) {
const buffer = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
return await crypto.subtle.importKey(
"spki",
buffer,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
true,
["encrypt"]
);
}Nous avons désormais les outils pour échanger des clés entre utilisateurs.
Enregistrement de la clé publique côté serveur
Dans la table users (ou participants), nous ajoutons une colonne pour stocker la clé publique :
ALTER TABLE users ADD COLUMN cle_publique TEXT;Ensuite, lors de la première connexion, le navigateur enverra la clé publique au serveur :
async function envoyerClePublique(pseudo) {
const clePubliqueBase64 = await exporterClePublique();
await fetch("save_key.php", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `pseudo=${encodeURIComponent(pseudo)}&cle=${encodeURIComponent(clePubliqueBase64)}`
});
}Côté PHP (save_key.php) :
<?php
require_once "db.php";
$pseudo = $_POST['pseudo'] ?? '';
$cle = $_POST['cle'] ?? '';
if ($pseudo && $cle) {
$stmt = $pdo->prepare("UPDATE users SET cle_publique = :cle WHERE pseudo = :pseudo");
$stmt->execute([':cle' => $cle, ':pseudo' => $pseudo]);
}De cette façon, chaque utilisateur a sa clé publique enregistrée sur le serveur, prête à être utilisée par les autres.
Envoi d’un message chiffré à un destinataire
Lorsqu’un utilisateur envoie un message, le navigateur :
- Génére une clé AES aléatoire.
- Chiffre le message avec cette clé AES.
- Récupère la clé publique du destinataire.
- Chiffre la clé AES avec la clé publique RSA du destinataire.
- Envoie le tout au serveur (message chiffré + clé AES chiffrée).
Voici le code correspondant :
async function envoyerMessage(destinataire, texte) {
// Étape 1 : génération de la clé AES
const cleAES = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
// Étape 2 : chiffrement du message
const iv = crypto.getRandomValues(new Uint8Array(12));
const enc = new TextEncoder().encode(texte);
const messageChiffre = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cleAES, enc);
// Étape 3 : récupération de la clé publique du destinataire
const rep = await fetch(`get_public_key.php?pseudo=${encodeURIComponent(destinataire)}`);
const { cle_publique } = await rep.json();
const clePubliqueImportee = await importerClePublique(cle_publique);
// Étape 4 : chiffrement de la clé AES
const cleAESexportee = await crypto.subtle.exportKey("raw", cleAES);
const cleAESchiffree = await crypto.subtle.encrypt(
{ name: "RSA-OAEP" },
clePubliqueImportee,
cleAESexportee
);
// Étape 5 : envoi au serveur
const bufferComplet = new Uint8Array(iv.byteLength + messageChiffre.byteLength);
bufferComplet.set(iv, 0);
bufferComplet.set(new Uint8Array(messageChiffre), iv.byteLength);
await fetch("send_message.php", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body:
`destinataire=${encodeURIComponent(destinataire)}` +
`&message=${encodeURIComponent(btoa(String.fromCharCode(...bufferComplet)))}` +
`&cle=${encodeURIComponent(btoa(String.fromCharCode(...new Uint8Array(cleAESchiffree))))}`
});
}Réception et déchiffrement du message
Quand le destinataire reçoit un message, il doit :
- Déchiffrer la clé AES avec sa clé privée RSA.
- Utiliser cette clé pour déchiffrer le message.
async function dechiffrerMessageRSA(messageBase64, cleAESbase64) {
const data = Uint8Array.from(atob(messageBase64), c => c.charCodeAt(0));
const iv = data.slice(0, 12);
const messageChiffre = data.slice(12);
const cleAESchiffree = Uint8Array.from(atob(cleAESbase64), c => c.charCodeAt(0));
const cleAESbrute = await crypto.subtle.decrypt(
{ name: "RSA-OAEP" },
userKeyPair.privateKey,
cleAESchiffree
);
const cleAES = await crypto.subtle.importKey(
"raw",
cleAESbrute,
"AES-GCM",
false,
["decrypt"]
);
const messageDechiffre = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cleAES, messageChiffre);
return new TextDecoder().decode(messageDechiffre);
}Le processus complet permet enfin une vraie messagerie chiffrée de bout en bout :
- les messages sont chiffrés localement avant l’envoi,
- le serveur ne conserve que des données illisibles,
- et seul le navigateur du destinataire peut les lire.
Gestion pratique des utilisateurs
Vous pouvez ajouter une table users simple dans votre base :
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
pseudo VARCHAR(100) UNIQUE NOT NULL,
cle_publique TEXT NOT NULL
);Puis créer un petit script get_public_key.php :
<?php
require_once "db.php";
$pseudo = $_GET['pseudo'] ?? '';
$stmt = $pdo->prepare("SELECT cle_publique FROM users WHERE pseudo = :pseudo");
$stmt->execute([':pseudo' => $pseudo]);
$cle = $stmt->fetch(PDO::FETCH_ASSOC);
header('Content-Type: application/json');
echo json_encode($cle ?: []);Cela permettra à chaque utilisateur d’obtenir la clé publique du destinataire avant d’envoyer un message.
Test complet du flux sécurisé
Pour tester votre messagerie chiffrée :
- Créez deux utilisateurs, “Alice” et “Bob”.
- Chacun génère sa paire de clés RSA.
- Le navigateur d’Alice envoie sa clé publique au serveur.
- Le navigateur de Bob fait de même.
- Alice écrit un message → il est chiffré avec AES puis RSA.
- Bob reçoit le message, le déchiffre avec sa clé privée RSA, et lit le texte original.
À aucun moment le serveur n’a accès au contenu réel.
Ce que vous venez de créer
Vous venez de réaliser une messagerie chiffrée de bout en bout en PHP et JavaScript, combinant :
- AES-GCM pour le chiffrement rapide et sécurisé des messages,
- RSA-OAEP pour le chiffrement des clés de session,
- une architecture PHP simple servant de passerelle d’échange sans accès aux données sensibles.
C’est exactement la logique utilisée par les messageries les plus sûres du web, mais adaptée à un format pédagogique et compréhensible par tous.
En partant d’un simple script PHP, vous avez appris à construire une application de messagerie chiffrée complète, sécurisée et moderne. Vous comprenez désormais les deux piliers de la cryptographie appliquée au web :
- Le chiffrement symétrique (AES) pour protéger les données efficacement.
- Le chiffrement asymétrique (RSA) pour échanger les clés en toute sécurité.
Ce projet vous a permis de voir comment PHP et JavaScript peuvent collaborer pour créer un système de sécurité puissant sans dépendre d’outils externes. Bien entendu, ce n’est qu’une base : vous pouvez désormais améliorer votre application avec :
- un vrai système d’utilisateurs et d’authentification,
- une interface en temps réel avec WebSocket,
- une sauvegarde chiffrée des clés privées via un mot de passe,
- ou même un chiffrement hybride combinant plusieurs couches de sécurité.
Mais surtout, vous avez franchi une étape essentielle : vous comprenez comment et pourquoi chiffrer les communications web. La prochaine fois que vous lirez “messagerie chiffrée de bout en bout”, vous saurez exactement ce qui se cache derrière ces mots.

