{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import math\n",
    "import random\n",
    "import time\n",
    "import datetime as dt\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "#!pip install cryptography\n",
    "#!pip install gmpy2\n",
    "from gmpy2 import is_square\n",
    "from cryptography.hazmat.primitives.asymmetric import rsa, padding\n",
    "from cryptography.hazmat.primitives import hashes, serialization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center> TP6 - Synthèse : complémentarité des cryptographies symétrique et asymétrique </center>\n",
    "<center> 2025/2026 - L. Naert, S. Bouchelaghem, T. Godin </center>\n",
    "\n",
    "__Usage de l'IA générative : interdit__\n",
    "\n",
    "Ce TP est une activité de synthèse à faire en binôme. Il utilise les notions vues dans les TP4 (LFSR) et TP5 (RSA). Vous trouverez ci-dessous les fonctions utiles de ces deux TP. Nous utiliserons les fonctions de la bibliothèque cryptography pour RSA. Comme pour le TP précédent, vous allez avoir besoin des fonctions de [serialization](https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#key-serialization) pour vous transmettre les clefs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Fonctions RSA__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b'message a chiffrer'\n"
     ]
    }
   ],
   "source": [
    "#Génération des clefs\n",
    "private_key = rsa.generate_private_key(\n",
    "    public_exponent=65537,\n",
    "    key_size=2048\n",
    ")\n",
    "public_key = private_key.public_key()\n",
    "\n",
    "#chiffrement (le padding ajoute des bits de façons à mettre le message dans le bon format pour être chiffré)\n",
    "messageClair = b\"message a chiffrer\"\n",
    "ciphertext = public_key.encrypt(messageClair,\n",
    "                                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))\n",
    "\n",
    "#dechiffrement\n",
    "dechiffre = private_key.decrypt(ciphertext, \n",
    "                                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None))\n",
    "\n",
    "print(dechiffre)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Fonctions LFSR__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#donne l'état suivant du LFSR\n",
    "def etatSuivant(etat,coeff):\n",
    "    bit_suiv = 0\n",
    "    sortie = 0\n",
    "    try:\n",
    "        assert max(coeff) < len(etat)\n",
    "        for i in coeff:\n",
    "            bit_suiv = bit_suiv ^ etat[i] #XOR sur les bits concernés\n",
    "        sortie = etat[0]\n",
    "        reg = etat[1:]+[bit_suiv] # on décale le registre\n",
    "        return reg, sortie\n",
    "    except:\n",
    "        print(\"erreur etatSuivant : la taille du registre etat ne correspond pas aux coefficients fournis\")\n",
    "\n",
    "#Génère une suite de taille n issue du LFSR donné en paramètre.\n",
    "def suite_LFSR(graine,coeff,n):\n",
    "    res = \"\"\n",
    "    etatReg = graine\n",
    "    for i in range(n) :\n",
    "        etatReg, sortie = etatSuivant(etatReg,coeff)\n",
    "        res = res + str(sortie)\n",
    "    return res\n",
    "\n",
    "#Génération d'une graine aléatoire de taille précisée\n",
    "def generation_reg_graine(taille):\n",
    "    \"\"\"\n",
    "    Génération d'une graine de taille \"taille\" basée sur l'heure\n",
    "    \"\"\" \n",
    "\n",
    "    ### Transformation de la date en une chaine de caractères\n",
    "    date = str(dt.datetime.now())\n",
    "    #print(date)\n",
    "    ### Transformation de la fin de la chaine en un entier compris entre 0 et 255 pour pouvoir le représenter avec 8 bits\n",
    "    init_entier = int(date[-4:]) % 2**taille # j'ai choisi de prendre les 4 derniers caractères arbitrairement\n",
    "\n",
    "    ### Représentation de l'entier sur un octet\n",
    "    init_bin = bin(init_entier)[2:] # on retire le 0b qui permet de préciser qu'il s'agit d'un nombre binaire\n",
    "    while len(init_bin) < taille : \n",
    "        init_bin = '0' + init_bin # on rajoute des 0 pour que le nombre produit soit composé de taille bits. (padding)\n",
    "    #print(init_bin)\n",
    "    ### Transformation de la chaine des bits en une liste\n",
    "    init_reg = [int(x) for x in init_bin]\n",
    "    return init_reg\n",
    "\n",
    "# chiffrement de Vernam\n",
    "def chiffrementVernam(msgBinaire, clef):\n",
    "    c = \"\"\n",
    "    for i in range(len(msgBinaire)):\n",
    "        c = c + str((int(msgBinaire[i]) ^ int(clef[i])))\n",
    "    return c\n",
    "\n",
    "# chiffrement par LFSR\n",
    "def chiffrementLFSR(msgAscii, graine, coeff):\n",
    "    msgbinary = stringToBinary(msgAscii)\n",
    "    clef = suite_LFSR(graine,coeff,len(msgbinary))\n",
    "    msgchiffre = chiffrementVernam(msgbinary,clef)\n",
    "    return msgchiffre\n",
    "\n",
    "# déchiffrement par LFSR\n",
    "def dechiffrementLFSR(msgChiffBinary, graine,coeff):\n",
    "    clef = suite_LFSR(graine,coeff,len(msgChiffBinary))\n",
    "    msgclair = binaryToString(chiffrementVernam(msgChiffBinary,clef))\n",
    "    return msgclair\n",
    "\n",
    "# Passer un message en ascii en message binaire\n",
    "def stringToBinary(msg):\n",
    "    msg_bin = \"\"\n",
    "    for i in bytearray(msg, encoding ='ascii') :\n",
    "        msg_bin = msg_bin + format(i, '08b')\n",
    "    return msg_bin\n",
    "\n",
    "# Passer un message binaire en ascii\n",
    "def binaryToString(binary):\n",
    "    msg = \"\"\n",
    "    for i in range(0, len(binary), 8):\n",
    "        byte_int = int(binary[i:i+8], 2)\n",
    "        byte_char = chr(byte_int)\n",
    "        msg = msg + byte_char\n",
    "        \n",
    "    return msg"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1 - Activité de synthèse : Application du chiffrement RSA à l'échange de clef (à faire en binome)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le chiffrement RSA, et les chiffrements asymétriques en général, permettent de résoudre le problème de l'échange des clefs du chiffrement symétrique puisque plus aucun échange n'est nécessaire. Cependant, ces types de chiffrement ne peuvent pas se substituer complètement aux chiffrements symétriques car les opérations de chiffrement et déchiffrement prennent trop de temps pour être utilisées régulièrement pour coder de longs messages. Les chiffrements symétriques et asymétriques sont donc souvent tous les deux utilisés pour chiffrer un message : \n",
    "- Le chiffrement asymétrique sert à l'échange de clef secrète : il permet de transmettre la clef secrète du chiffrement symétrique de manière sécurisée\n",
    "- Le chiffrement symétrique est utilisé pour le chiffrement du message lui-même.\n",
    "\n",
    "> __Activité (synthèse)__ :\n",
    "1. Chiffrez un message en utilisant un masque jetable généré par un LFSR de taille 8 et envoyez à votre binôme __le message chiffré__. \n",
    "2. Utilisez la clef publique RSA de votre binôme pour lui transmettre de manière confidentielle __la clef secrete de votre masque__ sous le format suivant \"Ma clef secrete est ???\". D'ailleurs, de quoi est constitué votre clef secrète ?\n",
    "3. Votre binôme arrive t-il à déchiffrer votre message ?\n",
    "\n",
    "Chacun de vous doit réaliser un chiffrement et un déchiffrement ! __Appelez l'enseignant quand vous avez réussi à effectuez l'activité dans les deux sens.__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Message chiffré : 100000000000010010101010101111011110011011011010011000010111001000000110110111110010100000011011111100111101000101010000100100010001111111100100101000101110011110001111011111100011101101010101110011110010110000011111111111111101000101000110100001110000100010110110101100101111110110000001\n",
      "Clef chiffrée de Greg : b'AD\\xd7O&\\x1fr\\xab2~\\x8e\\xba&*?\\x854[H\\x08\\xd98I\\xf8\\x10\\x7feGw\\x84\\xd7\\xb2\\xe1\\x8a5{q\\xc6\\x1e\\xfa\\xa1I\\x0f\\xf9\\x90\\x88\\xd1-\\xfb.\\x1f\\xb8Xa\\x86~\\xf3\\xc5c\\x05NU\\xecQ\\xfc0\\xa7-h\\xbe\\xe4S\\xff\\xa0\\xfc\\xbc\\x97\\xd5J3\\xfa\\xb0\\x84B\\xc1\\xbc\\xeeO\\xda-/\\xb1C\\x1fE\\xe9:\\n\\xae\\xe7\\x93\\'B!\\x01\\xdd\\xd9\\x97;8\\xc2\\x8dd\\xf1\\x1f\\x9f\\\\B\\xd3\\x14\\x1e]<\\xba\\x87o19c\\x12\\xcei\\x05\\xfc\\x16\\x99\\xdf\\xf6\\xd2\\xd7`K\\xff\\xa8\\xaa\\xd6\\x01m\\x14\\x88j\\xb0#\\xff\\x9c\\xd8\\x8d\\xf3\\xc5WA\\xbe \\xa1\\x08u\\xa1D\\xb9\\x9d<\\xe3&\\xee\\xe7\\x9d5B\\xb0C\\xd7\\x80\\xbf#Hy\\xb8w\\x98\"\\xaf5\\xfb\\x03x\\xcb\\x11 \\x8a\\xae\\xbe\\x08\\xca\\xf8^\\xa2\\x98\\x10e\\x80\\xca\\xb3\\xeb\\x9bA\\xc3\\xf9\"\\xd0[O\\'\\x10\\x8a\\xf9<\\xc0\\x14\\x0c/\\x82I\\x99H\\xb1\\xd7\\xc3NL7\\xbb\\x17\\x19\\xa0\\xca\\xd8>u\\xde\\xde,\\xb1\\xf1y\\xc2\\x96'\n",
      "public_key : b'-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtblB6jFhkhdmZp19QFpj\\n5QqGyzlCCDLxYK6EXnM/T/syF7RhQv3+qP3+PDdQLF4nMSRbhCJ/3iKeXUpFACKP\\nguBkwivFEnb4M3Yh/WwnzHzOD1R6I/DWmrqvuc0PrIBwQCDFCoshO98JsojlUJZs\\nZI4cIcGYyGzOzmrw7L/Uxz/ACa8qlXF/6PCJCRp/tBY37zANezrszzUXmFBn4NFK\\neTgYFhNot4Ap1lEOrV4zNKOH85P+lwFet9k33oDG2XXNOjo1rmzU4f2p6OjHE3mO\\ndvoGpp9X0KLTfof2FtWvXJD0FFMiXCxAPdz80twHsVzI8593dMOEAGFoCq4G4hKG\\nyQIDAQAB\\n-----END PUBLIC KEY-----\\n'\n"
     ]
    },
    {
     "ename": "ValueError",
     "evalue": "Ciphertext length must be equal to key size.",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[65], line 27\u001b[0m\n\u001b[1;32m     25\u001b[0m c \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m101010110010011100100101001001010010110100100110001111000110100000101011001010010110100000111110001010010110100000100100001011010110100000101111001110100010110100101111 \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m     26\u001b[0m \u001b[38;5;66;03m# secret_chiffre = b'/e\\xc2\\xb9!\\x8d\\x03\\xa9\\xbb\\xb2\\x0f\\x18q6:g\\x9f\\x1eS\\d\\x82%J\\xc5';8'=\\xa74\\x10\\xd4\\xdc\\x87v\\x99\\xac\\x96\\x13]\\xb2\\x08O~\\xf9\\xcd\\xa7\\xdfu\\x1b\\xcb6\\xdeKy\\xa9b|\\x0eWy\\xe1\\xf3\\xd8)\\x99\\xab\\xeb-\\xf6#z4\\xb4H\\xd4\\xecS\\xba\\xa3\\xf5,\\xd1\\xb6sZO\\xea5\\x9d\\xb72d\\xf8\\xe0\\x0b\\x19y@\\xcf\\xf0\\xd6\\xf6\\x02\\xed\\x83\\xbb\\x8b@\\x02\\x16\\xef\\xd4a\\'G2\\xc0\\x10\\xcb4\\xa0\\x1b\\x83G\\xad\\x079GO_\\xc3r\\x81A8\\x02J\\xc0Z\\xfe\\x12\\xda\\xdb\\x05\\xc6\\x80\\x8fn#\\xcc\\xc8U$\\xfd\\xaa\\xb8/\\x84\\x8f\\xcc\\x9f\\xdf\"\\x8d\\x01\\xee\\xce\\xcf\\xf7O\\xbd\\x86\\fee;\\x95\\byG\\ce\\1d\\9fX\\ZW\\'\\x13\\efQ\\fc\\xd0\\xb3\\xf1\\xd1X\\xf4\\86\\xb6\\xc5K\\ab\\xc2\\xc0$sU2\\xe4\\xd3\\xc9\\12\\xaa,\\x9b\\'\\xe4\\xe6\\xe9\\dbn\\ek\\xeaz>\\xa2\\xa2\\xb04\\b4\\xffU\\n\\xfaz\\t%+6\\xeer?\\xeb\\xf1\\xb7f'\u001b[39;00m\n\u001b[0;32m---> 27\u001b[0m secret \u001b[38;5;241m=\u001b[39m \u001b[43mprivate_key\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdecrypt\u001b[49m\u001b[43m(\u001b[49m\u001b[43msecret_chiffre\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[1;32m     28\u001b[0m \u001b[43m                                \u001b[49m\u001b[43mpadding\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mOAEP\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmgf\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpadding\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mMGF1\u001b[49m\u001b[43m(\u001b[49m\u001b[43malgorithm\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mhashes\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mSHA256\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43malgorithm\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mhashes\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mSHA256\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43mlabel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     29\u001b[0m secret_str \u001b[38;5;241m=\u001b[39m secret\u001b[38;5;241m.\u001b[39mdecode(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m     30\u001b[0m graine_str, coeff_str \u001b[38;5;241m=\u001b[39m secret_str\u001b[38;5;241m.\u001b[39msplit(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m;\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "\u001b[0;31mValueError\u001b[0m: Ciphertext length must be equal to key size."
     ]
    }
   ],
   "source": [
    "m = \"Bonjour, ceci est un message secret.\"\n",
    "graine = generation_reg_graine(8)\n",
    "coeff = [3,5,7]\n",
    "\n",
    "msgchiffre = chiffrementLFSR(m, graine, coeff)\n",
    "print(\"Message chiffré :\", msgchiffre)\n",
    "\n",
    "secret = str(graine) + \";\" + str(coeff)\n",
    "secret_bytes = secret.encode('utf-8')\n",
    "\n",
    "# Clef de Florian\n",
    "public_key_F = b'-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPdUmUYgXdSQdFAWNXK4\\nzrTflVbYlHCdt1be6IbIfVsXLNaDfJpIp1kz9TIft/+3clYnuMD6wpAm7q89NwOl\\nj17snspJbdvHQuPHQAkwbG31arDj3F3DZWTXUIjEcSnz36gMh5FMeV4IPvIwA8RZ\\nXXtQ9fabZJK+akWd0NvGjekFDLHuNrGvrPV0NKtusluC1j57SA2To3zRM1gtZqsG\\nXMRwnlTg2+UZJDuumf5gZRRViuri7T++yS3Rw6gQ1NOUuNOP47xUt9GxMd08Wnd2\\n+9aIsiFtDX2Ri5U7Qe7ny7WNCxV7fHankB6dY5ccGPUPhWDPHXJJDTy+uZhcR/aM\\njQIDAQAB\\n-----END PUBLIC KEY-----\\n'\n",
    "public_key_F = serialization.load_pem_public_key(public_key_F)\n",
    "\n",
    "ciphertext = public_key_F.encrypt(secret_bytes,\n",
    "                                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))\n",
    "print(\"Clef chiffrée de Greg :\", ciphertext)\n",
    "\n",
    "pem = public_key.public_bytes(\n",
    "   encoding=serialization.Encoding.PEM,\n",
    "   format=serialization.PublicFormat.SubjectPublicKeyInfo\n",
    ")\n",
    "print(\"public_key :\", pem)\n",
    "\n",
    "c = \"101010110010011100100101001001010010110100100110001111000110100000101011001010010110100000111110001010010110100000100100001011010110100000101111001110100010110100101111 \"\n",
    "# buggé secret_chiffre = b'/e\\xc2\\xb9!\\x8d\\x03\\xa9\\xbb\\xb2\\x0f\\x18q6:g\\x9f\\x1eS\\d\\x82%J\\xc5';8'=\\xa74\\x10\\xd4\\xdc\\x87v\\x99\\xac\\x96\\x13]\\xb2\\x08O~\\xf9\\xcd\\xa7\\xdfu\\x1b\\xcb6\\xdeKy\\xa9b|\\x0eWy\\xe1\\xf3\\xd8)\\x99\\xab\\xeb-\\xf6#z4\\xb4H\\xd4\\xecS\\xba\\xa3\\xf5,\\xd1\\xb6sZO\\xea5\\x9d\\xb72d\\xf8\\xe0\\x0b\\x19y@\\xcf\\xf0\\xd6\\xf6\\x02\\xed\\x83\\xbb\\x8b@\\x02\\x16\\xef\\xd4a\\'G2\\xc0\\x10\\xcb4\\xa0\\x1b\\x83G\\xad\\x079GO_\\xc3r\\x81A8\\x02J\\xc0Z\\xfe\\x12\\xda\\xdb\\x05\\xc6\\x80\\x8fn#\\xcc\\xc8U$\\xfd\\xaa\\xb8/\\x84\\x8f\\xcc\\x9f\\xdf\"\\x8d\\x01\\xee\\xce\\xcf\\xf7O\\xbd\\x86\\fee;\\x95\\byG\\ce\\1d\\9fX\\ZW\\'\\x13\\efQ\\fc\\xd0\\xb3\\xf1\\xd1X\\xf4\\86\\xb6\\xc5K\\ab\\xc2\\xc0$sU2\\xe4\\xd3\\xc9\\12\\xaa,\\x9b\\'\\xe4\\xe6\\xe9\\dbn\\ek\\xeaz>\\xa2\\xa2\\xb04\\b4\\xffU\\n\\xfaz\\t%+6\\xeer?\\xeb\\xf1\\xb7f'\n",
    "secret = private_key.decrypt(secret_chiffre, \n",
    "                                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None))\n",
    "secret_str = secret.decode('utf-8')\n",
    "graine_str, coeff_str = secret_str.split(\";\")\n",
    "graine = eval(graine_str)\n",
    "coeff = eval(coeff_str)\n",
    "\n",
    "print(\"Message chiffré de Florian :\", c)\n",
    "print(\"Graine de Florian :\", graine)\n",
    "print(\"Coefficients de Florian :\", coeff)\n",
    "\n",
    "msgclair2 = dechiffrementLFSR(c, graine, coeff)\n",
    "print(\"Message déchiffré de Florian :\", msgclair2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2 - Attaque RSA"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous vous proposons ici de réaliser une attaque pour retrouver la clef secrète RSA connaissant seulement la clef publique.\n",
    "Cette attaque est détaillée dans cette vidéo youtube : [Computerphile - Attaque RSA](https://www.youtube.com/watch?v=-ShwJqAalOk&t=740s)\n",
    "L'attaque repose sur la méthode de factorisation de Fermat pour retrouver les deux facteurs premiers $p$ et $q$ de $n$ et ainsi calculer $\\varphi(n)$ pour trouver $d$. __L'attaque ne fonctionne que si $p$ et $q$ sont proches entre eux.__\n",
    "\n",
    "\n",
    "L'attaque utilise le résultat suivant : \n",
    "\n",
    "<center>Tout entier naturel impair $N$ se décompose en la différence de deux carrés : $N = a^2 – b^2$</center>\n",
    "\n",
    "$ $\n",
    "\n",
    "Donc $N = a^2 – b^2 = (a-b)\\times(a+b)$ (identité remarquable)\n",
    "\n",
    "En notant $p = (a-b)$ et $q = (a+b)$, nous avons bien retrouvé nos deux facteurs $p$ et $q$ de $N$.\n",
    "\n",
    "Nous avons notamment $b$ en fonction de $a$ par : $b^2 = a^2 - N$\n",
    "\n",
    "__Description de l'attaque :__\n",
    "\n",
    "L'attaque fonctionne par tâtonnement en posant d'abord $a = \\lceil{\\sqrt{N}}\\rceil$ (la partie entière supérieure de la racine carré de $N$).\n",
    "On calcule ensuite $N - a^2$. \n",
    "- S'il s'agit d'un carré (pour vérifier cela, utilisez la fonction `is_square()` de gmpy2), nous avons trouvé $b^2$ ! On peut ensuite en déduire $p$, $q$ (puis la clef privée) !\n",
    "- Sinon, on recommence en incrémentant $a$ de $1$.\n",
    "\n",
    "> __Exercice__ : essayez de retrouver la décomposition en facteurs premiers des différents nombres donnés en utilisant l'attaque proposée. Pour cela ecrire la fonction factorise(n) qui renvoie i (le nombre d'itérations avant d'arriver à la factorisation), p et q. Tracez, pour les décompositions que vous avez réussi, le nombre d'itérations en fonction de la différence (en valeur absolue) entre p et q.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "def factorise(n): \n",
    "    #todo\n",
    "    return i, p, q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "n1 = 12349506576503392789637\n",
    "n2 = 22955938862933349879407\n",
    "n3 = 248490079731636283\n",
    "n4 = 408733787056848549922393\n",
    "n5 = 665288465072577132173501\n",
    "n6 = 814500002934085001856503\n",
    "n7 = 660959998217573994174211\n",
    "n8 = 307222223930632557778243"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x255fccbd7f0>]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot([1, 3, 4, 6], [2, 3, 5, 1])#todo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
