L’ensemble \(\mathbb{Z}\)#

Les nombres négatifs sont apparus comme des fantômes, mais ils se sont révélés être les plus fidèles compagnons des mathématiques.

John Napier

Construction de \(\mathbb{Z}\)#

L’équation \(x + n = m\) n’a de solution dans \(\mathbb{N}\) que si \(n \leq m\). Pour remédier à cela, on construit \(\mathbb{Z}\) comme un ensemble quotient.

Définition 45 (Relation d’équivalence sur \(\mathbb{N}^2\))

On définit sur \(\mathbb{N}^2\) : \((a, b) \sim (c, d) \iff a + d = b + c\)

(intuition : \((a, b)\) représente la différence \(a - b\)).

Proposition 44

\(\sim\) est une relation d’équivalence. L’ensemble \(\mathbb{Z} = \mathbb{N}^2/{\sim}\) est un anneau commutatif intègre, et \((\mathbb{Z}, +, \cdot)\) vérifie :

  • \((a,b) + (c,d) = (a+c, b+d)\), d’élément neutre \(\overline{(0,0)}\)

  • \((a,b) \cdot (c,d) = (ac+bd, ad+bc)\), d’élément neutre \(\overline{(1,0)}\)

  • \(-\overline{(a,b)} = \overline{(b,a)}\)

Remarque 21

On identifie \(\mathbb{N}\) à son image \(n \mapsto \overline{(n,0)}\) dans \(\mathbb{Z}\), et on note \(\overline{(0,n)} = -n\). Tout entier relatif s’écrit de façon unique comme \(n\) ou \(-n\) avec \(n \in \mathbb{N}\).

Structure de \(\mathbb{Z}\)#

Proposition 45 (\((\mathbb{Z}, +, \cdot)\) est un anneau commutatif intègre)

  • Groupe abélien \((\mathbb{Z}, +)\) : neutre \(0\), inverse \(-n\).

  • Monoïde commutatif \((\mathbb{Z}, \cdot)\) : neutre \(1\).

  • Intégrité : \(ab = 0 \implies a = 0\) ou \(b = 0\).

  • Non corps : \(2\) n’est pas inversible dans \(\mathbb{Z}\).

Valeur absolue et ordre#

Définition 46 (Valeur absolue et ordre)

\(|n| = n\) si \(n \geq 0\), \(|n| = -n\) si \(n < 0\).

\(m \leq n \iff n - m \in \mathbb{N}\).

Proposition 46 (Propriétés)

  • \(|mn| = |m| \cdot |n|\)

  • \(|m + n| \leq |m| + |n|\) (inégalité triangulaire)

  • \(\big||m| - |n|\big| \leq |m - n|\) (inégalité triangulaire inverse)

  • \((\mathbb{Z}, \leq)\) est un ordre total, mais pas bien fondé (\(\mathbb{Z}\) n’a pas de minimum).

Proof. Inégalité triangulaire : \(-|m| \leq m \leq |m|\) et \(-|n| \leq n \leq |n|\), donc \(-(|m|+|n|) \leq m+n \leq |m|+|n|\).

Division euclidienne dans \(\mathbb{Z}\)#

Proposition 47 (Division euclidienne dans \(\mathbb{Z}\))

Pour tout \(a \in \mathbb{Z}\) et \(b \in \mathbb{Z}^*\), il existe un unique \((q, r) \in \mathbb{Z} \times \mathbb{N}\) tel que

\[a = bq + r \quad \text{et} \quad 0 \leq r < |b|\]

Identité de Bézout et algorithme d’Euclide étendu#

Proposition 48 (Théorème de Bézout)

Pour tous \(a, b \in \mathbb{Z}\) (non tous deux nuls), il existe \(u, v \in \mathbb{Z}\) tels que

\[au + bv = \pgcd(a, b)\]

