Les mots de passe qui cassent

Bonjour, aujourd'hui, parlons programmation et sécurité.

Comme on le sait tous maintenant, le monde sur internet est régis par les mots de passe. Ces mots de passe sont souvent assez contraignant, et comment retenir plus d'une dizaine de mots de passe différents ? Et pourtant, on est bien obligé de les retenir, pour notre sécurité. Et concrètement, ça donne quoi ?

Je vais réaliser différents code en langage C (ne vous prenez pas trop la tête à comprendre quoi fait quoi dans le code, je vais vous expliquer de manière générale ce que le code fait). Nous traiterons différents cas de figures : un mot de passe avec seulement des chiffres, un mot de passe avec seulement des caractères, un mot de passe avec différents caractères (chiffre, lettre, spécial, etc), et pour finir, un mot du dictionnaire.


C:
/*Importation des librairies*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
/*Définition du mot de passe*/
#define MAX_LENGTH 6
#define TARGET_PASS "123456"

/*Exécution*/
int main() {
    /*Création de la chaine de caractères password*/
    char password[MAX_LENGTH + 1];
    /*Variable booléenne*/
    int found = 0;

    /*Initialisation des variables de temps*/
    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    /*Boucle de recherche du mot de passe*/
    for (int i = 0; i <= 999999 && !found; ++i) {
        snprintf(password, MAX_LENGTH + 1, "%06d", i);
        if (strcmp(password, TARGET_PASS) == 0) {
            found = 1;
            printf("Mot de passe cracké : %s\n", password);
        }
    }

    /*Calcul du temps écoulé et affichage*/
    gettimeofday(&end_time, NULL);
    double total_time = (end_time.tv_sec - start_time.tv_sec) + (double)(end_time.tv_usec - start_time.tv_usec) / 1000;
    printf("Temps écoulé : %.4f millisecondes\n", total_time);

    return 0;
}


Ce qu'on fait concrètement dans ce code, c'est qu'on initialise un mot de passe (#define TARGET_PASS "123456") et on indique sa taille (#define MAX_LENGTH 6), on initialise les différentes variables pour calculer la durée d'exécution, ainsi qu'une variable found qui servira d'indicateur si le mot de passe a été trouvé ou non. Pour trouver le mot de passe, on utilise une boucle, qui parcourira toutes les valeurs entre 0 et 999999, on sortira de cette boucle si on a trouvé le mot de passe à l'aide de la variable found. Puis, on affiche les résultats.

Nous nous retrouvons avec :
Code:
$> ./password
      Mot de passe cracké : 123456
      Temps écoulé : 9.0780 millisecondes
$>

Cependant, le résultat (le temps), peut varier. Donc remanions le code pour générer des mots de passe aléatoirement, et tenter de les trouver. On affichera une moyenne à la fin de l'exécution.

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#define MAX_LENGTH 6
/* Fonction pour générer un mot de passe aléatoirement */
void generateRandomPassword(char *password) {
    for (int i = 0; i < MAX_LENGTH; ++i) {
        password = '0' + rand() % 10;
    }
    password[MAX_LENGTH] = '\0';
}
/* Exécution */
int main() {
    char TARGET_PASS[MAX_LENGTH + 1];[I] // Allouer de la mémoire pour stocker le mot de passe cible[/I]
    double somme = 0.0;
    for (int k = 0; k < 10; k++) {
        generateRandomPassword(TARGET_PASS); // Passer le tableau de caractères alloué au lieu d'un pointeur constant
        char password[MAX_LENGTH + 1];
        int found = 0;
        struct timeval start_time, end_time;
        gettimeofday(&start_time, NULL);
        for (int i = 0; i <= 999999 && !found; ++i) {
            snprintf(password, MAX_LENGTH + 1, "%06d", i);
            if (strcmp(password, TARGET_PASS) == 0) {
                found = 1;
                printf("Mot de passe cracké : %s\n", password);
            }
        }
        gettimeofday(&end_time, NULL);
        double total_time = (end_time.tv_sec - start_time.tv_sec) + (double)(end_time.tv_usec - start_time.tv_usec) / 1000;
        printf("Temps écoulé : %.4f millisecondes\n", total_time);
        somme += total_time;
    }
    printf("\n");
    printf("Temps moyen écoulé : %.4f millisecondes\n",somme/10);
    return 0;
}

Ce qu'on vient concrètement de changer, c'est que nous avons ajouté une fonction pour générer un mot de passe aléatoirement, et maintenant, nous générons un mot de passe aléatoirement, on recherche le mot de passe, nous stockons le temps d'exécution dans une variable somme, et le tout 10 fois car nous sommes dans une boucle. A la fin, on affiche le résultat, donc la somme divisée par 10.

