Table of Contents
Les applications Web modernes traitent de nombreuses données sensibles : identifiants personnels, transactions financières, dossiers de santé et informations commerciales exclusives. Sans un cryptage adéquat, ces données sont vulnérables à l'interception, à la modification et au vol pendant la transmission. JavaScript, longtemps confiné aux interactions basées sur le navigateur, offre maintenant des outils matures et prêts à la production pour mettre en œuvre un cryptage solide et sécuriser les canaux de communication.
Comprendre les principes fondamentaux du chiffrement des données
Le chiffrement est le processus de transformation du texte clair lisible en un chiffrement illisible à l'aide d'un algorithme et d'une clé secrète. Seules les parties autorisées possédant la clé de déchiffrement correcte peuvent inverser la transformation et récupérer les données originales. La force d'un système de chiffrement dépend des propriétés mathématiques de l'algorithme, de la longueur et du caractère aléatoire de la clé, et de la sécurité des pratiques de gestion des clés.
La cryptographie distingue deux grandes catégories de chiffrement :
- Cryptage symétrique[ — utilise une seule clé partagée pour le cryptage et le déchiffrement. Elle est rapide et efficace pour les données en vrac, mais la clé doit être échangée en toute sécurité entre les parties.
- Cryptage asymétrique — utilise une paire de clés publiques privées. La clé publique chiffre les données; seule la clé privée correspondante peut la déchiffrer. Cela élimine le besoin d'un secret partagé mais est plus lent au calcul. Exemples: RSA, ECC (Cryptographie de courbe elliptique).
De plus, hashing (p. ex., SHA-256) est une fonction unidirectionnelle qui produit un digest de taille fixe à partir de données d'entrée. Il n'est pas de chiffrement (vous ne pouvez pas l'inverser), mais il est essentiel pour les vérifications d'intégrité, le stockage de mots de passe et les signatures numériques. HMAC (Code d'authentification des messages basé sur la Hash) combine une clé secrète avec un hachage pour vérifier l'intégrité et l'authenticité des données.
Chiffrement dans le navigateur vs. Node.js
JavaScript fonctionne dans deux environnements primaires : le navigateur et Node.js (sur le côté serveur). Chaque environnement fournit différentes interfaces natives :
- Browser — L'API Web Crypto[ est l'interface standard, cryptographiquement sonore. Elle prend en charge AES‐CBC, AES‐GCM, RSA‐OAEP, ECDH, et bien d'autres opérations. L'API est conçue pour être sécurisée par défaut, en utilisant le fournisseur de cryptographie natif du système plutôt que les algorithmes mis en œuvre par JavaScript.
- Node.js — Le module crypto intégré offre un large éventail de fonctions cryptographiques, y compris des primitives de bas niveau et des classes de haut niveau comme , , et . Il prend en charge les chiffrements de flux, HKDF et PBKDF2 pour la dérivation des clés.
Des bibliothèques tierces telles que CryptoJS, libsodium.js et Forge peuvent compléter les API natives, mais les développeurs devraient préférer les implémentations natives chaque fois que possible pour éviter les algorithmes mal implémentés ou dépréciés.
Algorithmes de chiffrement communs en JavaScript
AES (Norme de chiffrement avancée)
AES est le chiffre symétrique de facto utilisé dans le monde entier. Il fonctionne sur des blocs 128 bits et prend en charge des tailles clés de 128, 192 ou 256 bits. Les modes les plus courants pour JavaScript sont:
- AES‐GCM (Galois/Mode Counter) — fournit un chiffrement authentifié; il chiffre et produit une étiquette d'authentification qui détecte les manipulations.
- AES‐CBC (Cipher Block Chaining) — nécessite un vecteur d'initialisation (IV) et un rembourrage. Il ne fournit pas d'intégrité en soi, de sorte qu'il devrait être combiné avec HMAC ou utilisé dans un protocole qui ajoute l'authentification.
Exemple utilisant l'API Web Crypto (navigateur) pour chiffrer avec AES‐GCM :
async function encryptAESGCM(data, key) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(data);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoded
);
return { iv, ciphertext: new Uint8Array(encrypted) };
}
// Generate a 256‑bit AES key
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
RSA (Rivest–Shamir–Adleman)
RSA est un algorithme asymétrique couramment utilisé pour l'échange de clés et les signatures numériques. En JavaScript, il est disponible via l'API Web Crypto (browser) et le module Node.js . RSA‐OAEP (Optimal Asymétrique Encryption Padding) est le schéma de chiffrement recommandé, car il inclut un rembourrage aléatoire pour protéger contre les attaques de texteplaqué choisies.
RSA est beaucoup plus lent que le chiffrement symétrique et est limité dans la taille des données qu'il peut chiffrer (proportionnellement à la taille de la clé).Dans la pratique, RSA est utilisé pour chiffrer une clé AES (cryptage hybride), puis AES chiffre la charge utile réelle.
SHA-256 / SHA-3 (Algorithmes de la sécurité de la hache)
Les algorithmes de hashing ne sont pas des cryptages, mais ils sont indispensables pour une communication sécurisée. L'API Web Crypto fournit pour les systèmes SHA‐1, SHA‐256, SHA‐384 et SHA‐512. Notez que SHA‐1 est déprécié en raison d'attaques de collision.
Cryptographie par courbure elliptique (ECC)
ECC offre une sécurité équivalente à RSA avec des clés beaucoup plus courtes, ce qui le rend idéal pour les applications mobiles et haute performance. L'API Web Crypto prend en charge ECDH (Elliptic Curve Diffie‐Hellman) pour l'accord clé et ECDSA (Elliptic Curve Digital Signature Algorithm) pour la signature.
Mise en œuvre du chiffrement en JavaScript : un guide pratique
Voici des exemples complets, copier et coller pour le navigateur et Node.js. Toujours préférer le module Web Crypto API ou Node.js aux bibliothèques tierces lorsque c'est possible, car elles sont vérifiées et effectuent des opérations cryptographiques dans la mémoire sécurisée du système.
Navigateur : Chiffrement symétrique avec AES‐GCM (API Web Crypto)
// Encrypt a message with a given password
async function encryptWithPassword(password, plaintext) {
// Derive a key from the password using PBKDF2
const encoder = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(16));
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 600000, // OWASP recommended min
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
// Encrypt
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoder.encode(plaintext)
);
// Return salt, iv, ciphertext as base64 for transport
return {
salt: btoa(String.fromCharCode(...salt)),
iv: btoa(String.fromCharCode(...iv)),
ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted)))
};
}
// Decrypt
async function decryptWithPassword(password, { salt, iv, ciphertext }) {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const saltBytes = Uint8Array.from(atob(salt), c => c.charCodeAt(0));
const ivBytes = Uint8Array.from(atob(iv), c => c.charCodeAt(0));
const data = Uint8Array.from(atob(ciphertext), c => c.charCodeAt(0));
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: saltBytes,
iterations: 600000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: ivBytes },
key,
data
);
return decoder.decode(decrypted);
}
Node.js: AES-256-GCM avec le module crypto
const crypto = require('crypto');
function encrypt(text, key) {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString('hex');
return { iv: iv.toString('hex'), encrypted, authTag };
}
function decrypt(encryptedData, key) {
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Usage: generate a 256‑bit key
const key = crypto.randomBytes(32);
const message = 'This is secret.';
const ciphertext = encrypt(message, key);
console.log(ciphertext);
console.log('Decrypted:', decrypt(ciphertext, key));
Pratiques exemplaires en matière de communication sécurisée
Le chiffrement ne garantit pas à lui seul une communication sûre. Les pratiques suivantes doivent être intégrées dans l'ensemble du flux de données:
1. Toujours utiliser HTTPS avec TLS 1.2 ou supérieur
HTTPS n'est pas optionnel. Il crypte toute la conversation HTTP, empêchant les écoutes et les attaques man-in-the-middle. Configurez votre serveur pour utiliser TLS 1.2 ou TLS 1.3 et désactivez les suites de chiffrement faibles. Des services comme Cloudflare fournissent gratuitement la terminaison et la gestion des certificats TLS. Pour les serveurs auto- hébergeurs, utilisez des outils tels que Let=S Encrypt pour obtenir des certificats automatisés.
2. Mettre en oeuvre une gestion clé appropriée
- N'utilisez jamais de touches de code dur dans le code client. Utilisez des variables d'environnement ou des voûtes sécurisées (p. ex., HashiCorp Vault, AWS Secrets Manager) sur le serveur.
- Rotation des clés régulièrement et immédiatement révoquer les clés compromises.
- Utilisez les fonctions de dérivation de clé (PBKDF2, scrypt, Argon2) pour le chiffrement par mot de passe. L'API Web Crypto et Node.js prennent en charge tous les deux PBKDF2.
- Lors de l'échange de clés, utilisez un protocole d'accord sécurisé comme l'ECDH ou le RSA-OAEP, et combinez-le à une étape de confirmation de clé.
3. Authentification et autorisation
Le chiffrement protège les données en transit, mais le système doit également vérifier l'identité. Utilisez l'authentification basée sur les jetons (JWT, OAuth 2.0) et assurez-vous que les jetons ne sont transmis que via HTTPS. Pour les API, demandez un jeton d'accès valide pour chaque requête et implémentez des durées de vie courtes avec des jetons de rafraîchissement.
4. Protéger contre les attaques communes sur le Web
- Cross‐Site Scripting (XSS) – Les attaquants peuvent injecter des scripts qui volent des données ou des clés chiffrées. Utilisez les en-têtes de la politique de sécurité du contenu (CSP), échappez à l'entrée de l'utilisateur et ne jamais faire confiance .
- Cross-Site Request Forgery (CSRF) – Utilisez des jetons anti-CSRF ou des cookies SameSite pour empêcher les requêtes falsifiées.
- Clickjacking – Réglez l'en-tête à ou .
- Homme dans le milieu – Appliquer le TVHS (HTTP Strict Transport Security) et envisager le codage de certificat pour les applications à haute sécurité.
5. Valider toutes les entrées et utiliser des comparaisons temps-constante
Lors de la comparaison des HMAC, des balises d'authentification ou des mots de passe, utilisez toujours une fonction de comparaison à temps constant pour empêcher les attaques de synchronisation. Node.js fournit ; pour le navigateur, vous pouvez implémenter une comparaison à temps constant ou utiliser une bibliothèque comme .
Sujets avancés : Signatures numériques, chiffrement hybride et pincement de certificat
Signatures numériques
Les signatures numériques fournissent une non-répudiation et prouvent l'authenticité d'un message. Utilisez ECDSA (avec courbe P-256) ou RSA‐PSS. Dans le navigateur, l'API Crypto Web prend en charge et opérations. Exemple d'extrait de code (navigateur):
// Generate signing key pair
const keyPair = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign', 'verify']
);
// Sign a message
const data = new TextEncoder().encode('Important contract');
const signature = await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-256' },
keyPair.privateKey,
data
);
Chiffrement hybride pour les charges utiles importantes
Le chiffrement asymétrique ne peut pas gérer efficacement les grandes données. L'approche standard est le chiffrement hybride :
- Générer une clé symétrique aléatoire (p. ex., SAE 256 bits).
- Chiffrer la clé symétrique avec la clé publique RSA du destinataire (RSA‐OAEP).
- Chiffrer le message réel avec AES‐GCM en utilisant la clé symétrique.
- Transmettre à la fois la clé symétrique chiffrée et le code (avec la balise IV et auth).
Le destinataire déchiffre la clé symétrique avec sa clé privée, puis déchiffre le message avec AES‐GCM.
Pinnage du certificat
Pour mieux protéger les autorités de certification compromises, vous pouvez épingler le certificat de serveur prévu ou son hash à clé publique. Cela peut être fait en utilisant l'en-tête (déprécié mais encore utilisé dans certains contextes) ou via l'en-tête (Transparence de certificat).
Pièges courants et comment les éviter
- Utilisation d'une IV ou d'un non-ce faible – La IV doit être aléatoire et unique pour chaque opération de chiffrement avec la même clé. La réutilisation d'une IV avec AES‐GCM ou AES‐CTR brise complètement la sécurité.
- Mise en œuvre de la cryptographie personnalisée – N'écrivez jamais votre propre algorithme de chiffrement. Utilisez des implémentations standard, bien contrôlées depuis la plate-forme ou des bibliothèques de confiance.
- Storing keys in cookies or local stockage – Le stockage par le navigateur est accessible à JavaScript et donc aux attaques XSS. Pour le cryptage côté client, envisagez d'utiliser l'API Web Crypto pour générer des clés qui ne sont jamais exposées à la mémoire JavaScript.
- L'authentification en cours – Le chiffrement sans étiquette d'authentification (p. ex., en utilisant AES‐CBC sans HMAC) permet aux attaquants de modifier le code de caractères non détecté.
- Ignorer les attaques de canaux latéraux – Dans les environnements de haute sécurité, être conscient des attaques de temps, de cache et d'analyse de puissance.
Conclusion
JavaScript est pleinement capable de mettre en œuvre un cryptage robuste et de sécuriser la communication lorsqu'il est utilisé correctement. Le module Web Crypto API et Node.js fournit des fonctions cryptographiques de qualité de production, de plate-forme et de qualité qui respectent les normes de l'industrie. En combinant le cryptage symétrique et asymétrique avec les meilleures pratiques telles que HTTPS, la gestion des clés appropriée, les comparaisons à temps constant et la validation d'entrée, vous pouvez construire des applications Web qui protègent les données des utilisateurs contre les vecteurs de menaces multiples.