Proof. L’ensemble \(\{au + bv \mid u, v \in \mathbb{Z}\}\) est un sous-groupe de \((\mathbb{Z}, +)\), donc de la forme \(d\mathbb{Z}\) pour un certain \(d \geq 0\) (cf. chapitre 3). On vérifie que \(d = \pgcd(a, b)\) :

  • \(d \mid a\) et \(d \mid b\) (car \(a = a \cdot 1 + b \cdot 0\) et \(b \in d\mathbb{Z}\), donc \(d \mid a\)).

  • Si \(e \mid a\) et \(e \mid b\), alors \(e \mid (au + bv) = d\), donc \(e \leq d\). Ainsi \(d\) est bien le pgcd.

Proposition 49 (Algorithme d’Euclide étendu)

La remontée de l’algorithme d’Euclide fournit explicitement \(u\) et \(v\) tels que \(au + bv = \pgcd(a,b)\).

Exemple 19

\(\pgcd(252, 198)\) :

\[252 = 198 \cdot 1 + 54 \implies 54 = 252 - 198 \cdot 1\]
\[198 = 54 \cdot 3 + 36 \implies 36 = 198 - 54 \cdot 3\]
\[54 = 36 \cdot 1 + 18 \implies 18 = 54 - 36 \cdot 1\]
\[36 = 18 \cdot 2 + 0\]

Remontée : \(18 = 54 - 36 = 54 - (198 - 54 \cdot 3) = 4 \cdot 54 - 198 = 4(252 - 198) - 198 = 4 \cdot 252 - 5 \cdot 198\).

Donc \(\pgcd(252, 198) = 18\) avec \(u = 4\), \(v = -5\).

Proposition 50 (Lemme de Gauss)

Si \(a \mid bc\) et \(\pgcd(a, b) = 1\), alors \(a \mid c\).

Proof. Par Bézout, \(\exists u, v,\ au + bv = 1\). Multiplier par \(c\) : \(auc + bvc = c\). Comme \(a \mid auc\) et \(a \mid bc\) donc \(a \mid bvc\), on a \(a \mid c\).

Proposition 51 (Lemme d’Euclide)

Si \(p\) est premier et \(p \mid ab\), alors \(p \mid a\) ou \(p \mid b\).

Proof. Si \(p \nmid a\), alors \(\pgcd(p, a) = 1\) (car les seuls diviseurs de \(p\) sont \(1\) et \(p\)). Par le lemme de Gauss, \(p \mid b\).

Hide code cell source

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.patches import FancyBboxPatch

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

