C15 : Exercices

Exercices sur les mots de passe

C15.E1 : Longueur des mots de passe (sans calculatrice)

1) Montrer qu'il y a environ \(10^{20}\) mots de passe de 10 caractères choisis parmi l'ensemble de 95 caractères ASCII affichables.

2) En supposant qu'un ordinateur puisse tester un million de mots de passe à la seconde, combien de temps lui daudra-t-il pour explorer l'ensemble des mots de passe possibles ?

...

1)

Il y a exactement \(95^{10}\) mots de passes possibles.

En arrondissant on a \(95^{10} = 100^{10} = ({10^2})^{10} = 10^{20}\).

2)

\(1\ \text{million} = 10^6\)

\(temps = \pu{\dfrac{10^{20}}{10^6} s} = \pu{10^{14} s}\).

Remarque : \(\pu{10^{14} s} = \pu{\dfrac{10^{14}}{60×60×24×365} ans} = \pu{3×10^{12} ans}\).

C15.E2 : Mot de passe correct

Ecrire une fonction qui prend une chaine de caractères en paramètres et vérifie que cette chaine est un mot de passe correct.

On estime qu'un mot de passe est correct s'il contient : au moins 8 caractères, une majuscule, une minuscule, un chiffre et un caractère spécial parmi "_-.!;".

...
def chaine_to_dico(chaine:str)->dict:
    dico = {'nb_chiffres':0,
            'nb_majuscules':0,
            'nb_minuscules':0,
            'nb_speciaux':0,
            'autres':0}
    for caractere in chaine:
        val = ord(caractere)
        if val >= 48 and val <= 57:
            dico['nb_chiffres'] += 1
        elif val >= 65 and val <= 90:
            dico['nb_majuscules'] += 1
        elif val >= 97 and val <= 122:
            dico['nb_minuscules'] += 1
        elif val == 95 or val == 45 or val == 46 or val == 33 or val == 59:
            dico['nb_speciaux'] += 1
        else:
            dico['autres'] += 1
    return dico

def est_pw_correct(chaine:str)->bool:
    if len(chaine) >= 8:
        dico = chaine_to_dico(chaine)
        if dico['nb_chiffres'] > 0 and dico['nb_majuscules'] > 0 and dico['nb_minuscules'] > 0 and dico['nb_speciaux'] > 0:
            return True
    return False



print(chaine_to_dico('a'))
print(chaine_to_dico('aA8_'))

print(est_pw_correct('a')) #False
print(est_pw_correct('aaaAAaaa')) #False
print(est_pw_correct('1234_567')) #False
print(est_pw_correct('a8bC_rt5')) #True
print(est_pw_correct('a8bC_r5')) #False

Exercices sur le chiffrement symétrique

C15.E11 : Taille des clé pour le chiffrement AES (sans claculatrice)

Les clés utilisées pour le protocole de chiffrement AES font au minimum 128 bits.

1) Exprimer à l'aide d'une puissance de 2 le nombre de clefs différentes possibles.

2) Quelle est environ la taille de ce nombre en décimal ?

Aide au calcul : \(2^{10} = 1024 ≈ 1000 = 10^3\).

...

1) \(2^{128}\)

2) \(2^{128} ≈ 2^{130} = (2^{10})^{13} = (10^3)^{13} = 10^{3×13} = 10^{39}\)

C15.E12 : Chiffrement de Vigenère

Le chiffrement de Vigenère nécessite :

Le chiffrement consiste à prendre le première caractère du message clair et à le remplacer par le caractère obtenu en effectuant une permutation circulaire de longueur \(N_1\) dans l'alphabet, puis à prendre le deuxième caractère et à effectuer une permutation circulaire de longueur \(N_2\), et ainsi de suite. Lorsqu'on arrive au dernier nombre de la clé, on recommence avec le premier.

Travail à faire

On utilisera comme clé un nombre dont les différents chiffres serviront aux permutations circulaires successives.

