### 1. Chiffrement de César

alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']



mystere_cesar = "lxehg lnxmhgx cnexl vxltk ikhmxzxtbm lt vhkkxlihgwtgvx lxvkxmx xg kxfietvtgm vatjnx exmmkx itk vxeex jnb lx mkhnox mkhbl ktgzl ienl ehbg wtgl et eblmx wxl exmmkxl tbglb ex fhm khfx wxoxgtbm nkia vx ikhvxwx mkxl lbfiex lnyybltbm ltgl whnmx t mkhfixk lxl twoxkltbkxl ftbl be gx kxlblmx itl t ngx tgterlx tmmxgmbox be lnyybm xg xyyxm wx vhfimxk exl exmmkxl wn fxlltzx vabyykx et ienl ykxjnxgmx kxfietvx ikxljnx mhnchnkl et exmmkx x"

### 2. Chiffrement par substitution

az = "azertyuiopqsdfghjklmwxcvbn"

ORDRE_FR = "easnitruoldcpmvqfgbhjxyzkw"

mystere_substitution = "ry vjymfjwj hfjnaj aj hrmrvo rdrxj ra tfvkf djkfsj ar uayh rvnfjvvj kjhndfuofiv nivvyj kj ar wjobikj pyf ujdwjo kj nrhhjd ajh nbfggdjwjvoh urd hyxhofoyofiv fa djwrdpyj pyj krvh yvj arvsyj kivvjj njdorfvjh ajoodjh hivo xjryniyu uayh gdjpyjvojh pyj ajh ryodjh jv niwuorvo ajh hewxiajh ky wjhhrsj nbfggdj iv ujyo kivn kjmfvjd pyjaajh ajoodjh hj nrnbjvo kjddfjdj ajh hewxiajh ajh uayh niydrvoh uyfh niwuajojd ajh wioh yv urd yv niwwj krvh yvj sdfaaj kj wioh ndifhjh njooj wjobikj r jy kjh nivhjpyjvnjh odrsfpyjh jv wfaaj nfvp njvo pyrodj mfvso hfc wrdfj hoyrdo djfvj kjh jnihhrfh djojvyj udfhivvfjdj jv rvsajojddj niddjhuivkrfo jv hjndjo rmjn yv sdiyuj kj nivqydjh pyf udiqjorfjvo kj oyjd ar djfvj jafhrxjob uiyd uarnjd wrdfj hyd aj odivj ajydh ajoodjh jorfjvo nbfggdjjh urd hyxhofoyofiv wrfh obiwrh ubjafuujh ry hjdmfnj kj ar djfvj urdmfvo r ajh kjndeuojd wrdfj hoyrdo gyo qysjj nivkrwvjj uyfh jcjnyojj jv gjmdfjd wfaaj nfvp njvo pyrodj mfvso hjuo"

### Exercice 1:

'''1.a'''
def int_vers_char(i):
    return alphabet[i]

'''1.b'''
def char_vers_int(c):
    for i in range(len(alphabet)):
        if alphabet[i] == c:
            return i

'''2'''
def chiffrer_char(c, cle):
    if c == ' ':
        return c
    else :
        return alphabet[(char_vers_int(c)+cle)%26]

'''3'''
def chiffrer_texte(m,cle):
    res = ''
    for idx in range(len(m)):
        res += chiffrer_char(m[idx],cle)
    return res

'''4.a'''
def indice_max(m):
    compt = 26*[0]
    for i in range(len(m)):
        ind = char_vers_int(m[i])
        if ind != None:
            compt[char_vers_int(m[i])]+=1
    ind_max = 0
    max = compt[0]
    for j in range(26):
        val = compt[j]
        if val>max:
            ind_max = j
            max = val
    return ind_max

'''4.b'''
def decrypter_texte(m):
    cle = indice_max(m)
    cle = -(cle - 4)
    res = chiffrer_texte(m,cle)
    return res

'''4.c'''
"le message obtenue est: 'selon suetone jules cesar protegeait sa correspondance secrete en remplacant chaque lettre par celle qui se trouve trois rangs plus loin dans la liste des lettres ainsi le mot rome devenait urph ce procede tres simple suffisait sans doute a tromper ses adversaires mais il ne resiste pas a une analyse attentive il suffit en effet de compter les lettres du message chiffre la plus frequente remplace presque toujours la lettre e'"

