Pour détecter une substitution monoalphabétique, il est nécessaire de calculer l'indice de coïncidence.
Qu'est-ce que l'indice de coïncidence ?
Selon Indice de Coincidence - Calcul d'IC pour Cryptanalyse en Ligne, l'indice de coïncidence (IC ou IoC) est un indicateur utilisé en cryptanalyse qui permet d'évaluer la répartition globale des lettres dans un message chiffré pour un alphabet donné.
Un texte écrit en français a un indice de coïncidence de 0.0778. L'utilisation de certains chiffrements sur un message en français aura tendance à modifier cette valeur ce qui peut permettre de les reconnaitre.
Voici un script en Python qui permet de le calculer
from string import ascii_lowercase
from collections import Counter
import numpy as np
from unidecode import unidecode
# Texte à analyser
t = """Mes deux filles
Dans le frais clair-obscur du soir charmant qui tombe,
L'une pareille au cygne et l'autre à la colombe,
Belle, et toutes deux joyeuses, ô douceur !
Voyez, la grande sœur et la petite sœur
Sont assises au seuil du jardin, et sur elles
Un bouquet d'œillets blancs aux longues tiges frêles,
Dans une urne de marbre agité par le vent,
Se penche, et les regarde, immobile et vivant,
Et frissonne dans l'ombre, et semble, au bord du vase,
Un vol de papillons arrêté dans l'extase."""
# création du dict initial avec toutes les lettres de l'alphabet
e = dict(zip(list(ascii_lowercase), [0]*26))
# on compte l'ensemble des caractères du texte en convertissant les caractères accentués
c = Counter(unidecode(t).lower())
# on met à jour le dict initial avec le nombre de caractères trouvés (uniquement les caractères de a à z)
e.update({k:v for k,v in c.items() if k in ascii_lowercase})
# on créé un array numpy avec seulement les valeurs
f = np.array(list(e.values()))
# on calcul l'indice de coincidence
round(sum(f*(f-1)) / (sum(f)*(sum(f)-1)), 4)
Le résultat: 0.0765
La même chose sous la forme d'une fonction:
def indice_de_coincidence(ch: str):
e = dict(zip(list(ascii_lowercase), [0]*26))
c = Counter(unidecode(ch).lower())
e.update({k:v for k,v in c.items() if k in ascii_lowercase})
f = np.array(list(e.values()))
return round(sum(f*(f-1)) / (sum(f)*(sum(f)-1)), 4)
Par exemple, le même texte, chiffré par ROT13
Zrf qrhk svyyrf
Qnaf yr senvf pynve-bofphe qh fbve puneznag dhv gbzor,
Y'har cnervyyr nh pltar rg y'nhger n yn pbybzor,
Oryyr, rg gbhgrf qrhk wblrhfrf, b qbhprhe !
Iblrm, yn tenaqr fbrhe rg yn crgvgr fbrhe
Fbag nffvfrf nh frhvy qh wneqva, rg fhe ryyrf
Ha obhdhrg q'brvyyrgf oynapf nhk ybathrf gvtrf seryrf,
Qnaf har hear qr zneoer ntvgr cne yr irag,
Fr crapur, rg yrf ertneqr, vzzbovyr rg ivinag,
Rg sevffbaar qnaf y'bzoer, rg frzoyr, nh obeq qh infr,
Ha iby qr cncvyybaf neergr qnaf y'rkgnfr.
L'indice de coïncidence calculé sera exactement le même que le texte original 0.0765