Compléter la fonction chiffrer(message_clair:str, alphabet:str, cle:int)->str qui renvoie le message chiffré par la méthode de Vigenère. On pourra utiliser des fonctions intermédiaires

def chiffrement_vigenere(message_clair:str, alphabet:str, cle:int)->str:
    """
    Parametres:
        message_clair (str) : chaine que l'on souhaite chiffrer, contenant uniquement des caractères de l'alphabet
        alphabet (str) : chaine de caractères contenant l'ensemble des caractères utilisables dans le message clair
        cle (int) : entier correspondant aux décalages successifs souhaités pour le chiffrement
    Return (str) : message chiffré
    Exemples:
        >>> chiffrement_vigenere("bonjour", "abcdefghijklmenopqrstuvwxyz", 0)
        'bonjour'
        >>> chiffrement_vigenere("bonjour", "abcdefghijklmenopqrstuvwxyz", 123)
        'cqqkqxs'
        >>> chiffrement_vigenere("bonjour", "abcdefghijklmenopqrstuvwxyz", 45)
        'ftrnszv'
    """
    pass
...
def chiffrement_lettre(lettre_clair:str, alphabet:str, cle:int)->str:
    """
    Parametres:
        lettre_clair (str) : caractère de l'alphabet que l'on souhaite chiffrer
        alphabet (str) : chaine de caractères contenant l'ensemble des caractères utilisables dans le message clair
        cle (int) : entier correspondant au décalage souhaité pour le chiffrement de lettre
    Return (str) : lettre chiffrée
    """
    pos_clair = alphabet.index(lettre_clair)
    pos_chiffre = (pos_clair + cle) % len(alphabet)
    lettre_chiffre = alphabet[pos_chiffre]
    return lettre_chiffre

def chiffrement_vigenere(message_clair:str, alphabet:str, cle:int)->str:
    """
    Parametres:
        message_clair (str) : chaine que l'on souhaite chiffrer, contenant uniquement des caractères de l'alphabet
        alphabet (str) : chaine de caractères contenant l'ensemble des caractères utilisables dans le message clair
        cle (int) : entier correspondant aux décalages successifs souhaités pour le chiffrement
    Return (str) : message chiffré
    Exemples:
        >>> chiffrement_vigenere("bonjour", "abcdefghijklmenopqrstuvwxyz", 0)
        'bonjour'
        >>> chiffrement_vigenere("bonjour", "abcdefghijklmenopqrstuvwxyz", 123)
        'cqqkqxs'
        >>> chiffrement_vigenere("bonjour", "abcdefghijklmenopqrstuvwxyz", 45)
        'ftrnszv'
    """
    cle = str(cle)
    message_chiffre = ""
    for i in range(len(message_clair)):
        lettre_clair = message_clair[i]
        cle_lettre = int(cle[i % len(cle)])
        lettre_chiffre = chiffrement_lettre(lettre_clair, alphabet, cle_lettre)
        message_chiffre = message_chiffre + lettre_chiffre
    return message_chiffre

import doctest
doctest.testmod(verbose=True)

C15.E13 : Chiffrement par permutation à l'aide d'un dictionnaire

On dispose d'un alphabet sous forme d'une chaine de caractères.

On appelle dictionnaire de chiffrement, un dictionnaire dont les clés sont les caractères de l'alphabet utilisé et les valeurs les caractères du même alphabet, mais redistribuées aléatoirement.

Le chiffrement consiste alors à substituer chaque caractère du message et à le substituer par la valeur correspondante dans le dictionnaire de chiffrement.

1) Écrire une fonction dico_chiffrement(alphabet:str)->dict qui prend un alphabet en paramètre et renvoie un dictionnaire de chiffrement.

2) Écrire une fonction chiffrement(dico:dict, message_clair:str)->str qui prend un dictionnaire de chiffrement et un message clair en paramètres et renvoie un message chiffré à l'aide du dictionnaire de chiffrement.

3) Écrire une fonction inv_dico(dico:dict)->dict qui prend un dictionnaire en paramètre et renvoie un dictionnaire où les clés et les valeurs son inversées (autrement dit, les valeurs servent de clés et les clés servent de valeurs).