'''5.a'''
"la fonction renvoie 'raje reze reye'. L'attaque echoue car la lettre la plus repeté n'est probablment pas un e donc le decalage d'indice de fonctionne pas"

'''5.b'''
def force_brute(m):
    for i in range(len(alphabet)):
        a = chiffrer_texte(m,i)
        print("cle: ",i," message: ",a)



### Exercice 2:

'''1'''
def chiffre_substitution(m,cle):
    res = ''
    for i in range(len(m)):
        nouv_i = char_vers_int(m[i])
        res += cle[nouv_i]

    return res

'''2'''
def cle_cesar(k):
    res = ''
    for i in range(k,len(alphabet)):
        res += alphabet[i]
    for i in range(k):
        res += alphabet[i]
    return res

'''3'''
def est_cle_valide(m):
    if len(m) == 26:
        for i in range(len(m)):
            for j in range(i+1,len(m)):
                if m[j] == m[i]:
                    return False
        return True
    return False

'''4'''
def position(c, cle):
    for i in range(len(cle)):
        if cle[i] == c:
            return i
    return None

def dechiffre_substitution(m, cle):
    res = ""
    for c in m:
        if c == ' ':
            res = res + ' '
        else:
            res = res + alphabet[position(c, cle)]
    return res

'''5'''
''' Une clé de substitution est un choix de 26 lettres parmi 26, avec ordre et sans remise.
    Il s'agit d'une permutation de la liste alphabet contenant 26 éléments. Il existe donc
    26! clés de substitution différentes. On a ci-dessous une fonction permettant le calcul
    de n! avec n un entier positif. Pour n = 26, elle renvoie : 403291461126605635584000000'''

def factorielle(n):
    if n == 0:
        return 1
    res = 1
    for i in range(n):
        res = res * (n - i)
    return res


''' 26! est de l'ordre de 10**26. Un milliard est égal à 10**9. Pour essayer toutes les clés,
    il faudrait à l'ordinateur environ (10**26) / (10**9) = 10**17 secondes, ce qui équivaut
    à plus de 3 millards d'années, ce qui lui est impossible. '''

'''6'''
def occurrences(m):
    occ = []
    for lettre in alphabet:
        compte = 0
        for c in m:
            if c == lettre:
                compte += 1
        occ.append(compte)
    return occ


def ordre_frequences(m):
    occ = occurrences(m)
    chaine = ''
    for _ in range(26):
        i_max = -1
        for j in range(26):
            if occ[j] > occ[i_max]:
                i_max = j
        chaine += alphabet[i_max]
        occ[i_max] = -1
    return chaine


def cle_probable(m):
    L = ordre_frequences(m)
    message = ''
    for c in m:
        if c == ' ':
            message += c
        else:
            for i in range(26):
                if L[i] == c:
                    message += ORDRE_FR[i]
    return message

''' Le texte renvoyé par cle_probable(mystere_substitution) est illisible. Il est possible
    que le message originel respecte pas strictement l'étude statistique de ORDRE_FR. '''

def echange(cle, a, b):
    l_cle = []
    for c in cle:
        l_cle.append(c)

    i = char_vers_int(a)
    j = char_vers_int(b)

    temp = l_cle[i]
    l_cle[i] = l_cle[j]
    l_cle[j] = temp

    z = ""
    for c in l_cle:
        z = z + c
    return z

#C'est l'ordre des caractères dans la clé qui détermine leur correspondance avec l'alphabet. Echanger 2 lettres dont les positions renvoient aux lettres a et b dans l'alphabet correspond donc a échanger les lettres a et b dans le message déchiffré.

# Le texte final est au neuvieme siecle le savant arabe al zindi redige la plus ancienne description connue de la methode qui permet de casser les chiffrements par substitution il remarque que dans une langue donnee certaines lettres sont beaucoup plus frequentes que les autres en comptant les symboles du message chiffre on peut donc deviner quelles lettres se cachent derriere les symboles les plus courants puis completer les mots un par un comme dans une grille de mots croises cette methode a eu des consequences tragiques en mille cinq cent quatre vingt six marie stuart reine des ecossais retenue prisonniere en angleterre correspondait en secret avec un groupe de conjures qui projetaient de tuer la reine elisabeth pour placer marie sur le trone leurs lettres etaient chiffrees par substitution mais thomas phelippes au service de la reine parvint a les decrypter marie stuart fut jugee condamnee puis executee en fevrier mille cinq cent quatre vingt sept
























