Ce petit notebook va implémenter les algorithmes de vérification des numéros de :
J'avais déjà implementé les deux derniers, cf. ces scripts : check_IBAN.py, et check_NIRPP.py.
Si vous êtes curieux de l'aspect historique, ce petit article explique très bien les origines de ces chiffres de contrôles et de la formule de Luhn.
Je vais utiliser cette fonction plusieurs fois, qui permet de transformer une lettre 'A',...,'Z' en entier.
def l_to_c(l):
try:
return str(int(l))
except ValueError:
return str(10 + ord(l.upper()) - ord('A'))
for l in 'ABCDEFGHIJKLMNROPQRSTUVWXYZ':
print("l = {} --> c = {}".format(l, l_to_c(l)))
l = A --> c = 10 l = B --> c = 11 l = C --> c = 12 l = D --> c = 13 l = E --> c = 14 l = F --> c = 15 l = G --> c = 16 l = H --> c = 17 l = I --> c = 18 l = J --> c = 19 l = K --> c = 20 l = L --> c = 21 l = M --> c = 22 l = N --> c = 23 l = R --> c = 27 l = O --> c = 24 l = P --> c = 25 l = Q --> c = 26 l = R --> c = 27 l = S --> c = 28 l = T --> c = 29 l = U --> c = 30 l = V --> c = 31 l = W --> c = 32 l = X --> c = 33 l = Y --> c = 34 l = Z --> c = 35
exemple_cb = "4970 1012 3456 7890" # pas valide
D'abord l'algorithme de Luhn :
def verifie_Luhn(numeros):
numeros = numeros.replace(' ', '')
nb_chiffres = len(numeros)
parite = nb_chiffres % 2
chiffres = [int(l_to_c(l)) for l in numeros]
somme = chiffres[nb_chiffres - 1]
for i in range(nb_chiffres - 2, -1, -1):
chiffre = chiffres[i]
if i % 2 == parite:
chiffre *= 2
if chiffre > 9:
chiffre -= 9
somme += chiffre
return (somme % 10) == 0
verifie_Luhn('972 487 086')
True
verifie_Luhn('972 487 081')
verifie_Luhn('972 487 082')
verifie_Luhn('972 487 087')
False
False
False
verifie_Luhn(exemple_cb)
False
Ensuite la vérification pour un numéro de carte bleue :
def verifie_cb(cb):
print("\nVérification du numéro de CB '%s'..." % cb)
check = verifie_Luhn(cb)
if check:
print("OK '%s' semble être un numéro de CB valide." % cb)
else:
print("[ATTENTION] PAS OK '%s' semble ne pas être un numéro de CB valide!" % cb)
return check
verifie_cb(exemple_cb)
Vérification du numéro de CB '4970 1012 3456 7890'... [ATTENTION] PAS OK '4970 1012 3456 7890' semble ne pas être un numéro de CB valide!
False
Avec un autre faux numéro mais conçu pour être vrai :
verifie_cb("4976 5301 7218 3533")
Vérification du numéro de CB '4976 5301 7218 3533'... OK '4976 5301 7218 3533' semble être un numéro de CB valide.
True
exemple_iban = "FR76 1254 8029 9838 3759 0150 071"
def verifie_iban(iban):
print("\nVérification du nombre IBAN '%s'..." % iban)
ib = iban.replace(' ', '')
ib = ib[4:] + ib[:4]
print(" De longueur", len(ib))
i = int(''.join(l_to_c(l) for l in ib))
check = (i % 97) == 1
if check:
print("OK '%s' semble être un nombre IBAN valide." % iban)
else:
print("[ATTENTION] PAS OK '%s' semble ne pas être un nombre IBAN valide!" % iban)
return check
verifie_iban(exemple_iban)
Vérification du nombre IBAN 'FR76 1254 8029 9838 3759 0150 071'... De longueur 27 OK 'FR76 1254 8029 9838 3759 0150 071' semble être un nombre IBAN valide.
True
verifie_iban("GB87 BARC 2065 8244 9716 55")
Vérification du nombre IBAN 'GB87 BARC 2065 8244 9716 55'... De longueur 22 OK 'GB87 BARC 2065 8244 9716 55' semble être un nombre IBAN valide.
True
verifie_iban("GB87 BARC 2065 8244 9716 51")
Vérification du nombre IBAN 'GB87 BARC 2065 8244 9716 51'... De longueur 22 [ATTENTION] PAS OK 'GB87 BARC 2065 8244 9716 51' semble ne pas être un nombre IBAN valide!
False
verifie_iban("BE43 0689 9999 9501")
Vérification du nombre IBAN 'BE43 0689 9999 9501'... De longueur 16 OK 'BE43 0689 9999 9501' semble être un nombre IBAN valide.
True
verifie_iban("BE43 0689 9999 9500")
Vérification du nombre IBAN 'BE43 0689 9999 9500'... De longueur 16 [ATTENTION] PAS OK 'BE43 0689 9999 9500' semble ne pas être un nombre IBAN valide!
False
exemple_nirpp = "2 69 05 49 588 157 80"
length_checksum = 2
def verifie_nirpp(nirpp, length_checksum=length_checksum):
print("\nVérification du nombre NIRPP '%s' ..." % nirpp)
ib = nirpp.replace(' ', '')
checksum = int(ib[-length_checksum:])
ib = ib[:-length_checksum]
print(" De longueur", len(ib))
num_nirpp = int(''.join(l_to_c(l) for l in ib))
print(" De somme de contrôle num_nirpp =", num_nirpp)
print(" Module à 97 =", (97 - (num_nirpp % 97)))
print(" Et la somme de contrôle attendue était", checksum)
check = (97 - (num_nirpp % 97)) == checksum
if check:
print("OK '%s' semble être un nombre NIRPP valide." % nirpp)
else:
print("[ATTENTION] PAS OK '%s' semble ne pas être un nombre NIRPP valide!" % nirpp)
return check
verifie_nirpp(exemple_nirpp)
Vérification du nombre NIRPP '2 69 05 49 588 157 80' ... De longueur 13 De somme de contrôle num_nirpp = 2690549588157 Module à 97 = 80 Et la somme de contrôle attendue était 80 OK '2 69 05 49 588 157 80' semble être un nombre NIRPP valide.
True
Il suffit de récupérer les informations de chaque morceau du code NIRPP, et les stocker comme ça :
information_nirpp = {
(0, 1): {
"meaning": "sexe",
"mapping": {
"1": "homme",
"2": "femme",
"3": "personne étrangère de sexe masculin en cours d'immatriculation en France",
"4": "personne étrangère de sexe féminin en cours d'immatriculation en France"
}
},
(1, 2): {
"meaning": "deux derniers chiffres de l'année de naissance",
"mapping": {
# DONE nothing to do for this information
}
},
(3, 2): {
"meaning": "mois de naissance",
"mapping": {
"01": "janvier",
"02": "février",
"03": "mars",
"04": "avril",
"05": "mai",
"06": "juin",
"07": "juillet",
"08": "août",
"09": "septembre",
"10": "octobre",
"11": "novembre",
"12": "décembre",
}
},
# Only case A : TODO implement case B and C
(5, 2): {
"meaning": "département de naissance métropolitain",
"mapping": { # Cf. http://www.insee.fr/fr/methodes/nomenclatures/cog/documentation.asp
"01": "Ain",
"02": "Aisne",
"03": "Allier",
"04": "Alpes-de-Haute-Provence",
"05": "Hautes-Alpes",
"06": "Alpes-Maritimes",
"07": "Ardèche",
"08": "Ardennes",
"09": "Ariège",
"10": "Aube",
"11": "Aude",
"12": "Aveyron",
"13": "Bouches-du-Rhône",
"14": "Calvados",
"15": "Cantal",
"16": "Charente",
"17": "Charente-Maritime",
"18": "Cher",
"19": "Corrèze",
"2A": "Corse-du-Sud",
"2B": "Haute-Corse",
"21": "Côte-d'Or",
"22": "Côtes-d'Armor",
"23": "Creuse",
"24": "Dordogne",
"25": "Doubs",
"26": "Drôme",
"27": "Eure",
"28": "Eure-et-Loir",
"29": "Finistère",
"30": "Gard",
"31": "Haute-Garonne",
"32": "Gers",
"33": "Gironde",
"34": "Hérault",
"35": "Ille-et-Vilaine",
"36": "Indre",
"37": "Indre-et-Loire",
"38": "Isère",
"39": "Jura",
"40": "Landes",
"41": "Loir-et-Cher",
"42": "Loire",
"43": "Haute-Loire",
"44": "Loire-Atlantique",
"45": "Loiret",
"46": "Lot",
"47": "Lot-et-Garonne",
"48": "Lozère",
"49": "Maine-et-Loire",
"50": "Manche",
"51": "Marne",
"52": "Haute-Marne",
"53": "Mayenne",
"54": "Meurthe-et-Moselle",
"55": "Meuse",
"56": "Morbihan",
"57": "Moselle",
"58": "Nièvre",
"59": "Nord",
"60": "Oise",
"61": "Orne",
"62": "Pas-de-Calais",
"63": "Puy-de-Dôme",
"64": "Pyrénées-Atlantiques",
"65": "Hautes-Pyrénées",
"66": "Pyrénées-Orientales",
"67": "Bas-Rhin",
"68": "Haut-Rhin",
"69": "Rhône",
"70": "Haute-Saône",
"71": "Saône-et-Loire",
"72": "Sarthe",
"73": "Savoie",
"74": "Haute-Savoie",
"75": "Paris",
"76": "Seine-Maritime",
"77": "Seine-et-Marne",
"78": "Yvelines",
"79": "Deux-Sèvres",
"80": "Somme",
"81": "Tarn",
"82": "Tarn-et-Garonne",
"83": "Var",
"84": "Vaucluse",
"85": "Vendée",
"86": "Vienne",
"87": "Haute-Vienne",
"88": "Vosges",
"89": "Yonne",
"90": "Territoire de Belfort",
"91": "Essonne",
"92": "Hauts-de-Seine",
"93": "Seine-Saint-Denis",
"94": "Val-de-Marne",
"95": "Val-d'Oise",
# TODO support these too
"971": "Guadeloupe",
"972": "Martinique",
"973": "Guyane",
"974": "La Réunion",
"975": "Saint-Pierre-et-Miquelon",
"976": "Mayotte",
"977": "Saint-Barthélemy",
"978": "Saint-Martin",
"984": "Terres australes et antarctiques françaises",
"986": "Wallis-et-Futuna",
"987": "Polynésie française",
"988": "Nouvelle-Calédonie",
"989": "Île de Clipperton"
}
},
(7, 3): {
"meaning": "code officiel de la commune de naissance",
"mapping": { # TODO
}
},
(10, 3): {
"meaning": "numéro d’ordre de la naissance dans le mois et la commune (ou le pays)",
"mapping": {
# DONE nothing to do for this information
}
}
}
Pour les villes, on a besoin d'une base de donnée plus grande. J'ai récupéré ce fichier sur le site de l'INSEE (lien mort).
!ls data/
comsimp2016.txt Exemple_CB.jpg Exemple_RIB.jpg Exemple_CarteVitale.jpg Exemple_IMEI.jpg
!wc data/comsimp2016.txt
35886 40952 1685861 data/comsimp2016.txt
Il ressemble à ça :
!head data/comsimp2016.txt
CDC,CHEFLIEU,REG,DEP,COM,AR,CT,TNCC,ARTMAJ,NCC,ARTMIN,NCCENR 0,0,84,01,001,2,08,5,(L') ABERGEMENT-CLEMENCIAT,(L') Abergement-Clémenciat 0,0,84,01,002,1,01,5,(L') ABERGEMENT-DE-VAREY,(L') Abergement-de-Varey 0,1,84,01,004,1,01,1,AMBERIEU-EN-BUGEY,Ambérieu-en-Bugey 0,0,84,01,005,2,22,1,AMBERIEUX-EN-DOMBES,Ambérieux-en-Dombes 0,0,84,01,006,1,04,1,AMBLEON,Ambléon 0,0,84,01,007,1,01,1,AMBRONAY,Ambronay 0,0,84,01,008,1,01,1,AMBUTRIX,Ambutrix 0,0,84,01,009,1,04,1,ANDERT-ET-CONDON,Andert-et-Condon 0,0,84,01,010,1,10,1,ANGLEFORT,Anglefort
Briançon est bien dans la liste :
!grep "BRIANCON" data/comsimp2016.txt
1,2,93,05,023,1,98,0,BRIANCON,Briançon 0,0,93,06,024,1,11,0,BRIANCONNET,Briançonnet
Allons-y :
import subprocess
length_checksum = 2
def pprint_nirpp(nirpp, length_checksum=length_checksum):
print("\nAffichage d'informations contenues dans le numéro NIRPP '%s' ..." % nirpp)
nirpp = nirpp.replace(' ', '')
ib = nirpp[:-length_checksum]
# Printing
for (i, l) in sorted(information_nirpp):
n = nirpp[i: i + l]
info = information_nirpp[(i, l)]
if n in info["mapping"]:
explain = "\"{}\"".format(info["mapping"][n])
else:
explain = n
# For towns, durty hack to extract the town from the INSEE database
if i == 7:
try:
args = [
"grep", "--", "',{},{},'".format(
nirpp[5: 5 + 2],
nirpp[7: 7 + 3]
),
"data/comsimp2016.txt",
"|", "cut", "-d,", "-f10"
]
command = ' '.join(args)
# print("Executing subprocess.check_output to \"{}\"".format(command))
explain = subprocess.check_output(command, shell=True)
explain = explain[:-1].decode()
# print("explain =", explain)
explain = "{} (code {})".format(explain, nirpp[7: 7 + 3])
except Exception as e:
# print("e =", e)
explain = n
print("- Le nombre '{}' (indice {}:{}) signifie:\n\t\"{}\" : \t{}".format(
n, i, i + l, info["meaning"], explain)
)
pprint_nirpp(exemple_nirpp)
Affichage d'informations contenues dans le numéro NIRPP '2 69 05 49 588 157 80' ... - Le nombre '2' (indice 0:1) signifie: "sexe" : "femme" - Le nombre '69' (indice 1:3) signifie: "deux derniers chiffres de l'année de naissance" : 69 - Le nombre '05' (indice 3:5) signifie: "mois de naissance" : "mai" - Le nombre '49' (indice 5:7) signifie: "département de naissance métropolitain" : "Maine-et-Loire" - Le nombre '588' (indice 7:10) signifie: "code officiel de la commune de naissance" : (code 588) - Le nombre '157' (indice 10:13) signifie: "numéro d’ordre de la naissance dans le mois et la commune (ou le pays)" : 157
Avec un exemple assez proche de mon numéro de sécurité sociale (modifié) :
pprint_nirpp("1 93 01 05 023 122 23")
Affichage d'informations contenues dans le numéro NIRPP '1 93 01 05 023 122 23' ... - Le nombre '1' (indice 0:1) signifie: "sexe" : "homme" - Le nombre '93' (indice 1:3) signifie: "deux derniers chiffres de l'année de naissance" : 93 - Le nombre '01' (indice 3:5) signifie: "mois de naissance" : "janvier" - Le nombre '05' (indice 5:7) signifie: "département de naissance métropolitain" : "Hautes-Alpes" - Le nombre '023' (indice 7:10) signifie: "code officiel de la commune de naissance" : Briançon (code 023) - Le nombre '122' (indice 10:13) signifie: "numéro d’ordre de la naissance dans le mois et la commune (ou le pays)" : 122
Les numéros d'identification des téléphones portables (les IMEI) terminent aussi par un chiffre de contrôle, qui utilise aussi la formule de Luhn. Je termine ce notebook en implémentant aussi cette vérification.
exemple_imei = "448674 52 897641 0" # avant 2014, 6-2-6-1
def verifie_imei(imei):
print("\nVérification du numéro IMEI '%s'..." % imei)
check = verifie_Luhn(imei)
if check:
print("OK '%s' semble être un numéro IMEI valide." % imei)
else:
print("[ATTENTION] PAS OK '%s' semble ne pas être un numéro IMEI valide!" % imei)
return check
verifie_imei(exemple_imei)
Vérification du numéro IMEI '448674528976410'... OK '448674528976410' semble être un numéro IMEI valide.
True
exemple_imei = "448674 52 897641 1"
verifie_imei(exemple_imei)
Vérification du numéro IMEI '448674528976411'... [ATTENTION] PAS OK '448674528976411' semble ne pas être un numéro IMEI valide!
False
exemple_imei = "468674 52 897641 0"
verifie_imei(exemple_imei)
Vérification du numéro IMEI '468674528976410'... [ATTENTION] PAS OK '468674528976410' semble ne pas être un numéro IMEI valide!
False
Avec un IMEI semblable à celui d'un de mes anciens téléphones :
mon_faux_imei_1 = "35569508 262195 2"
verifie_imei(mon_faux_imei_1)
Vérification du numéro IMEI '35569508 262195 2'... OK '35569508 262195 2' semble être un numéro IMEI valide.
True
mon_faux_imei_2 = "35569508 283295 5"
verifie_imei(mon_faux_imei_2)
Vérification du numéro IMEI '35569508 283295 5'... OK '35569508 283295 5' semble être un numéro IMEI valide.
True