4) Écrire une fonction dechiffrement(dico:dict, message_chiffre:str)->str qui prend un dictionnaire de chiffrement et un message chiffré en paramètres et renvoie le message clair correspondant.

5) Décryptage

5.a. Combien de dictionnaires de chiffrement sont envisageables avec un alphabet de 26 caractères ?

5.b. Cette technique de chiffrement vous semble-t-elle difficile à décrypter ?

...
from random import randint

def dico_chiffrement(alphabet:str)->dict:
    liste_lettres = list(alphabet)
    dico = {}
    for lettre in alphabet:
        # récupère et enlève un lettre au hasard dans liste_lettre
        lettre_permute = liste_lettres.pop(randint(0, len(liste_lettres)-1))
        dico[lettre] = lettre_permute
    return dico

def chiffrement(dico:dict, message_clair:str)->str:
    message_chiffre = ""
    for lettre_clair in message_clair:
        lettre_chiffre = dico[lettre_clair]
        message_chiffre = message_chiffre + lettre_chiffre
    return message_chiffre

def inv_dico(dico:dict)->dict:
    dico_inverse = {}
    for cle in dico:
        dico_inverse[dico[cle]] = cle
    return dico_inverse

def dechiffrement(dico:dict, message_chiffre:str)->str:
    dico_dechiffrement = inv_dico(dico)
    return chiffrement(dico_dechiffrement, message_chiffre)

alphabet = "abcdefghijklmnopqrstuvwxys ABCDEFGHIJKLMNOPQRSTUVWXYZ."

message_clair = "bonjour la NSI"
print('Le message clair est : ' + message_clair)
dictionnaire_chiffrement = dico_chiffrement(alphabet)
print(dictionnaire_chiffrement)
message_chiffre = chiffrement(dictionnaire_chiffrement, message_clair)
print('Le message chiffré est : ' + message_chiffre)
message_dechiffre = dechiffrement(dictionnaire_chiffrement, message_chiffre)
print('Le message déchiffré est : ' + message_dechiffre)

C15.E14 : Décryptage d'un message chiffré avec le chiffrement de Vigenère (difficile)

Le message ci-dessous a été intercepté :

gjwvkiafmnmfcgcedmfjwvkmyxmfkwhktruqrdarhbcgcedmfkmvyvrmzffivfaqsvfdmhjaqwurmzrklrhqrjzrfmhxxbmzywauguzwazgzgwtfvmflqawanmbewxnkcahwhjtrkmvyvrmzqwagwvrtzrkahjabfabejewbegvrviaktrhilklrewevwegcfwbrflrfbywabejewahfiafmnmxbmzywatgciwzawzggcfmvnfvrsccgcedmflzbmdrjcasvawihhwhjtrkizwvrjbbmarllnfaywagwvrtzrktrktvwznmxnqaqwubjlbjwhkmgwvqwvgdmfguojmflwhlkricvwaggzawjeatywxnkbbmapwckicvwzewvgfmfgvghifhmevcfdmiamhpyhamflnbjbawlrhmeabcgqaltrkznuqawacjwsgvqwaawabfbcsanlbravgwacszywordlrkkrflewahfnrmarnmvdtrjiqwabejewahfmymuvwzrbivdtvjiewvbmdrdmrkmestrhmricvxcgtzvkmrdmfsvfuwhjwafmfwznvmagciwihjwvdmisagwubflrnwhkmalwhjmqwbbmapgbrkdbmacgciwhigcfwvpdwewunaaigcffmcgciwhrlmefmydmzwvgdmgwvvjmavmugzfvmigapdwgmzrkcasvawihhwhjtrkobmdrjvrjbbmahfiafmnmxbmzywagjwhnmemvnfvrsccgcedmfsurfmelwhkmgviaktrkbrfmojmfdmfdqrj

Nous savons de source sûre que la clé est constituée de 3 entiers, que l'alphabet utilisé est "abcde...yz" et que la méthode de chiffrement est celle de Vigenère.

