Algorithmes de chiffrement faible

1. Algorithmes de chiffrement faible (facilement déchiffrables)

Les premiers algorithmes utilisés pour le chiffrement d’une information étaient assez rudimentaires dans leur ensemble. Ils consistaient notamment au remplacement de caractères par d’autres. La confidentialité de l’algorithme de chiffrement était donc la pierre angulaire de ce système.

Exemples d’algorithmes de chiffrement faibles :

  • ROT13 (rotation de 13 caractères, sans clé) ;
  • Chiffre de César (décalage de plusieurs lettres dans l’alphabet sur la gauche ou sur la droite).
  • Chiffre de Vigenère (introduit la notion de clé)

Exercices avec des algorithme faibles

Pour s’amuser avec ces algorithmes vous pouvez vous référer aux sites https://asecuritysite.com/ ou http://www.cryptool-online.org/ ou encore, parmi d’autres ressources, la libraire python pycipher : http://pycipher.readthedocs.io/en/master/. Pour des exemples en Python on ira volontiers consulter https://github.com/TheAlgorithms/Python/tree/master/ciphers

On trouvera plus bas des exercices d’implémentation en Bash et en Python 3 de quelques algorithmes faibles.

  • Projet traducteur de langage Geek
  • Projet ROT13
  • Projet chiffrement de César
  • Projet chiffrement de Vigenère

2. Projet traducteur de langage Geek

2.1. Objectif

Programme qui traduit une phrase ou un mot en langage geek.

2.2. Dictionnaire Leet

dictionnary = {"A": "4", "B": "8", "C": "(", "D": ")", "E": "3", "F": "f",
               "G": "6", "H": "#", "I": "1", "J": "j", "K": "k", "L": "|",
               "M": "m", "N": "n", "O": "0", "P": "p", "Q": "q", "R": "2",
               "S": "5", "T": "7", "U": "u", "V": "v", "W": "w", "X": "x",
               "Y": "y", "Z": "z"}

2.3. Résultat attendu

python3 leetspeak.py
Enter message: Do you speek leet
Clear to coded: )0 y0u 5p33k |337
Coded to clear:  DO YOU SPEEK LEET

2.4. En pratique

2.5. Solution

En Bash:

echo "Do you speek leet" \
| tr '[a-z]' '[A-Z]' \
| tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' '48()3f6#1jk|mn0pq257uvwxyz'
echo ")0 y0u 5p33k |337" \
| tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' '48()3f6#1jk|mn0pq257uvwxyz' \
| tr '[a-z]' '[A-Z]'

En Python 3 :

#!/usr/local/bin/python3
"""
Leetspeak translator
"""

dictionnary = {"A": "4", "B": "8", "C": "(", "D": ")", "E": "3", "F": "f",
               "G": "6", "H": "#", "I": "1", "J": "j", "K": "k", "L": "|",
               "M": "m", "N": "n", "O": "0", "P": "p", "Q": "q", "R": "2",
               "S": "5", "T": "7", "U": "u", "V": "v", "W": "w", "X": "x",
               "Y": "y", "Z": "z"}


def translation(input_string: str, encrypt: bool) -> str:
    """
    https://en.wikipedia.org/wiki/Leet
    >>> string0 = 'Do you speek leet'
    >>> string1 = translation(string0, True)
    >>> string1 == ')0 y0u 5p33k |337'
    True
    >>> string2 = translation(string1, False)
    >>> string2 == string0.upper()
    True
    """
    input_string = input_string.upper()
    if encrypt is True:
        for key, value in dictionnary.items():
            input_string = input_string.replace(key, value)
        out = input_string
    else:
        for key, value in dictionnary.items():
            input_string = input_string.replace(value, key)
        out = input_string
    return out


def main() -> None:
    """Main Function"""
    string0 = input("Enter message: ")

    string1 = translation(string0, True)
    print("Clear to coded:", string1)

    string2 = translation(string1, False)
    print("Coded to clear: ", string2)


if __name__ == "__main__":
    import doctest

    doctest.testmod()

    main()

3. Projet ROT13

3.1. Objectif

Encoder et décoder un texte grâce à l’algorithme ROT13.

3.2. Algorithme ROT13

ROT13 est un cas particulier du chiffrement de César qui a été développé dans la Rome antique.

Comme il y a 26 lettres (2×13) dans l’alphabet latin de base, ROT13 est son propre inverse ; c’est-à-dire que pour annuler ROT13, le même algorithme est appliqué, de sorte que la même action peut être utilisée pour l’encodage et le décodage.

ROT13 crée un écalage de chaque lettre de 13 vers la droite dans l’alphabet. Si les lettres de l’alphabet sont numérotées de 0 à 25, il s’agirait d’ajouter 13 à leur rang et de s’assurer qu’il reste compris entre 0 et 25 avec le modulo 26.