Nous tombons donc sur :
Code:
$> ./password
   Mot de passe cracké : 367535
   Temps écoulé : 19.6110 millisecondes
   Mot de passe cracké : 629127
   Temps écoulé : 27.7840 millisecondes
   Mot de passe cracké : 093606
   Temps écoulé : 4.3740 millisecondes
   Mot de passe cracké : 261879
   Temps écoulé : 11.6390 millisecondes
   Mot de passe cracké : 202375
   Temps écoulé : 9.4630 millisecondes
   Mot de passe cracké : 922897
   Temps écoulé : 43.5810 millisecondes
   Mot de passe cracké : 361293
   Temps écoulé : 15.9320 millisecondes
   Mot de passe cracké : 194784
   Temps écoulé : 8.7290 millisecondes
   Mot de passe cracké : 503610
   Temps écoulé : 22.1430 millisecondes
   Mot de passe cracké : 632061
   Temps écoulé : 28.3490 millisecondes

   Temps moyen écoulé : 19.1605 millisecondes
$>

Donc seulement 19 millisecondes... Mais ça donne quoi avec des lettres aléatoires ? Car en théorie, on passe d'une combinaison entre 0 et 9, à A et Z (donc 26 possibilités car 26 lettres).

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define MAX_LENGTH 6

/* Fonction pour générer un mot de passe aléatoirement */
void generateRandomPassword(char *password) {
    for (int i = 0; i < MAX_LENGTH; ++i) {
        password = 'a' + rand() % 26; // Chiffre aléatoire entre 'a' et 'z'
    }
    password[MAX_LENGTH] = '\0';
}[/SIZE]

/* Exécution */
[SIZE=4]int main() {
    char TARGET_PASS[MAX_LENGTH + 1]; // Allouer de la mémoire pour stocker le mot de passe cible
    long double somme = 0.0;

    for (int k = 0; k < 10; k++) {
        generateRandomPassword(TARGET_PASS); // Passer le tableau de caractères alloué au lieu d'un pointeur constant
        char password[MAX_LENGTH + 1];
        int found = 0;

        struct timespec start_time, end_time;
        clock_gettime(CLOCK_MONOTONIC, &start_time);

        for (int i = 0; i <= 999999 && !found; ++i) {
            generateRandomPassword(password); // Générer un nouveau mot de passe aléatoire
            if (strcmp(password, TARGET_PASS) == 0) {
                found = 1;
                printf("Mot de passe cracké : %s\n", password);
            }
        }

        clock_gettime(CLOCK_MONOTONIC, &end_time);
        double total_time = (end_time.tv_sec - start_time.tv_sec) * 1000.0 + (double)(end_time.tv_nsec - start_time.tv_nsec) / 1000000.0;

        if(total_time < 0){
            total_time = -total_time;
        }

        printf("Temps écoulé : %.4f millisecondes\n", total_time);
        somme += total_time;
    }

    printf("\n");
    printf("Temps moyen écoulé : %.4Lf millisecondes\n", somme / 10);

    return 0;
}

Nous avons :
Code:
$> ./password
   Temps écoulé : 92.5627 millisecondes
   Temps écoulé : 79.7321 millisecondes
   Temps écoulé : 81.4394 millisecondes
   Temps écoulé : 81.1473 millisecondes
   Temps écoulé : 81.3780 millisecondes
   Temps écoulé : 81.6272 millisecondes
   Temps écoulé : 81.6914 millisecondes
   Temps écoulé : 82.3126 millisecondes
   Temps écoulé : 81.3545 millisecondes
   Temps écoulé : 81.3483 millisecondes

   Temps moyen écoulé : 82.4594 millisecondes
$>

Wow, 82 millisecondes. C'est drastiquement plus long en effet. Mais dans ce cas, que se passe-t-il si on prend un mot de passe avec différents caractères ?


C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAX_LENGTH 10

