Génération sécurisée de mots de passe en JavaScript : crypto.getRandomValues() vs Math.random()

  • 19 septembre 2025
  • 5 min. à lire

Guide technique pour implémenter une génération cryptographiquement sécurisée de mots de passe en JavaScript côté client. Comparaison entre Math.random() et l'API Web Crypto avec exemples pratiques.

La génération de mots de passe sécurisés en JavaScript est un défi technique fondamental, surtout quand on développe des outils côté client. Beaucoup de développeurs utilisent encore Math.random() par réflexe, sans réaliser qu’ils compromettent fondamentalement la sécurité de leurs applications. Dans cet insight, nous allons explorer pourquoi cette approche est dangereuse et comment implémenter une solution cryptographiquement robuste.

Le problème fondamental de Math.random()

Pourquoi Math.random() est-il dangereux ?

Math.random() utilise un générateur pseudo-aléatoire déterministe (PRNG). Cela signifie que :

  1. Prédictibilité : Avec la même graine (seed), la séquence de nombres sera identique
  2. Période finie : La séquence se répète après un certain nombre d’itérations
  3. Pas cryptographiquement sécurisé : Un attaquant peut potentiellement prédire les valeurs suivantes
// ❌ DANGEREUX : Génération non sécurisée
function generateWeakPassword(length) {
    const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
    let password = '';

    for (let i = 0; i < length; i++) {
        // Math.random() est prévisible !
        const randomIndex = Math.floor(Math.random() * charset.length);
        password += charset[randomIndex];
    }

    return password;
}

Insight : Un mot de passe généré avec Math.random() peut sembler aléatoire à l’œil nu, mais il est potentiellement prédictible par un attaquant sophistiqué qui connaît l’état interne du générateur.

La solution : l’API Web Crypto

crypto.getRandomValues() : l’entropie cryptographique

L’API Web Crypto fournit crypto.getRandomValues(), qui utilise des sources d’entropie cryptographiquement sécurisées du système d’exploitation :

// ✅ SÉCURISÉ : Génération cryptographiquement robuste
function generateSecurePassword(length) {
    const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
    let password = '';

    for (let i = 0; i < length; i++) {
        const randomIndex = getSecureRandomInt(charset.length);
        password += charset[randomIndex];
    }

    return password;
}

function getSecureRandomInt(max) {
    // Utilise l'API Web Crypto pour une génération cryptographiquement sécurisée
    const array = new Uint32Array(1);
    crypto.getRandomValues(array);

    // Évite le biais modulo avec rejection sampling
    const maxValidValue = Math.floor(0xFFFFFFFF / max) * max;
    let randomValue = array[0];

    while (randomValue >= maxValidValue) {
        crypto.getRandomValues(array);
        randomValue = array[0];
    }

    return randomValue % max;
}

Le problème du biais modulo

Pourquoi le simple modulo est-il problématique ?

Même avec crypto.getRandomValues(), une implémentation naïve peut introduire un biais statistique :

// ❌ PROBLÉMATIQUE : Biais de distribution
function biasedRandom(max) {
    const array = new Uint32Array(1);
    crypto.getRandomValues(array);
    return array[0] % max; // Crée un biais !
}

Explication du biais : Si 2^32 n’est pas divisible par max, certaines valeurs apparaîtront plus souvent que d’autres.

Solution : Rejection Sampling

Notre implémentation utilise le rejection sampling pour garantir une distribution parfaitement uniforme :

function getSecureRandomInt(max) {
    const array = new Uint32Array(1);
    crypto.getRandomValues(array);

    // Calcule la valeur maximale valide pour éviter le biais
    const maxValidValue = Math.floor(0xFFFFFFFF / max) * max;
    let randomValue = array[0];

    // Rejette et régénère si la valeur créerait un biais
    while (randomValue >= maxValidValue) {
        crypto.getRandomValues(array);
        randomValue = array[0];
    }

    return randomValue % max;
}

Implémentation complète d’un générateur sécurisé

Voici une classe complète pour générer des mots de passe cryptographiquement sécurisés (similaire à celle utilisée dans notre générateur en ligne) :

class SecurePasswordGenerator {
    constructor() {
        this.lowercase = 'abcdefghijklmnopqrstuvwxyz';
        this.uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        this.numbers = '0123456789';
        this.symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
        this.similarChars = '0Ol1I';
    }

    generatePassword(options = {}) {
        const {
            length = 16,
            includeUppercase = true,
            includeLowercase = true,
            includeNumbers = true,
            includeSymbols = true,
            excludeSimilar = false
        } = options;

        const charset = this.buildCharset({
            includeUppercase,
            includeLowercase,
            includeNumbers,
            includeSymbols,
            excludeSimilar
        });

        if (charset.length === 0) {
            throw new Error('Au moins un type de caractère doit être sélectionné');
        }

        let password = '';
        for (let i = 0; i < length; i++) {
            const randomIndex = this.getSecureRandomInt(charset.length);
            password += charset[randomIndex];
        }

        return password;
    }