Chiffré avec ROT13, le mot « HELLO » devient « URYYB » (et inversement).

Source de l’image

3.3. Résultat attendu

python rot13.py
Enter message: Hello World
Clear to coded: Uryyb Jbeyq
Coded to clear:  Hello World

3.4. En pratique

3.5. Solution

En bash :

echo "Hello World" | tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
echo "Uryyb Jbeyq" | tr '[N-ZA-Mn-za-m]' '[A-Za-z]'

En Python 3 :

#!/usr/local/bin/python3
"""ROT13 cipher"""

from string import ascii_lowercase, ascii_uppercase


def translation(input_string: str) -> str:
    """
    https://en.wikipedia.org/wiki/ROT13

    >>> string0 = 'Hello World'
    >>> string1 = translation(string0)
    >>> string1 == 'Uryyb Jbeyq'
    True
    >>> string2 = translation(string1)
    >>> string2 == string0
    True
    """
    lower = ascii_lowercase
    upper = ascii_uppercase
    alphabet = lower + upper
    key = 13
    out = ""
    for character in input_string:
        if character in ascii_lowercase:
            out += lower[(lower.index(character) + key) % len(lower)]
        elif character in ascii_uppercase:
            out += upper[(upper.index(character) + key) % len(upper)]
        elif character not in alphabet:
            out += character
    return out


def main() -> None:
    """Main Function"""
    string0 = input("Enter message: ")

    string1 = translation(string0)
    print("Clear to coded:", string1)

    string2 = translation(string1)
    print("Coded to clear: ", string2)


if __name__ == "__main__":
    import doctest

    doctest.testmod()

    main()

Encore en python 3 :