//Tous les caractères possibles
#define NUM_CHARS 62
const char *CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+{}[]|\\:;<>,.?/~`";

/* Fonction pour générer un mot de passe aléatoirement */
void generateRandomPassword(char *password) {
    for (int i = 0; i < MAX_LENGTH; ++i) {
        password = CHARS[rand() % NUM_CHARS]; // Choisir un caractère aléatoire de la liste CHARS
    }
    password[MAX_LENGTH] = '\0';
}

/* Exécution */
int main() {
    char TARGET_PASS[MAX_LENGTH + 1]; // Allouer de la mémoire pour stocker le mot de passe cible
    long double somme = 0.0;

    for (int k = 0; k < 10; k++) {
        generateRandomPassword(TARGET_PASS); // Passer le tableau de caractères alloué au lieu d'un pointeur constant
        char password[MAX_LENGTH + 1];
        int found = 0;

        struct timespec start_time, end_time;
        clock_gettime(CLOCK_MONOTONIC, &start_time);

        for (int i = 0; i <= 999999 && !found; ++i) {
            generateRandomPassword(password); // Générer un nouveau mot de passe aléatoire
            if (strcmp(password, TARGET_PASS) == 0) {
                found = 1;
                printf("Mot de passe cracké : %s\n", password);
            }
        }

        clock_gettime(CLOCK_MONOTONIC, &end_time);
        double total_time = (end_time.tv_sec - start_time.tv_sec) * 1000.0 + (double)(end_time.tv_nsec - start_time.tv_nsec) / 1000000.0;
        if(total_time < 0){
            total_time = -total_time;
        }

        printf("Temps écoulé : %.4f millisecondes\n", total_time);
        somme += total_time;
    }

    printf("\n");
    printf("Temps moyen écoulé : %.4Lf millisecondes\n", somme / 10);

    return 0;
}

Voici le résultat :
Code:
$> ./password
   Temps écoulé : 130.9251 millisecondes
   Temps écoulé : 127.2518 millisecondes
   Temps écoulé : 127.1703 millisecondes
   Temps écoulé : 138.8559 millisecondes
   Temps écoulé : 127.6754 millisecondes
   Temps écoulé : 127.3461 millisecondes
   Temps écoulé : 127.1704 millisecondes
   Temps écoulé : 127.2650 millisecondes
   Temps écoulé : 127.6003 millisecondes
   Temps écoulé : 127.3118 millisecondes

Temps moyen écoulé : 128.8572 millisecondes
$>

AH OUI ! C'est un grand changement. Cependant le problème, c'est que qui peut retenir une stupide suite de caractères aléatoires ? Nous, en tant que personne censée, on peut seulement retenir une suite de mot logique, comme un mot par exemple.

J'ai donc téléchargé un fichier (dico.txt) qui contient quelques centaines de mots (tirés du dictionnaire). La fonction pour générer un mot de passe vient donc piocher dans ce fichier pour trouver un mot, et pour le cracker, on vient aussi le parcourir.


C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MAX_LENGTH 100 // Longueur maximale d'un mot dans le dictionnaire
// Fonction pour générer un mot de passe aléatoirement à partir du fichier "dico.txt"
void generateRandomPassword(char *password) {
    // Ouvrir le fichier en mode lecture
    FILE *file = fopen("dico.txt", "r");
    if (file == NULL) {
        printf("Erreur lors de l'ouverture du fichier dico.txt\n");
        exit(1);
    }
    // Choisir un numéro de ligne aléatoire
    int num_lines = 0;
    char line[MAX_LENGTH];
    while (fgets(line, MAX_LENGTH, file) != NULL) {
        num_lines++;
    }
    rewind(file); // Rembobiner le fichier pour lire à partir du début
    int random_line = rand() % num_lines;
    // Lire et ignorer les lignes jusqu'à ce que nous atteignions la ligne aléatoire choisie
    for (int i = 0; i < random_line; i++) {
        fgets(line, MAX_LENGTH, file);
    }
    // Copier le mot dans le mot de passe
    fgets(password, MAX_LENGTH, file);
    // Supprimer le caractère de nouvelle ligne s'il est présent
    char *newline_ptr = strchr(password, '\n');
    if (newline_ptr != NULL) {
        *newline_ptr = '\0';
    }
    // Fermer le fichier
    fclose(file);
}
// Exécution
int main() {
    char TARGET_PASS[MAX_LENGTH + 1]; // Allouer de la mémoire pour stocker le mot de passe cible
    long double somme = 0.0;
    for (int k = 0; k < 10; k++) {
        generateRandomPassword(TARGET_PASS); // Générer un mot de passe à partir du fichier "dico.txt"
        char password[MAX_LENGTH + 1];
        int found = 0;
        struct timespec start_time, end_time;
        clock_gettime(CLOCK_MONOTONIC, &start_time);
        // Ouvrir le fichier en mode lecture
        FILE *file = fopen("dico.txt", "r");
        if (file == NULL) {
            printf("Erreur lors de l'ouverture du fichier dico.txt\n");
            return 1;
        }
        // Parcourir le fichier ligne par ligne pour la recherche
        char line[MAX_LENGTH];
        while (fgets(line, MAX_LENGTH, file) != NULL && !found) {
            // Supprimer le caractère de nouvelle ligne s'il est présent
            char *newline_ptr = strchr(line, '\n');
            if (newline_ptr != NULL) {
                *newline_ptr = '\0';
            }
            // Comparer le mot de passe avec le mot de la ligne actuelle
            if (strcmp(line, TARGET_PASS) == 0) {
                found = 1;
                printf("Mot de passe cracké : %s\n", line);
            }
        }
        // Fermer le fichier
        fclose(file);
        clock_gettime(CLOCK_MONOTONIC, &end_time);
        double total_time = (end_time.tv_sec - start_time.tv_sec) * 1000.0 + (double)(end_time.tv_nsec - start_time.tv_nsec) / 1000000.0;
        if(total_time < 0){
            total_time = -total_time;
        }
        printf("Temps écoulé : %.4f millisecondes\n", total_time);
        somme += total_time;
    }
    printf("\n");
    printf("Temps moyen écoulé : %.4Lf millisecondes\n", somme / 10);
    return 0;
}

Ce qui donne :
Code:
$> ./password
   Mot de passe cracké : fonce
   Temps écoulé : 0.0843 millisecondes
   Mot de passe cracké : tirer
   Temps écoulé : 0.0551 millisecondes
   Mot de passe cracké : coquin
   Temps écoulé : 0.0405 millisecondes
   Mot de passe cracké : different
   Temps écoulé : 0.0454 millisecondes
   Mot de passe cracké : couleur
   Temps écoulé : 0.0233 millisecondes
   Mot de passe cracké : tunnel
   Temps écoulé : 0.0896 millisecondes
   Mot de passe cracké : ligne
   Temps écoulé : 0.0285 millisecondes
   Mot de passe cracké : apporter
   Temps écoulé : 0.0179 millisecondes
   Mot de passe cracké : appareil
   Temps écoulé : 0.0287 millisecondes
   Mot de passe cracké : couper
   Temps écoulé : 0.0436 millisecondes

   Temps moyen écoulé : 0.0457 millisecondes
$>

C'est... affligeant. En effet, c'est pas bien long de venir trouver un simple mot dans un dictionnaire, et c'est pas pour rien que les pires mots de passe sont ceux tirés d'un dictionnaire. Cependant, tentons un truc. Essayons de mettre un temps d'attente d'une seconde entre chaque recherche.


C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define MAX_LENGTH 100 // Longueur maximale d'un mot dans le dictionnaire
// Fonction pour générer un mot de passe aléatoirement à partir du fichier "dico.txt"
void generateRandomPassword(char *password) {
    // Ouvrir le fichier en mode lecture
    FILE *file = fopen("dico.txt", "r");
    if (file == NULL) {
        printf("Erreur lors de l'ouverture du fichier dico.txt\n");
        exit(1);
    }
    // Choisir un numéro de ligne aléatoire
    int num_lines = 0;
    char line[MAX_LENGTH];
    while (fgets(line, MAX_LENGTH, file) != NULL) {
        num_lines++;
    }
    rewind(file); // Rembobiner le fichier pour lire à partir du début
    int random_line = rand() % num_lines;
    // Lire et ignorer les lignes jusqu'à ce que nous atteignions la ligne aléatoire choisie
    for (int i = 0; i < random_line; i++) {
        fgets(line, MAX_LENGTH, file);
    }
    // Copier le mot dans le mot de passe
    fgets(password, MAX_LENGTH, file);
    // Supprimer le caractère de nouvelle ligne s'il est présent
    char *newline_ptr = strchr(password, '\n');
    if (newline_ptr != NULL) {
        *newline_ptr = '\0';
    }
    // Fermer le fichier[
    fclose(file);
}
// Exécution
int main() {
    char TARGET_PASS[MAX_LENGTH + 1]; // Allouer de la mémoire pour stocker le mot de passe cible
    generateRandomPassword(TARGET_PASS); // Générer un mot de passe à partir du fichier "dico.txt"
    printf("Mot de passe à rechercher : %s\n", TARGET_PASS);
    struct timespec start_time, end_time;
    clock_gettime(CLOCK_MONOTONIC, &start_time);
    // Ouverture du fichier en mode lecture
    FILE *file = fopen("dico.txt", "r");
    if (file == NULL) {
        printf("Erreur lors de l'ouverture du fichier dico.txt\n");
        return 1;
    }
    // Parcourir le fichier ligne par ligne pour la recherche
    char line[MAX_LENGTH];
    int found = 0;
    while (fgets(line, MAX_LENGTH, file) != NULL && !found) {
        // Supprimer le caractère de nouvelle s'il est présent[/I]
        char *newline_ptr = strchr(line, '\n');
        if (newline_ptr != NULL) {
            *newline_ptr = '\0';
        }
        // Comparer le mot de passe avec le mot de la ligne actuelle
        if (strcmp(line, TARGET_PASS) == 0) {
            printf("Mot de passe cracké : %s\n", line);
            found = 1;
            break;
        }
        // Attente d'une seconde
        usleep(1000000);
    }
    // Fermer le fichier
    fclose(file);
    clock_gettime(CLOCK_MONOTONIC, &end_time);
    double total_time = (end_time.tv_sec - start_time.tv_sec) + (double)(end_time.tv_nsec - start_time.tv_nsec) / 1000000000.0;
    printf("Temps écoulé : %.4f secondes\n", total_time);
    return 0;
}

Donc :
Code:
$> ./password
   Mot de passe à rechercher : fonce
   Mot de passe cracké : fonce
   Temps écoulé : 559.7000 secondes
$>

C'est beaucoup non ? Ca fait environ 8 minutes. Et je rappelle que mettre un mot d'un dictionnaire c'est très peu sécurisé. Donc on a transformé les pires des mots de passe, en mots de passe passables. Et c'est pour cela que lorsque vous entrez un mauvais mot de passe, ça met du temps avant de vous dire que votre mot de passe est incorrect lors de votre connexion, car y'a ce petit délai (couplé avec des sécurités supplémentaires évidemment). On aurait cependant pu aller plus vite en faisant une recherche dichotomique, et en utilisant une machine plus puissante pour faire les calculs.

Mais enfin, pas la peine de vous casser la tête à faire une suite aléatoire de caractères pour vous construire un bon mot de passe. Vous pouvez choisir le refrain d'une musique que vous aimez, ne garder que les premières lettres de chaque mots (en conservant les majuscules), remplacer certains caractères par d'autres (un O par un 0, un a par un @ ou 4, etc). Vous aurez donc un mot de passe assez fort, que vous oublierez moins facilement.

Sans oublier que la taille compte (malheureusement pour vous messieurs), et pour l'expliquer, rien de mieux que les mathématiques. Reprenons notre mot de passe basique avec les nombres, un mot de passe de 4 chiffres aura 10⁴ possibilités, donc 10 000 possibilités. Mais changeons la taille, et prenons 6 chiffres, on passe donc à 10⁶, donc 1 000 000 de possibilités, ce qui est énormément plus. Et si on prend l'exemple des lettres aléatoires, avec 4 lettres, on a 26⁴ possibilités, donc 456 976 possibilités. Et avec 6 lettres, ça fait 308 915 776 possibilités, ce qui est énorme.

Post scriptum : Lors des tests avec les combinaisons de lettres, les combinaisons sont SOIT en minuscules, SOIT en majuscule, et non un mix des deux. Si nous avions mélangés les deux, nous aurions eu une complexité deux fois plus grande.
 
Description courte
Comment un mot de passe est-il considéré complexe ? Et pourquoi faut-il choisir un bon mot de passe ?
Dernière édition:
  • Wow
  • Like
Réactions: 2 membres

Guimauve

Initié(e)
2 Déc 2023
245
106
Bravooooo bon article ^^
Je m'y connais PAS DU TOUT en informatique mais j'ai comme même compris la moitié
 

Égo_Supremo

Grand Sage
20 Mai 2023
1,857
987
Bon article (Le temps pour trouver un mot de passe devient encore plus ridicule si l'ordinateur utilisé a un gros processeur, euh je crois*.). Est-ce que le language de programmation utilisé fait changer le résultat ? Si oui le plus rapide x) c'est pour un ami..
 

Gno

Initié(e)
Auteur du topic
9 Nov 2022
341
253
Bon article (Le temps pour trouver un mot de passe devient encore plus ridicule si l'ordinateur utilisé a un gros processeur, euh je crois*.). Est-ce que le language de programmation utilisé fait changer le résultat ? Si oui le plus rapide x) c'est pour un ami..
En sachant que le plus poussé pour tout ce qui est data c'est Python, je dirais Python du coup. Et oui, le temps avec une grosse machine sera bien différent, donc c'est encore plus affligeant de voir que c'est si rapide même avec seulement mon pc (qui n'est pas non plus un mauvais pc pour être honnête)
 
  • Like
Réactions: 1 membre