A faire : Proposer un programme pour décrypter ce message.

Exercices sur le chiffrement asymétrique

C15.E21 : Utilisation des clés

Alice et Bob souhaitent communiquer. Ils possède chacun une paire de clé publique/privée.

1. Quelle clé Alice doit-elle utiliser pour chiffrer un message confidentiel pour Bob ?

2. Quelle clé Alice doit-elle utiliser pour chiffrer un message confidentiel et authentifié pour Bob ?

3. Quelle clé Alice doit-elle utiliser pour déchiffrer un message confidentiel provenant de Bob ?

4. Quelle clé Alice doit-elle utiliser pour déchiffrer un message confidentiel et authentifié provenant de Bob ?

C15.E22 : Clé publique d'une communication HTTPS

A l'aide d'un navigateur, aller sur le site de l'ANSSI : cyber.gouv.fr

1. La communication entre votre ordinateur et le serveur du site est-elle sécurisée ?

2. Dans le certificat du serveur, relever les éléments suivants :

3. Repérer la clé publique liée au certificat.

4. Les informations contiennent aussi la clé publique de l'organisme de certification. A quoi va servir cette clé publique ?

Exercices autres

C15.E31 : Machine Enigma

Source : d'après un exercice de M. Boddaert

Nous allons, dans cet exercice, nous intéresser à la machine Enigma.

La machine Enigma

Inventée par l'Allemand Athur Scherbius, cette machine électromecanique portable a servi au chiffrement et déchiffrement de l'intégralité des messages échangés par les Allemands nazis pendant la Seconde Guerre mondiale.

Pour plus de détails : vidéo (11 min) Enigma Machine - Numberphile

Caractéristiques de la machine Enigma

Une clé est constituée :

  • des 10 câblages possibles de lettres dans le tableau de connexions.
  • de 3 rotors (parmi 5) et de leur position de départ.
  • des câblages du réflecteur.

Il y a 158 962 555 217 826 360 000 clés possibles. Et les Allemands avaient une clé différente pour chaque jour. Autrement dit, les Alliés avaient 24h pour espérer trouver la clé permettant de déchiffrer les messages du jour.

Jusqu'à ce qu'Alan Turing crée sa machine.

Bibliothèque py-enigma

Cet exercice nécessite l'utilisation de la blibliothèque py-enigma qu'il faut installer dans Thonny.

Cette bibliothèque permet de simuler le fonctionnement d'une machine Enigma en Python.

Pour configurer la position des rotors :

>>> machine.set_display('PIX')

Pour afficher la position des rotors :

>>> machine.get_display()
'PIX'

Pour chiffrer une lettre :

>>> machine.key_press('A')
'I'

Le déchiffrement se fait de la manière inverse (il faut que la position des rotors soit la même) :

>>> machine.set_display('PIX')
>>> machine.key_press('I')
'A'

Travail à faire

1) Copier-coller et exécuter code ci-dessous :

# Importation du module
from enigma.machine import EnigmaMachine

# Machine enigma configurée avec les rotors III, IV et II,
#                                le réflecteur C
#                                le tableau de connexions CE DV IO NR ST QX AP
machine = EnigmaMachine.from_key_sheet(rotors = "III IV II",
                                       reflector = "C",
                                       plugboard_settings = "CE DV IO NR ST QX AP")

1.a) Quels rotors sont utilisés et sur quelles positions sont-ils configurés ?

1.b) Quelle est la lettre chiffrée obtenue lors de l'appuie sur la touche 'A' ?

2) Écrire une fonction chiffre_enigma(message_clair : str, machine : EnigmaMachine, pos_rotors : str)->str qui prend en paramètres un message clair, une machine Enigma configurée et la position des rotors. Cette fonction renvoie le message chiffré.

3) Écrire une fonction dechiffre_enigma(message_chiffre : str, machine : EnigmaMachine, pos_rotors : str)->str qui prend en paramètres un message chiffré, une machine Enigma configurée et la position des rotors. Cette fonction renvoie le message clair.