rot13 = str.maketrans(
    "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",
    "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
"Hello World!".translate(rot13)
#'Uryyb Jbeyq!'

Et encore en python 3 :

import codecs
codecs.encode('Hello World!', 'rot_13')

Source

4. Projet Caesar

4.1. Objectif

Encoder et décoder un texte grâce à l’algorithme de César. Mener une attaque Brute-Force pour découvrir le texte original.

4.2. Algorithme de César

En cryptographie, le chiffrement de César est l’une des techniques de chiffrement les plus simples et les plus connues. Il s’agit d’un type de chiffrement par substitution dans lequel chaque lettre du texte en clair est remplacée par une lettre située à un nombre fixe de positions dans l’alphabet. Par exemple, avec un décalage vers la gauche de 3, D est remplacé par A, E devient B, et ainsi de suite. La méthode doit son nom à Jules César, qui l’utilisait dans sa correspondance privée.

L’étape de chiffrement effectuée par un chiffre César est souvent incorporée dans des schémas plus complexes, tels que le chiffre de Vigenère, et a encore une application moderne dans le système ROT13.

4.3. Résultat attendu

python3 caesar.py
Enter message: Hello World
Enter a key (off-set): 13
Clear to coded: UryyB jBEyq
Coded to clear:  Hello World

4.4. En pratique

4.5. Solution

En Bash:

K=13
A=$(echo {a..z} {A..Z} | sed -r 's/ //g')
C=$(echo $A | sed -r "s/^.{$K}//g")$(echo $A | sed -r "s/.{$( expr ${#A} - $K )}$//g")
echo "Hello World" | tr ${A} ${C}
echo "UryyB jBEyq" | tr ${C} ${A}

En Python 3 :

#!/usr/local/bin/python3
"""Caesar cipher"""

from string import ascii_letters
from typing import Dict


def translation(input_string: str, key: int = 13, encrypt: bool) -> str:
    """
    https://en.m.wikipedia.org/wiki/Caesar_cipher

    >>> string0 = 'Hello World'
    >>> key = 13
    >>> string1 = translation(string0, key, True)
    >>> string1 == 'UryyB jBEyq'
    True
    >>> string2 = translation(string1, key, False)
    >>> string2 == string0
    True
    """
    alphabet = ascii_letters
    if encrypt is not True:
        key *= -1
    out = ""
    for character in input_string:
        if character not in alphabet:
            out += character
        else:
            out += alphabet[(alphabet.index(character) + key) % len(alphabet)]
    return out


def bruteforce(input_string: str) -> Dict[int, str]:
    """
    https://en.wikipedia.org/wiki/Brute_force

    >>> string0 = 'Hello World'
    >>> key = 13
    >>> string1 = translation(string0, key, True)
    >>> bruteforce(string1)[key] == string0
    True
    """
    alphabet = ascii_letters
    brute_force_data = {}
    for key in range(1, len(alphabet) + 1):
        brute_force_data[key] = translation(input_string, key, False)
    return brute_force_data


def main() -> None:
    """Main Function"""
    string0 = input("Enter message: ")
    key = int(input("Enter a key (off-set): "))

    string1 = translation(string0, key, True)
    print("Clear to coded:", string1)

    string2 = translation(string1, key, False)
    print("Coded to clear: ", string2)

    print(bruteforce(string1))


if __name__ == "__main__":
    import doctest

    doctest.testmod()

    main()

5. Projet Vigenere

5.1. Objectif

Encoder et décoder un texte grâce à l’algorithme de Vigenère et une clé.

5.2. Algortithme de Vigenère

Pour casser le code de César il suffit de tester autant de décalages possibles qu’il n’y a de lettres dans l’alphabet.

L’algortithme de Vigenère consiste à introduire une clé rendant le décalage variable selon la position du caractère dans le message.

Message clairciscosystems
n# du message clair28182141824181941218
Clé répétéeabcdabcdabcd
n# de clé (décalage)012301230123
Résultat n# + n#2920514190211951421
Message chiffrécjufotavtfov

5.3. Résultat attendu

python vigenere.py
Enter message: ciscosystems
Enter a key: abcd
Clear to coded: cjufotAvtfov
Coded to clear:  ciscosystems

5.4. En pratique

5.5. Solution

#!/usr/local/bin/python3
"""Vigenere cipher"""

from string import ascii_letters


def translation(input_string: str, key: str = 'abcd', encrypt: bool) -> str:
    """
    https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
    >>> string0 = 'ciscosystems'
    >>> encryption_key = 'abcd'
    >>> string1 = translation(string0, encryption_key, True)
    >>> string1 == 'cjufotAvtfov'
    True
    >>> string2 = translation(string1, encryption_key, False)
    >>> string2 == string0
    True
    """
    alphabet = ascii_letters
    out = ""
    for index, character in enumerate(input_string):
        key_index = alphabet.index(key[index % len(key)])
        if encrypt is not True:
            key_index *= -1
        if character not in alphabet:
            out += character
        else:
            out += alphabet[(alphabet.index(character)
                             + key_index) % len(alphabet)]
    return out


def main() -> None:
    """Main Fynction"""
    string0 = input("Enter message: ")
    encryption_key = input("Enter a key: ")

    string1 = translation(string0, encryption_key, True)
    print("Clear to coded:", string1)

    string2 = translation(string1, encryption_key, False)
    print("Coded to clear: ", string2)


if __name__ == "__main__":
    import doctest

    doctest.testmod()

    main()

6. Cisco type 7 password

6.5. Solution

#!/usr/local/bin/python3
"""
Cisco IOS type 7 password encoder decoder (enable password, user password)
https://github.com/ilneill/Py-CiscoT7/blob/master/src/Py-CiscoT7.py
Each plaintext character is XOR'ed with a different character
from a well-knowed key.
The first character used from the key is determined by a random number offset
between 0 and 15.
This offset is pre-pended to the encrypted password as 2 decimal digits.
The offset is incremented as each character of the plaintext password
is encrypted.
The ASCII code of each encrypted character is appended to the
encrypted password as 2 hex digits.
A plaintext password has a permissible length of 1 to 25 characters.
"""

import random


# Cisco XOR key.
# The encryption/decryption key used by Cisco
# See https://www.gitmemory.com/issue/heyglen/network_tech/21/489420875
CISCO_KEY = 'dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87'
xkey = [int(hex(ord(x)), 16) for x in CISCO_KEY]


def type7_encode(input_string):
    """doc"""
    if 1 <= len(input_string) <= 25:
        salt = random.randint(0, 15)
        enc_pwd = format(salt, "02d")
        for counter in range(0, len(input_string), 1):
            enc_char = ord(input_string[counter]) \
                           ^ xkey[(counter + salt) % len(CISCO_KEY)]
            enc_pwd += format(enc_char, "02X")
    else:
        enc_pwd = "Error! Bad password length."
    return enc_pwd


def type7_decode(enc_pwd):
    """doc"""
    index = int(enc_pwd[:2])
    enc_pwd = enc_pwd[2:].rstrip()
    pwd_hex = [enc_pwd[x:x + 2] for x in range(0, len(enc_pwd), 2)]
    cleartext = [chr(xkey[index+i] ^ int(pwd_hex[i], 16))
                 for i in range(0, len(pwd_hex))]
    return ''.join(cleartext)


def test() -> None:
    """
    test function

    >>> string0 = 'password'
    >>> string1 = type7_encode(string0)
    >>> type7_decode(string1) == string0
    True
    >>> string2 = '095C4F1A0A1218000F'
    >>> string2
    '095C4F1A0A1218000F'
    >>> type7_decode(string2) == string0
    True
    """


def main() -> None:
    """Main Function"""
    string0 = input("Enter message: ")

    string1 = type7_encode(string0)
    print("Clear to coded:", string1)

    string2 = type7_decode(string1)
    print("Coded to clear: ", string2)


if __name__ == "__main__":
    import doctest

    doctest.testmod()

    main()