def euclide_etendu(a, b):
    """Retourne (gcd, u, v) tels que a*u + b*v = gcd."""
    if b == 0:
        return a, 1, 0
    g, u1, v1 = euclide_etendu(b, a % b)
    return g, v1, u1 - (a // b) * v1

# Visualisation de l'algorithme d'Euclide étendu
a_ex, b_ex = 252, 198
gcd_ex, u_ex, v_ex = euclide_etendu(a_ex, b_ex)

fig, axes = plt.subplots(2, 1, figsize=(9, 9))

# Tableau des étapes
steps_data = []
a_curr, b_curr = a_ex, b_ex
u_prev, v_prev = 1, 0
u_curr, v_curr = 0, 1
while b_curr:
    q = a_curr // b_curr
    r = a_curr % b_curr
    steps_data.append((a_curr, b_curr, q, r))
    a_curr, b_curr = b_curr, r

ax = axes[0]
ax.axis('off')
headers = ['$a$', '$b$', '$q$', '$r = a - bq$']
col_widths = [0.22, 0.22, 0.12, 0.35]
x_starts = [0.02, 0.24, 0.46, 0.58]

# En-têtes
for h, x in zip(headers, x_starts):
    ax.text(x, 0.92, h, fontsize=12, fontweight='bold', color='#2c3e50')

# Séparateur
ax.axhline(y=0.88, xmin=0.01, xmax=0.99, color='#bdc3c7', lw=1)

n_steps = len(steps_data)
for i, (a_s, b_s, q_s, r_s) in enumerate(steps_data):
    y = 0.82 - i * 0.14
    color = '#27ae60' if r_s == 0 else '#ecf0f1' if i % 2 == 0 else 'white'
    ax.add_patch(FancyBboxPatch((0.01, y - 0.06), 0.98, 0.12,
                 boxstyle='round,pad=0.01', facecolor=color, alpha=0.6, edgecolor='#bdc3c7'))
    for val, x in zip([a_s, b_s, q_s, r_s], x_starts):
        ax.text(x, y, str(val), fontsize=11)

ax.text(0.5, 0.06, f'$\\mathrm{{pgcd}}({a_ex}, {b_ex}) = {gcd_ex}$\n$= {u_ex} \\times {a_ex} + ({v_ex}) \\times {b_ex}$',
        ha='center', fontsize=12, fontweight='bold', color='#27ae60')
ax.set_title('Algorithme d\'Euclide étendu', fontsize=11)

# Visualisation géométrique du PGCD (réseau de points)
ax2 = axes[1]
N_grid = 12
X, Y = np.meshgrid(range(N_grid + 1), range(N_grid + 1))
ax2.scatter(X, Y, s=20, color='#bdc3c7', alpha=0.5, zorder=1)

# Points accessibles comme au + bv avec a=3, b=5
a_geo, b_geo = 3, 5
g_geo = np.gcd(a_geo, b_geo)
accessible = set()
for u in range(-5, 6):
    for v in range(-5, 6):
        val = a_geo * u + b_geo * v
        for w in range(0, N_grid + 1):
            if val == w:
                accessible.add(w)

# Multiples du pgcd
for k in range(0, N_grid + 1):
    if k % g_geo == 0:
        ax2.axvline(x=k, color='#3498db', alpha=0.3, lw=1)

ax2.scatter(range(0, N_grid + 1, g_geo), [0] * (N_grid // g_geo + 1),
            s=200, color='#e74c3c', zorder=5, label=f'Multiples de $\\mathrm{{pgcd}}({a_geo},{b_geo})={g_geo}$')

# Combinaisons linéaires 3u + 5v
combins = sorted(set(
    max(0, min(N_grid, a_geo * u + b_geo * v))
    for u in range(-4, 5) for v in range(-4, 5)
    if 0 <= a_geo * u + b_geo * v <= N_grid
))
ax2.scatter(combins, [0.3] * len(combins), s=100, color='#2ecc71', marker='D', zorder=5,
           label=f'$3u + 5v \\in [0,{N_grid}]$')

ax2.set_xlim(-0.5, N_grid + 0.5)
ax2.set_ylim(-0.5, 1)
ax2.set_xlabel('Valeur')
ax2.set_yticks([])
ax2.legend(fontsize=9)
ax2.set_title(f'Théorème de Bézout : les combinaisons $au+bv$\nsont exactement les multiples de $\\mathrm{{pgcd}}(a,b)$', fontsize=10)

plt.tight_layout()
plt.show()
_images/3d7dd046fb660ba36587b5a850968426c35a04d848811395f9582557e485b715.png

Congruences et arithmétique modulaire#

Définition 47 (Congruence)

Pour \(n \in \mathbb{N}^*\), on dit que \(a \equiv b [n]\)\(a\) est congru à \(b\) modulo \(n\) ») si \(n \mid (a - b)\).

Proposition 52 (Propriétés des congruences)

La congruence modulo \(n\) est une relation d’équivalence sur \(\mathbb{Z}\), compatible avec \(+\) et \(\cdot\) :

\[a \equiv a' [n] \land b \equiv b' [n] \implies a+b \equiv a'+b' [n] \land ab \equiv a'b' [n]\]

Définition 48 (Anneau \(\mathbb{Z}/n\mathbb{Z}\))

L’ensemble des classes de congruence modulo \(n\), noté \(\mathbb{Z}/n\mathbb{Z} = \{\bar{0}, \bar{1}, \ldots, \overline{n-1}\}\), est un anneau commutatif pour \(\bar{a} + \bar{b} = \overline{a+b}\) et \(\bar{a} \cdot \bar{b} = \overline{ab}\).

Proposition 53 (\(\mathbb{Z}/n\mathbb{Z}\) est un corps \(\iff\) \(n\) est premier)

Plus précisément : \(\bar{a}\) est inversible dans \(\mathbb{Z}/n\mathbb{Z}\) si et seulement si \(\pgcd(a, n) = 1\).

Proof. \(\bar{a}\) inversible \(\iff \exists \bar{u},\ \bar{a}\bar{u} = \bar{1} \iff \exists u, v \in \mathbb{Z},\ au + nv = 1 \iff \pgcd(a, n) = 1\) (Bézout).

Définition 49 (Groupe des inversibles)

Le groupe des unités de \(\mathbb{Z}/n\mathbb{Z}\) est \((\mathbb{Z}/n\mathbb{Z})^* = \{\bar{a} \mid \pgcd(a, n) = 1\}\).

Définition 50 (Indicatrice d’Euler)

\(\varphi(n) = \#\{k \in \{1, \ldots, n\} \mid \pgcd(k, n) = 1\} = |(\mathbb{Z}/n\mathbb{Z})^*|\)

Proposition 54 (Formule de l’indicatrice d’Euler)

\[\varphi(n) = n \prod_{p \mid n,\ p\ \text{premier}} \left(1 - \frac{1}{p}\right)\]

Proof. Par inclusion-exclusion. Si \(n = p_1^{\alpha_1} \cdots p_k^{\alpha_k}\), les entiers \(\leq n\) non copremiers avec \(n\) sont ceux divisibles par au moins un \(p_i\). Par inclusion-exclusion :

\[\varphi(n) = n\left(1 - \frac{1}{p_1}\right)\left(1 - \frac{1}{p_2}\right)\cdots\left(1 - \frac{1}{p_k}\right)\]

Proposition 55 (Théorème d’Euler et petit théorème de Fermat)

  • Euler : Pour \(\pgcd(a, n) = 1\) : \(a^{\varphi(n)} \equiv 1 [n]\).

  • Fermat : Pour \(p\) premier et \(p \nmid a\) : \(a^{p-1} \equiv 1 [p]\).

Proof. Le groupe \((\mathbb{Z}/n\mathbb{Z})^*\) est d’ordre \(\varphi(n)\). Par le théorème de Lagrange (ordre d’un élément divise l’ordre du groupe), \(\bar{a}^{\varphi(n)} = \bar{1}\).

Le cas de Fermat est le cas particulier \(n = p\) premier, où \(\varphi(p) = p - 1\).

Exemple 20

\(a = 7\), \(n = 10\) : \(\varphi(10) = \varphi(2)\varphi(5) = 1 \cdot 4 = 4\). Donc \(7^4 \equiv 1 [10]\). En effet, \(7^2 = 49 \equiv 9\), \(7^4 \equiv 81 \equiv 1 [10]\). ✓

Théorème chinois des restes#

Proposition 56 (Théorème chinois des restes (CRT))

Soient \(n_1, \ldots, n_k \in \mathbb{N}^*\) deux à deux copremiers. Pour tous \(a_1, \ldots, a_k \in \mathbb{Z}\), le système

\[x \equiv a_i [n_i], \quad i = 1, \ldots, k\]

admet une unique solution modulo \(N = n_1 \cdots n_k\).

Proof. Pour \(k = 2\) : comme \(\pgcd(n_1, n_2) = 1\), Bézout donne \(u_1 n_1 + u_2 n_2 = 1\). Posons \(x = a_1 u_2 n_2 + a_2 u_1 n_1\). Alors \(x \equiv a_1 \cdot 1 \equiv a_1 [n_1]\) et \(x \equiv a_2 \cdot 1 \equiv a_2 [n_2]\). L’unicité modulo \(N = n_1 n_2\) découle de \(n_1 \mid x\) et \(n_2 \mid x \implies n_1 n_2 \mid x\) (copremiers).

Le cas général se démontre par récurrence.

Remarque 22

Le CRT est isomorphe à l’anneau : \(\mathbb{Z}/N\mathbb{Z} \cong \mathbb{Z}/n_1\mathbb{Z} \times \cdots \times \mathbb{Z}/n_k\mathbb{Z}\) (isomorphisme d’anneaux). Il est fondamental en cryptographie (RSA), en algèbre computationnelle (FFT), et en théorie des nombres.

Hide code cell source

# Visualisation des congruences et de Z/nZ
fig, axes = plt.subplots(3, 1, figsize=(9, 14))

# --- Horloge Z/12Z ---
ax = axes[0]
n_clock = 12
angles = np.linspace(np.pi/2, np.pi/2 - 2*np.pi, n_clock + 1)[:-1]
r_clock = 1.4

ax.add_patch(plt.Circle((0, 0), r_clock + 0.2, color='#ecf0f1', zorder=0))
ax.add_patch(plt.Circle((0, 0), r_clock + 0.2, fill=False, edgecolor='#bdc3c7', lw=2))

for k in range(n_clock):
    x = r_clock * np.cos(angles[k])
    y = r_clock * np.sin(angles[k])
    color = '#3498db' if k % 3 == 0 else '#ecf0f1'
    ax.add_patch(plt.Circle((x, y), 0.22, color=color, zorder=5))
    ax.text(x, y, str(k), ha='center', va='center', fontsize=11,
            color='white' if k % 3 == 0 else 'black', fontweight='bold')

# Flèche +4 (exemple)
k_start = 3
k_end = (k_start + 4) % n_clock
x1, y1 = r_clock * np.cos(angles[k_start]), r_clock * np.sin(angles[k_start])
x2, y2 = r_clock * np.cos(angles[k_end]), r_clock * np.sin(angles[k_end])
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
            arrowprops=dict(arrowstyle='->', color='#e74c3c', lw=2,
                           connectionstyle='arc3,rad=0.4'))
ax.text(0, 0, f'$3 + 4 \\equiv 7$\n(mod 12)', ha='center', va='center', fontsize=10)

ax.set_xlim(-2, 2); ax.set_ylim(-2, 2)
ax.set_aspect('equal'); ax.axis('off')
ax.set_title('$\\mathbb{Z}/12\\mathbb{Z}$ — horloge\n(multiples de 3 en bleu)', fontsize=10)

# --- Indicatrice d'Euler ---
ax2 = axes[1]
n_vals = np.arange(1, 51)
phi_vals = np.array([sum(1 for k in range(1, n+1) if np.gcd(k, n) == 1) for n in n_vals])

ax2.bar(n_vals, phi_vals, color='#3498db', alpha=0.7, label=r'$\varphi(n)$')
ax2.plot(n_vals, n_vals - 1, 'r--', lw=1.5, alpha=0.7, label='$n-1$ (si $n$ premier)')
ax2.set_xlabel('$n$'); ax2.set_ylabel(r'$\varphi(n)$')
ax2.set_title('Indicatrice d\'Euler $\\varphi(n)$\n($\\varphi(n) = n-1$ pour $n$ premier)', fontsize=10)
ax2.legend(fontsize=9)

# --- Puissances de 2 modulo 13 (Fermat) ---
ax3 = axes[2]
p = 13
powers = [pow(2, k, p) for k in range(p + 2)]
k_vals = list(range(p + 2))

ax3.plot(k_vals, powers, 'o-', color='#e74c3c', markersize=8, lw=2, label=f'$2^k$ mod {p}')
ax3.axhline(y=1, color='gray', linestyle='--', alpha=0.7)
ax3.axvline(x=p - 1, color='#2ecc71', linestyle='--', lw=2,
            label=f'$k = p-1 = {p-1}$, $2^{{p-1}} \\equiv 1$')

ax3.set_xlabel('$k$'); ax3.set_ylabel(f'$2^k$ mod {p}')
ax3.set_title(f'Petit théorème de Fermat\n$2^{{p-1}} \\equiv 1$ (mod $p$), $p={p}$', fontsize=10)
ax3.legend(fontsize=9)
ax3.set_ylim(0, p)

plt.tight_layout()
plt.show()
_images/f756c4831e7c45e665bd274dcb5017ef6e63e661b9507554c4db21e9a9e1f87b.png

Hide code cell source

# Théorème chinois des restes : visualisation
fig, axes = plt.subplots(2, 1, figsize=(9, 9))

# CRT : x ≡ 2 (mod 3), x ≡ 3 (mod 5)  => x ≡ 8 (mod 15)
n1, a1 = 3, 2
n2, a2 = 5, 3
N = n1 * n2

solutions = [x for x in range(N) if x % n1 == a1 and x % n2 == a2]
print(f"Solution : {solutions[0]} mod {N}")

ax = axes[0]
x_range = range(3 * N)
class1 = [x for x in x_range if x % n1 == a1]
class2 = [x for x in x_range if x % n2 == a2]
intersect = [x for x in x_range if x % n1 == a1 and x % n2 == a2]

ax.scatter(class1, [1]*len(class1), s=100, color='#3498db', alpha=0.7,
           label=f'$x = {a1}$ (mod {n1})', zorder=5)
ax.scatter(class2, [2]*len(class2), s=100, color='#e74c3c', alpha=0.7,
           label=f'$x = {a2}$ (mod {n2})', zorder=5)
ax.scatter(intersect, [1.5]*len(intersect), s=200, color='#2ecc71',
           marker='*', label=f'Solution : $x = {solutions[0]}$ (mod {N})', zorder=6)

for x_sol in intersect:
    ax.axvline(x=x_sol, color='#2ecc71', alpha=0.3, lw=1)

ax.set_xlim(-1, 3*N)
ax.set_yticks([1, 1.5, 2])
ax.set_yticklabels([f'mod {n1}', 'intersection', f'mod {n2}'])
ax.set_xlabel('$x$')
ax.set_title(f'Théorème chinois des restes\n$x = {a1}$ (mod {n1}), $x = {a2}$ (mod {n2}) => $x = {solutions[0]}$ (mod {N})',
             fontsize=10)
ax.legend(fontsize=9)

# Isomorphisme CRT : Z/15Z ≅ Z/3Z × Z/5Z
ax2 = axes[1]
pairs = [(x % 3, x % 5) for x in range(15)]
from matplotlib.collections import LineCollection

for x, (r3, r5) in enumerate(pairs):
    color = plt.cm.tab20(x / 15)
    ax2.scatter([r3], [r5], s=400, color=color, zorder=5)
    ax2.text(r3, r5, str(x), ha='center', va='center', fontsize=9, color='white', fontweight='bold')

ax2.set_xlabel('$x$ mod 3'); ax2.set_ylabel('$x$ mod 5')
ax2.set_xticks([0, 1, 2]); ax2.set_yticks([0, 1, 2, 3, 4])
ax2.set_title('$\\mathbb{Z}/15\\mathbb{Z} \\cong \\mathbb{Z}/3\\mathbb{Z} \\times \\mathbb{Z}/5\\mathbb{Z}$\n(chaque point = un élément de $\\mathbb{Z}/15\\mathbb{Z}$)', fontsize=10)
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
Solution : 8 mod 15
_images/8963371631292e7b85120f81ce54e1b5b4f310762eafe57504338e722dcb301b.png

Nombres premiers et cryptographie#

Remarque 23

Le chiffrement RSA repose sur les structures vues ici :

  1. On choisit deux grands premiers \(p\) et \(q\), pose \(N = pq\).

  2. L’exposant public \(e\) vérifie \(\pgcd(e, \varphi(N)) = 1\)\(\varphi(N) = (p-1)(q-1)\).

  3. L’exposant privé \(d\) est l’inverse de \(e\) modulo \(\varphi(N)\) : \(de \equiv 1 [\varphi(N)]\).

  4. Chiffrement : \(c \equiv m^e [N]\). Déchiffrement : \(m \equiv c^d [N]\) (par Euler : \(c^d = m^{ed} = m^{1 + k\varphi(N)} \equiv m\)).

La sécurité repose sur la difficulté de factoriser \(N\) pour trouver \(p\) et \(q\).

Hide code cell source

# Démonstration simplifiée de RSA avec de petits nombres
fig, ax = plt.subplots(figsize=(12, 6))
ax.axis('off')

# Paramètres RSA jouet
p_rsa, q_rsa = 11, 13
N_rsa = p_rsa * q_rsa
phi_rsa = (p_rsa - 1) * (q_rsa - 1)
e_rsa = 7  # gcd(7, 120) = 1
d_rsa = pow(e_rsa, -1, phi_rsa)  # inverse modulaire
m_rsa = 42  # message

c_rsa = pow(m_rsa, e_rsa, N_rsa)
m_dec = pow(c_rsa, d_rsa, N_rsa)

steps_rsa = [
    (f'Clés', f'$p={p_rsa}$, $q={q_rsa}$ (premiers)'),
    (f'Modulus', f'$N = pq = {N_rsa}$'),
    (f'$\\varphi(N)$', f'$(p-1)(q-1) = {phi_rsa}$'),
    (f'Clé publique', f'$e = {e_rsa}$, $\\mathrm{{pgcd}}(e, \\varphi) = {np.gcd(e_rsa, phi_rsa)}$'),
    (f'Clé privée', f'$d \\equiv e^{{-1}}$ (mod $\\varphi$) = {d_rsa}$'),
    (f'Message', f'$m = {m_rsa}$'),
    (f'Chiffrement', f'$c = m^e$ (mod $N$) = {m_rsa}^{{{e_rsa}}} mod {N_rsa} = {c_rsa}$'),
    (f'Déchiffrement', f'$m = c^d$ (mod $N$) = {c_rsa}^{{{d_rsa}}} mod {N_rsa} = {m_dec}$ ✓'),
]

colors_rsa = ['#3498db']*2 + ['#9b59b6'] + ['#27ae60']*2 + ['#e67e22'] + ['#e74c3c'] + ['#2ecc71']

for i, ((key, val), col) in enumerate(zip(steps_rsa, colors_rsa)):
    y = 0.92 - i * 0.115
    ax.add_patch(FancyBboxPatch((0.01, y - 0.05), 0.98, 0.1,
                 boxstyle='round,pad=0.02', facecolor=col, alpha=0.3, edgecolor=col))
    ax.text(0.15, y, key + ' :', fontsize=11, va='center', fontweight='bold', color=col)
    ax.text(0.38, y, val, fontsize=11, va='center')

ax.set_title('RSA simplifié — illustration du chiffrement par clé publique', fontsize=13, fontweight='bold')
ax.text(0.5, 0.02, f'$d \\cdot e = {d_rsa} \\times {e_rsa} = {d_rsa * e_rsa} = 1$ (mod {phi_rsa}) (vérification)',
        ha='center', fontsize=10, color='gray')

plt.tight_layout()
plt.show()
_images/6f5302c93b5d46ed7d4908ac4f581d4a83ac00dffba6c71b0a398ebee037de17.png