    buildCharset(options) {
        let charset = '';

        if (options.includeLowercase) charset += this.lowercase;
        if (options.includeUppercase) charset += this.uppercase;
        if (options.includeNumbers) charset += this.numbers;
        if (options.includeSymbols) charset += this.symbols;

        // Exclure les caractères similaires si demandé
        if (options.excludeSimilar) {
            for (let char of this.similarChars) {
                charset = charset.replace(new RegExp(char, 'g'), '');
            }
        }

        return charset;
    }

    getSecureRandomInt(max) {
        const array = new Uint32Array(1);
        crypto.getRandomValues(array);

        const maxValidValue = Math.floor(0xFFFFFFFF / max) * max;
        let randomValue = array[0];

        while (randomValue >= maxValidValue) {
            crypto.getRandomValues(array);
            randomValue = array[0];
        }

        return randomValue % max;
    }

    // Calcule la force du mot de passe
    calculateStrength(password) {
        if (!password) return { score: 0, label: 'Aucun' };

        let score = 0;
        const checks = {
            length: password.length >= 12,
            lowercase: /[a-z]/.test(password),
            uppercase: /[A-Z]/.test(password),
            numbers: /\d/.test(password),
            symbols: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
        };

        score = Object.values(checks).filter(Boolean).length;

        const levels = [
            { score: 0, label: 'Très faible', class: 'danger' },
            { score: 1, label: 'Faible', class: 'warning' },
            { score: 2, label: 'Moyen', class: 'info' },
            { score: 3, label: 'Bon', class: 'primary' },
            { score: 4, label: 'Fort', class: 'success' },
            { score: 5, label: 'Très fort', class: 'success' }
        ];

        return levels[score] || levels[0];
    }
}

// Utilisation
const generator = new SecurePasswordGenerator();

// Mot de passe sécurisé standard
const password = generator.generatePassword({
    length: 16,
    includeSymbols: true,
    excludeSimilar: true
});

console.log('Mot de passe généré :', password);
console.log('Force :', generator.calculateStrength(password));

Considérations de compatibilité

Support navigateur

L’API Web Crypto est largement supportée :

  • Chrome/Edge : Depuis la version 37
  • Firefox : Depuis la version 34
  • Safari : Depuis la version 7
  • Contexte sécurisé requis : HTTPS ou localhost

Fallback pour les anciens navigateurs

function isWebCryptoSupported() {
    return typeof crypto !== 'undefined' &&
           typeof crypto.getRandomValues === 'function';
}

function generatePassword(length) {
    if (!isWebCryptoSupported()) {
        throw new Error('Web Crypto API non supportée. Impossible de générer un mot de passe sécurisé.');
        // Plutôt que de compromettre la sécurité avec Math.random()
    }

    return generateSecurePassword(length);
}

Important : Dans certains cas, il vaut mieux refuser de générer un mot de passe plutôt que d’en proposer un non sécurisé. Une fausse impression de sécurité peut être plus dangereuse qu’une absence de fonctionnalité.

Bonnes pratiques et recommandations

1. Validation côté serveur

Insight : Même avec une génération sécurisée côté client, validez toujours la complexité des mots de passe côté serveur. Le client peut être compromis ou contourné.

2. Gestion mémoire

// Nettoyage sécurisé (limité en JavaScript)
function secureCleanup(sensitiveString) {
    // JavaScript ne permet pas un nettoyage mémoire garanti
    // Mais on peut au moins "surcharger" la variable
    sensitiveString = null;

    // Forcer le garbage collector (pas garanti)
    if (typeof gc === 'function') {
        gc();
    }
}

3. Audit et logging

class AuditablePasswordGenerator extends SecurePasswordGenerator {
    generatePassword(options = {}) {
        const startTime = performance.now();
        const password = super.generatePassword(options);
        const endTime = performance.now();

        // Log anonyme des métriques (sans le mot de passe !)
        console.log(`Génération: ${endTime - startTime}ms, Longueur: ${password.length}`);

        return password;
    }
}

Conclusion

La génération sécurisée de mots de passe en JavaScript nécessite une approche rigoureuse. L’utilisation de crypto.getRandomValues() avec rejection sampling garantit une entropie cryptographiquement sécurisée et une distribution uniforme. Cette implémentation, combinée à une validation côté serveur, offre une base solide pour développer des outils de sécurité robustes.

Points clés à retenir :

  • Math.random() n’est jamais approprié pour la cryptographie
  • crypto.getRandomValues() fournit une entropie cryptographiquement sécurisée
  • Le rejection sampling élimine le biais de distribution
  • La validation côté serveur reste indispensable
  • Un contexte HTTPS est requis pour l’API Web Crypto

En appliquant ces principes, vous pouvez développer des générateurs de mots de passe qui respectent les standards de sécurité modernes tout en restant performants et compatibles avec les navigateurs actuels.

Mise en pratique

Nous avons implémenté ces techniques dans notre générateur de mots de passe sécurisé, qui utilise crypto.getRandomValues() avec rejection sampling pour garantir une sécurité optimale. L’outil est entièrement gratuit, fonctionne offline et ne transmet aucune donnée.

Nicolas Verlhiac

Nicolas Verlhiac

Full stack software expert | E-commerce & CRM

Nous sommes spécialisés dans la création de solutions technologiques innovantes qui aident les entreprises à rester compétitives et à prospérer.

tracking-thumb