Épreuve écrite · 2025 · Jour 1

Bac NSI 2025 Polynésie jour 1

Ce sujet relie trois formes de raisonnement : choisir une route selon son coût, construire une solution récursive à un casse-tête et contrôler les choix d’un gestionnaire de mots de passe. La correction conserve les conventions particulières du PDF, notamment sa distance RIP et les deux numérotations du baguenaudier, tout en explicitant les limites des exemples de sécurité.

Les trois exercices indépendants valent 6, 6 et 8 points ; l’épreuve dure 3 h 30 sans calculatrice. Les 38 questions sont accompagnées de calculs, de code vérifié et d’ateliers.

Dans ce sujet

Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.

Exercice 1 · 6 points

Maillage RIP/OSPF et calculs d’adresses IPv4

Partie A : choisir une route

Le cœur de réseau contient six routeurs. Derrière R1 à R5, un switch distribue le LAN correspondant ; R6 relie le maillage à Internet. Les débits des liens sont indiqués sur la figure.

10111001010100100100R1 / LAN1R2 / LAN2R3 / LAN3R4 / LAN4R5 / LAN5R6 / Internet
Figure 1 : maillage des routeurs, débits en Mbit/s
Lire les connexions du schéma
  • R1 / LAN1 relié à R2 / LAN2 : 10
  • R1 / LAN1 relié à R4 / LAN4 : 1
  • R1 / LAN1 relié à R5 / LAN5 : 1
  • R2 / LAN2 relié à R3 / LAN3 : 100
  • R2 / LAN2 relié à R4 / LAN4 : 10
  • R2 / LAN2 relié à R5 / LAN5 : 10
  • R3 / LAN3 relié à R4 / LAN4 : 100
  • R4 / LAN4 relié à R5 / LAN5 : 100
  • R5 / LAN5 relié à R6 / Internet : 100

RIP minimise le nombre de routeurs intermédiaires selon la convention de la table de ce sujet. OSPF minimise la somme des coûts des liaisons.

DestinationProchain sautDistance
R2R20
R3R21
R4……
R5……
R6……

Question 1

#

Compléter la table RIP de R1 : destination, prochain saut et distance.

Comprendre la correction
DestinationProchain sautDistance du sujet
R2R20
R3R21
R4R40
R5R50
R6R51

R4 et R5 sont directement reliés à R1 ; R6 se rejoint via R5. On conserve la convention de l’énoncé, qui attribue 0 à un voisin direct et 1 à une destination atteinte après un intermédiaire. Si l’on compte les liaisons franchies plutôt que les intermédiaires, ces nombres augmentent de 1, mais les meilleurs chemins restent les mêmes.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)

Question 2

#

Un utilisateur de LAN1 interroge un serveur Internet depuis son navigateur. Détailler la route suivie dans le maillage selon RIP.

Comprendre la correction

R1 → R5 → R6 → Internet. Seul R6 possède la sortie Internet ; l’atteindre via le voisin direct R5 minimise les intermédiaires. RIP ne tient pas compte ici du faible débit du lien R1-R5.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Le coût est 100 / débit, débit en Mbit/s. Exemple R1-R2 : 100 / 10 = 10. Les coûts directs depuis R1 sont 10 vers R2, 100 vers R4 et 100 vers R5. La ligne R2 de la table OSPF est fournie : prochain saut R2, distance 10.

Question 3

#

Compléter la table OSPF de R1, où la distance est la somme des coûts des liaisons.

Indice

Un détour sur plusieurs liens rapides peut coûter moins qu’un lien direct.

Comprendre la correction
DestinationProchain sautDistanceChemin choisi
R2R210R1-R2
R3R211R1-R2-R3
R4R212R1-R2-R3-R4
R5R213R1-R2-R3-R4-R5
R6R214R1-R2-R3-R4-R5-R6

Un lien à 100 Mbit/s coûte 1 ; à 10 Mbit/s, 10 ; à 1 Mbit/s, 100. Le détour par plusieurs liens rapides évite les liens directs très coûteux. Le prochain saut est toujours R2, même lorsque la destination finale est un autre routeur.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 4

#

Donner la route vers Internet depuis LAN1 selon OSPF.

Comprendre la correction

R1 → R2 → R3 → R4 → R5 → R6 → Internet. Le coût dans le maillage vaut 10 + 1 + 1 + 1 + 1 = 14, contre 101 pour R1-R5-R6. OSPF accepte davantage de sauts si leur coût total est plus petit.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 5

#

R2 tombe en panne. Donner la nouvelle route OSPF de LAN1 vers Internet et sa distance.

Comprendre la correction

R1 → R5 → R6, de coût 100 + 1 = 101. L’autre départ disponible, R1-R4-R5-R6, coûte 102. Aucun chemin ne peut utiliser le routeur en panne ; on retire donc R2 et toutes ses liaisons avant de comparer.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Partie B : DHCP et préfixes

Un serveur DHCP attribue des paramètres IPv4. Le sigle développé correctement est Dynamic Host Configuration Protocol ; le document imprime « Control ». Une IPv4 a quatre octets. En notation CIDR /n, les n premiers bits définissent le réseau ; le masque contient n bits à 1 suivis de zéros. Le ET bit à bit entre une adresse et le masque donne l’adresse réseau.

192 : 11000000
255 : 11111111
 ET : 11000000 = 192
Tableau à compléterOctet 1Octet 2Octet 3Octet 4
Machine1100000010101000……
Masque11111111110000000000000000000000
Réseau binaire11000000………
Réseau décimal192………

Question 6

#

Compléter le tableau pour la machine 192.168.1.100 et le masque 255.192.0.0 afin de déterminer l’adresse réseau.

Comprendre la correction
DonnéeOctet 1Octet 2Octet 3Octet 4
Machine binaire11000000101010000000000101100100
Masque binaire11111111110000000000000000000000
Réseau binaire11000000100000000000000000000000
Réseau décimal19212800

Le réseau est 192.128.0.0. Au deuxième octet, seuls les deux bits de poids fort sont conservés par le masque : 10101000 AND 11000000 = 10000000. Ce masque correspond à /10, et ne se traite donc pas comme un /24.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Complémenter un octet échange ses 0 et ses 1. L’adresse de diffusion est le OU bit à bit entre l’adresse réseau et le complément du masque. Exemple :

12 : 00001100
 9 : 00001001
OU : 00001101 = 13

Question 7

#

Compléter le tableau de broadcast du réseau 192.128.0.0 de masque 255.192.0.0.

Comprendre la correction
DonnéeOctet 1Octet 2Octet 3Octet 4
Réseau11000000100000000000000000000000
Masque11111111110000000000000000000000
Complément00000000001111111111111111111111
Broadcast11000000101111111111111111111111
Décimal192191255255

Le broadcast est 192.191.255.255. Le complément du masque met à 1 tous les bits hôtes ; le OU les ajoute au préfixe réseau conservé.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

Question 8

#

Une machine reçoit 172.16.1.100, masque 255.255.0.0, passerelle 172.16.255.254. Donner l’adresse de LAN1, le broadcast et le nombre d’adresses distribuables, sans restrictions administratives supplémentaires.

Comprendre la correction
InformationRésultat
Réseau172.16.0.0/16
Broadcast172.16.255.255
Adresses du bloc65536
Adresses hôtes utilisables65534
Adresses restantes si la passerelle donnée est déjà réservée65533

Il reste 16 bits hôtes, donc 2¹⁶ = 65536 combinaisons. Réseau et diffusion ne s’attribuent pas à une machine, d’où 65534 adresses hôtes. La passerelle occupe déjà l’une d’elles : un pool DHCP qui doit l’exclure en conserve au plus 65533, avant d’éventuelles autres réservations. Le nombre habituellement attendu pour la capacité du sous-réseau est 65534 ; préciser ce qui est compté évite de redistribuer l’adresse de la passerelle.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

split sépare une chaîne : 'ab-pq-rs'.split('-') donne ['ab','pq','rs']. join assemble des chaînes : '.'.join(['ab','pq','rs']) donne 'ab.pq.rs'. L’opérateur & réalise un ET bit à bit entre entiers : 192 & 255 = 192.

class IPv4(object):
    def __init__(self, adresse: str):
        self.adresse = adresse

    def octets(self) -> list[int]:
        return [int(i) for i in self.adresse.split('.')]
def masquer(self, masque: str) -> str:
    # IPv4("192.168.1.100").masquer("255.192.0.0") donne "192.128.0.0"
    tmp = []
    ip = self.octets()
    crible = IPv4(masque).octets()
    for i in range(4):
        tmp.append(... & ...)
    return ".".join(...)

Question 9

#

Compléter les lignes 15 et 16 de masquer pour appliquer le masque et renvoyer le résultat décimal pointé.

Indice

Les quatre opérations ET donnent des entiers ; join attend des chaînes.

Comprendre la correction
def masquer(self, masque: str) -> str:
    tmp = []
    ip = self.octets()
    crible = IPv4(masque).octets()
    for i in range(4):
        tmp.append(ip[i] & crible[i])
    return '.'.join([str(octet) for octet in tmp])

Le ET porte sur les entiers correspondant au même octet. join attend des chaînes : on convertit donc chaque résultat avec str. Pour 192.168.1.100 et 255.192.0.0, les entiers calculés sont 192,128,0,0.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
def adresse_suivante(self, adresse_max: str) -> str:
    # 192.168.1.100 -> 192.168.1.101
    # 192.168.1.255 -> 192.168.2.0 si la borne est 192.168.255.254
    assert self.adresse < adresse_max
    liste_courante = self.octets()
    liste_suivante = list()
    retenue = 1
    for index in range(4):
        somme = liste_courante[3 - index] + retenue
        valeur, retenue = ..., ...
        liste_suivante = ...
    return '.'.join(liste_suivante)

Question 10

#

Compléter les lignes 19 et 20 de adresse_suivante, qui calcule l’adresse immédiatement suivante sous une borne maximale.

Indice

256 est la base de chaque octet : reste pour la valeur, quotient pour la retenue.

Comprendre la correction
def adresse_suivante(self, adresse_max: str) -> str:
    assert self.adresse < adresse_max
    liste_courante = self.octets()
    liste_suivante = []
    retenue = 1
    for index in range(4):
        somme = liste_courante[3 - index] + retenue
        valeur, retenue = somme % 256, somme // 256
        liste_suivante = [str(valeur)] + liste_suivante
    return '.'.join(liste_suivante)

Une IPv4 se comporte comme un nombre à quatre chiffres en base 256. On ajoute 1 en partant du dernier octet. Le reste modulo 256 donne l’octet résultat, le quotient entier donne la retenue. On ajoute chaque nouvelle chaîne devant la liste puisque les octets sont calculés de droite à gauche.

Limite de l’assertion fournie : comparer les adresses décimales comme des chaînes ne compare pas correctement leurs valeurs numériques. Par exemple, '192.168.1.9' < '192.168.1.10' est faux. Pour rendre la borne fiable, remplacer la ligne 13 par assert self.octets() < IPv4(adresse_max).octets(). Le calcul des retenues, lui, est correct. L’exemple .255 → octet suivant .0 est un successeur numérique, pas une promesse que cette adresse est attribuable sur tout masque.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
Choisissez une route et coupez un routeurUn atelier pour expérimenter

Sélectionnez RIP ou OSPF, puis simulez la panne de R2. L’atelier énumère les chemins simples vers R6 et visualise celui de coût minimal.

Lire le résultat de l’expérience initiale

R1 → R2 → R3 → R4 → R5 → R6

RIP et OSPF évaluent les mêmes chemins avec des critères différents. Le prochain saut est seulement le premier routeur après R1.

CheminCoût OSPFIntermédiaires
R1 → R2 → R3 → R4 → R5 → R6144
R1 → R2 → R5 → R6212
R1 → R2 → R4 → R5 → R6223
R1 → R5 → R61011
R1 → R4 → R5 → R61022
R1 → R4 → R3 → R2 → R5 → R61134
R1 → R4 → R2 → R5 → R61213

Le critère de distance doit être annoncé avant les calculs. Une panne retire les sommets et liaisons concernés, puis impose de recalculer les routes.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Résoudre le baguenaudier par récursivité mutuelle

Le baguenaudier est une réglette de n cases numérotées 1 àn. Une case est vide ou contient un pion. Jouer une case inverse cet état. Initialement toutes sont vides ; le but est de toutes les remplir.

  • On joue une seule case à la fois et elle contient au plus un pion.
  • On peut toujours jouer la case 1.
  • Si le jeu n’est ni vide ni rempli, on peut aussi jouer la case qui suit la première case remplie.
  • Aucune autre case n’est jouable.
Figure 1Case 1Case 2Case 3Case 4Case 5
État○○●●○

Dans cet exemple, on peut remplir 1 ou vider 4. Le tableau Python correspondant est [False, False, True, True, False]. Les indices Python commencent à 0, contrairement aux numéros de cases du jeu.

Question 1

#

Un baguenaudier à 4 cases a ses trois dernières cases remplies. Désigner les cases jouables et dessiner l’état après chacun des coups possibles.

Comprendre la correction
SituationCase 1Case 2Case 3Case 4
Départ○●●●
Jouer 1●●●●
Jouer 3○●○●

On peut jouer les cases 1 et 3. La première remplie est 2, donc son successeur est 3. En Python, ces coups auront pour indices 0 et 2.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 2

#

Écrire initialiser(n), qui renvoie un baguenaudier vide. Exemple initialiser(3) : [False, False, False].

Comprendre la correction
def initialiser(n):
    return [False for _ in range(n)]

Chaque case est un booléen faux. Une compréhension crée exactement n positions ; [False] * n conviendrait aussi puisque les booléens sont immuables.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
def victoire(tab):
    for etat_case in tab:
        if etat_case == ...:
            return ...
    ...

Question 3

#

Compléter les lignes 3,4 et 5 de victoire, qui indique si toutes les cases sont remplies.

Comprendre la correction
def victoire(tab):
    for etat_case in tab:
        if etat_case == False:
            return False
    return True

Dès qu’une case vide est rencontrée, la victoire est impossible et on renvoie faux. Le retour vrai doit attendre la fin de la boucle. Pour une liste vide, cette définition renvoie vrai par convention logique ; le jeu physique suppose normalement n positif.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
indice_premiere_case_occupee([True, True, True]) # 0
indice_premiere_case_occupee([False, False, False, True, True]) # 3
indice_premiere_case_occupee([False, False, False, False]) # None

Question 4

#

Écrire indice_premiere_case_occupee(tab), qui renvoie le premier indice rempli ou None si tout est vide.

Comprendre la correction
def indice_premiere_case_occupee(tab):
    for i in range(len(tab)):
        if tab[i]:
            return i
    return None

On parcourt les indices de gauche à droite et on retourne immédiatement le premier vrai. Si aucun n’est trouvé, le retour est None. L’indice 0 est une réponse valide : il ne doit pas être confondu avec l’absence de résultat.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Question 5

#

Écrire coup_valide(tab, case), qui vérifie les règles et que l’indice est un entier entre 0 inclus et la longueur exclue.

PythonAutoriser le bon anneau du baguenaudierÉcrivez votre solution et mettez-la à l’épreuve

Écrivez coup_valide(tab, case). Le plateau est une liste de booléens, True pour une case occupée. Un indice entier valide vaut de 0 à len(tab)-1. La case 0 peut toujours être changée. Une autre case peut être changée si elle suit immédiatement la première case occupée et si le plateau n’est pas entièrement occupé. Les fonctions indice_premiere_case_occupee et victoire sont fournies. Le plateau ne doit pas être modifié.

def coup_valide(tab, case):
    # À vous de jouer
    pass

Les cas de test proposés :

  • La case zéro reste accessible : Les autres restrictions concernent les cases suivantes, pas la case zéro.
  • Un premier pion à l’indice zéro : L’indice 0 n’est pas l’absence de pion : 1 est la case autorisée.
  • Des cases vides avant le premier pion : Il faut trouver le premier True, puis viser exactement la position suivante.
  • Pas de pion ou un plateau plein : Sans premier pion, aucune case après zéro ne convient ; le plateau plein est un autre cas explicitement exclu.
  • Bornes et types invalides : Un indice flottant, un booléen ou une chaîne ne satisfait pas le contrat d’un véritable entier d’indice.
  • La vérification ne joue pas le coup : La modification du plateau appartient à changer_case, pas à coup_valide.
Indice 1

Contrôlez les bornes avant toute lecture de tab[case].

Indice 2

La première case remplie peut avoir l’indice 0 : comparez explicitement à None.

Comprendre la correction
def coup_valide(tab, case):
    if type(case) is not int or case < 0 or case >= len(tab):
        return False
    if case == 0:
        return True
    premier = indice_premiere_case_occupee(tab)
    return premier is not None and not victoire(tab) and case == premier + 1

On contrôle les bornes avant de manipuler la case. L’indice 0 est toujours autorisé s’il existe. Pour les autres indices, il faut un premier pion, un plateau pas déjà plein, et viser exactement la position suivante.

Tester premier is not None est nécessaire : un premier pion d’indice 0 autorise précisément l’indice 1. Un test if premier le rejetterait par erreur. Le test de type exclut aussi les booléens, qui sont des sous-types d’entier en Python.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
changer_case([False, False, False], 1) # [False, False, False]
changer_case([False, False, False], 0) # [True, False, False]
changer_case([False, True, False], 2)  # [False, True, True]

Question 6

#

Écrire changer_case(tab, case). Un coup valide inverse la case et renvoie le plateau ; un coup invalide renvoie le plateau sans modification.

Comprendre la correction
def changer_case(tab, case):
    if coup_valide(tab, case):
        tab[case] = not tab[case]
    return tab

not inverse directement un booléen. Le tableau est modifié en place uniquement dans la branche valide, puis le même objet est retourné. La fonction n’a pas à dupliquer les règles de légalité : elle réutilise coup_valide.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

Les figures 2 et 3 donnent les suites d’états suivantes, à lire de haut en bas :

ÉtapeFigure 2 :3 casesFigure 3 :4 cases
0○ | ○ | ○○ | ○ | ○ | ○
1● | ○ | ○● | ○ | ○ | ○
2● | ● | ○● | ● | ○ | ○
3○ | ● | ○○ | ● | ○ | ○
4○ | ● | ●○ | ● | ● | ○
5● | ● | ●● | ● | ● | ○
6● | ○ | ● | ○
7○ | ○ | ● | ○
8○ | ○ | ● | ●
9● | ○ | ● | ●
10● | ● | ● | ●

Les fonctions récursives vider et remplir affichent les actions sur les n premières cases. À partir d’ici, les cases sont numérotées 1 à n. vider(0) et remplir(0) ne font rien.

def vider(n):
    if n == 1:
        print('Vider case 1')
    elif n > 1:
        vider(n - 2)
        print(...)
        remplir(n - 2)
        vider(n - 1)

Question 7

#

Compléter la ligne 6 de vider pour afficher « Vider case » suivi de n.

Comprendre la correction
print('Vider case ' + str(n))

On convertit l’entier en chaîne avant la concaténation. L’alternative print('Vider case', n) donne également le bon affichage grâce à l’espace ajouté par print.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Question 8

#

Sachant que remplir(1) affiche « Remplir case 1 » et que remplir(0) ne fait rien, donner les affichages de vider(3).

Comprendre la correction
Vider case 1
Vider case 3
Remplir case 1
Vider case 2
Vider case 1

L’appel commence par vider(1), puis vide 3, rétablit 1 et termine avec vider(2). Ce dernier ne produit rien pour ses appels d’argument 0, puis affiche vider 2 et vider 1. On doit suivre l’ordre d’exécution, pas seulement lister les appels directs de vider 3.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Question 9

#

En vous inspirant de vider et des résolutions données, écrire la fonction récursive remplir.

Indice 1

Inversez les opérations de vider et l’ordre dans lequel elles sont faites.

Indice 2

Avant de jouer la case n, il faut que la première case occupée soit n-1.

Comprendre la correction
def remplir(n):
    if n == 1:
        print('Remplir case 1')
    elif n > 1:
        remplir(n - 1)
        vider(n - 2)
        print('Remplir case ' + str(n))
        remplir(n - 2)

Pour remplir n, on commence par remplir les n-1 premières cases. On vide ensuite les n-2 premières : la première case occupée devient n-1 et autorise alors n. On remplit n, puis on restaure les n-2 premières. C’est l’inversion, dans l’ordre inverse, des opérations de vider.

Les fonctions manipulent des préfixes d’un plateau plus grand : « vider les deux premières cases » ne signifie pas que le plateau global a seulement deux cases. Cette distinction permet de relier leurs appels récursifs aux règles du jeu.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Question 10

#

La fonction vider est-elle adaptée à 2000 cases ? Justifier.

Comprendre la correction

Non. Le nombre d’actions suit T(n) = T(n - 1) + 2T(n - 2) + 1, avec T(0)=0 et T(1)=1. Il croît exponentiellement, de l’ordre de 2ⁿ. Les premières valeurs 1,2,5,10,21 montrent déjà la croissance ; à 2000, afficher la solution est irréaliste.

En plus, la profondeur de récursion croît avec n et peut dépasser la limite de Python. Augmenter cette limite ou écrire une version itérative n’éliminerait pas le nombre astronomique de coups à produire.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
Jouez une séquence et voyez où elle bloqueUn atelier pour expérimenter

Entrez les numéros des cases à jouer, séparés par des espaces ou virgules. Le plateau démarre vide. L’atelier exécute chaque coup autorisé, explique les refus et indique les prochains coups possibles.

Lire le résultat de l’expérience initiale

Toutes les cases sont remplies !

Les numéros saisis commencent à1 ; le code Python utilise des indices à partir de0. Un coup refusé ne change aucune case.

Case demandéeValiditéPlateau après le coupExplication
1Joué● ○ ○La case a changé d’état
2Joué● ● ○La case a changé d’état
1Joué○ ● ○La case a changé d’état
3Joué○ ● ●La case a changé d’état
1Joué● ● ●La case a changé d’état

Un appel récursif résout une partie du plateau pour rendre possible le coup décisif, puis rétablit les cases temporairement vidées. Le détour fait partie de la solution.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Gestionnaire de mots de passe : génération, SQL, XOR et droits Linux

Alice crée un gestionnaire pour rassembler ses informations de connexion. Cette étude reprend un modèle d’examen ; les programmes de génération et de chiffrement servent à comprendre leurs mécanismes et leurs limites.

Partie A : générer des mots de passe

La figure 1 donne les codes ASCII décimaux et hexadécimaux. Les caractères spéciaux encadrés sont exactement ceux des plages 33 à 47 et 58 à 64.

DécimalHexadécimalCaractèreCatégorie
3220SPNon retenu dans gen_mdp
3321!Spécial autorisé
3422"Spécial autorisé
3523#Spécial autorisé
3624$Spécial autorisé
3725%Spécial autorisé
3826&Spécial autorisé
3927'Spécial autorisé
4028(Spécial autorisé
4129)Spécial autorisé
422A*Spécial autorisé
432B+Spécial autorisé
442C,Spécial autorisé
452D-Spécial autorisé
462E.Spécial autorisé
472F/Spécial autorisé
48300Non retenu dans gen_mdp
49311Non retenu dans gen_mdp
50322Non retenu dans gen_mdp
51333Non retenu dans gen_mdp
52344Non retenu dans gen_mdp
53355Non retenu dans gen_mdp
54366Non retenu dans gen_mdp
55377Non retenu dans gen_mdp
56388Non retenu dans gen_mdp
57399Non retenu dans gen_mdp
583A:Spécial autorisé
593B;Spécial autorisé
603C<Spécial autorisé
613D=Spécial autorisé
623E>Spécial autorisé
633F?Spécial autorisé
6440@Spécial autorisé
6541AMajuscule autorisée
6642BMajuscule autorisée
6743CMajuscule autorisée
6844DMajuscule autorisée
6945EMajuscule autorisée
7046FMajuscule autorisée
7147GMajuscule autorisée
7248HMajuscule autorisée
7349IMajuscule autorisée
744AJMajuscule autorisée
754BKMajuscule autorisée
764CLMajuscule autorisée
774DMMajuscule autorisée
784ENMajuscule autorisée
794FOMajuscule autorisée
8050PMajuscule autorisée
8151QMajuscule autorisée
8252RMajuscule autorisée
8353SMajuscule autorisée
8454TMajuscule autorisée
8555UMajuscule autorisée
8656VMajuscule autorisée
8757WMajuscule autorisée
8858XMajuscule autorisée
8959YMajuscule autorisée
905AZMajuscule autorisée
915B[Non retenu dans gen_mdp
925C\Non retenu dans gen_mdp
935D]Non retenu dans gen_mdp
945E^Non retenu dans gen_mdp
955F_Non retenu dans gen_mdp
9660`Non retenu dans gen_mdp
9761aMinuscule autorisée
9862bMinuscule autorisée
9963cMinuscule autorisée
10064dMinuscule autorisée
10165eMinuscule autorisée
10266fMinuscule autorisée
10367gMinuscule autorisée
10468hMinuscule autorisée
10569iMinuscule autorisée
1066AjMinuscule autorisée
1076BkMinuscule autorisée
1086ClMinuscule autorisée
1096DmMinuscule autorisée
1106EnMinuscule autorisée
1116FoMinuscule autorisée
11270pMinuscule autorisée
11371qMinuscule autorisée
11472rMinuscule autorisée
11573sMinuscule autorisée
11674tMinuscule autorisée
11775uMinuscule autorisée
11876vMinuscule autorisée
11977wMinuscule autorisée
12078xMinuscule autorisée
12179yMinuscule autorisée
1227AzMinuscule autorisée
1237B{Non retenu dans gen_mdp
1247C|Non retenu dans gen_mdp
1257D}Non retenu dans gen_mdp
1267E~Non retenu dans gen_mdp

gen_mdp(longueur, cont_min, cont_maj, cont_spe) renvoie une chaîne de la longueur demandée. Chaque booléen indique si la catégorie peut apparaître. L’opérateur+concatène aussi les listes ou les chaînes. chr(97) donne a ; chr(33) donne !.

from random import randint
def gen_mdp(longueur, cont_min, cont_maj, cont_spe):
    assert cont_min or cont_maj or cont_spe
    minuscules = [chr(i) for i in ...]
    majuscules = [...]
    caracteres_speciaux = ... + ...
    jeu_caracteres = []
    if cont_min:
        ...
    ...
        ...
    ...
        ...
    mot_de_passe = ''
    n = len(jeu_caracteres)
    for i in range(longueur):
        mot_de_passe = ...
    return mot_de_passe

Question 1

#

Écrire un appel générant au moins 8 caractères, uniquement des majuscules et minuscules.

Comprendre la correction
gen_mdp(8, True, True, False)

8 satisfait la longueur minimale et False interdit les caractères spéciaux. Cet appel autorise les deux types de lettres ; il ne garantit pas que chacun apparaisse, distinction étudiée en Q5.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 2

#

Compléter les initialisations de minuscules, majuscules et caractères spéciaux en utilisant obligatoirement des compréhensions.

Comprendre la correction
minuscules = [chr(i) for i in range(97, 123)]
majuscules = [chr(i) for i in range(65, 91)]
caracteres_speciaux = ([chr(i) for i in range(33, 48)]
                      + [chr(i) for i in range(58, 65)])

Les bornes droites de range sont exclues :123 inclut le code 122 de z,91 inclut 90 de Z. Les deux intervalles spéciaux évitent les chiffres 48 à 57, qui ne sont pas autorisés dans cette fonction. On obtient 26 minuscules,26 majuscules et 22 spéciaux.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 3

#

Compléter la construction de jeu_caracteres, la liste de tous les caractères autorisés.

Indice

Plusieurs catégories peuvent être autorisées simultanément.

Comprendre la correction
if cont_min:
    jeu_caracteres = jeu_caracteres + minuscules
if cont_maj:
    jeu_caracteres = jeu_caracteres + majuscules
if cont_spe:
    jeu_caracteres = jeu_caracteres + caracteres_speciaux

Les trois tests sont indépendants : plusieurs catégories peuvent être activées à la fois. Une chaîne de elif n’en ajouterait qu’une, ce qui serait faux. L’assertion garantit qu’au moins une catégorie est choisie, donc que la liste n’est pas vide.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 4

#

Compléter la ligne 21. randint(a,b) inclut ses deux bornes.

Comprendre la correction
mot_de_passe = mot_de_passe + jeu_caracteres[randint(0, n - 1)]

Les indices valides vont de 0 à n-1. Utiliser n comme borne pourrait produire un indice hors liste. Chaque tirage ajoute un caractère à la chaîne en construction.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 5

#

Un site exige au moins 12 caractères, dont au moins un spécial et au moins une minuscule. Pourquoi gen_mdp peut-il produire un mot de passe non conforme ?

Comprendre la correction

Même avec gen_mdp(12, True, False, True), chaque tirage peut choisir une minuscule : il est possible d’obtenir 12 lettres sans aucun spécial. Inversement, tous les tirages peuvent choisir des spéciaux. Les booléens autorisent une catégorie, ils n’imposent pas sa présence.

Pour garantir la composition, il faudrait choisir d’abord un caractère de chaque catégorie obligatoire, compléter jusqu’à la longueur voulue et mélanger les positions. Pour un véritable secret, on utiliserait aussi une source aléatoire adaptée à la sécurité, comme secrets, plutôt que le générateur random de l’exercice.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Partie B : comptes et sites

mot_de_passe (clé primaire)utilisateurrenouvellementid_site
Asrtg!MyfjaliceB242022-06-301
@rDfohpj!&aliceB242021-03-122
GxRGDxc(u-PMalice_B@votremailp.me2018-10-144
Ghcj=+f*AZsalice12762022-06-303
cYFgt!:Ehr;alice_B2@votremailp.me2022-06-304
id (clé primaire)nom_siteurl
1Vosnoteshttps://logi-educ.net/vosnotes/eleve.html
2Banque Persohttps://www.banqueperso.fr/connexion.html
3Elec vertehttps://espace-client.ev.fr/login
4Votremailphttps://account.votremailp.me/login

compte.id_site référence site.id. renouvellement est une chaîne AAAA-MM-JJ, par exemple 2025-02-21 pour le 21 février 2025.

Question 6

#

Pourquoi Alice ne peut-elle pas utiliser le même mot de passe pour deux sites dans ce modèle ?

Comprendre la correction

mot_de_passe est la clé primaire de compte, donc chaque valeur doit être unique dans toute la table. Une seconde ligne contenant le même mot de passe serait rejetée même si son site diffère. C’est une contrainte du schéma choisi, pas une impossibilité générale du Web.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 8

#

Remplacer le mot de passe @rDfohpj!& par yhTS?d@UTJe pour Banque Perso. Ignorer volontairement le champ renouvellement.

Comprendre la correction
UPDATE compte
SET mot_de_passe = 'yhTS?d@UTJe'
WHERE mot_de_passe = '@rDfohpj!&';

Le filtre sur l’ancienne clé identifie une seule ligne. La question demande explicitement de ne pas changer la date ; une application réelle devrait normalement mettre cette information à jour aussi.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 9

#

Au 20 mars 2025, lister les id_site des mots de passe non renouvelés depuis plus d’un an.

Comprendre la correction
SELECT DISTINCT id_site
FROM compte
WHERE renouvellement < '2024-03-20';

« Plus d’un an » exclut la date exactement égale au 20 mars 2024 : on utilise un test strict. DISTINCT évite de répéter un site qui possède plusieurs comptes anciens, comme Votremailp dans l’extrait.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 10

#

Pourquoi préférer AAAA-MM-JJ à JJ-MM-AAAA pour une date stockée en texte ?

Comprendre la correction

À longueur fixe et avec les zéros initiaux, l’ordre lexicographique compare d’abord l’année, puis le mois, puis le jour : il coïncide avec l’ordre chronologique. En JJ-MM-AAAA, comparer les jours d’abord ferait par exemple placer 02-01-2025 avant 31-12-2024, alors que la première date est plus récente.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 11

#

Afficher utilisateurs et mots de passe du ou des sites nommés Votremailp, sans connaître leur identifiant, par ordre chronologique de renouvellement.

SQLClasser les comptes Votremailp par renouvellementÉcrivez votre solution et mettez-la à l’épreuve

Affichez utilisateur et mot_de_passe pour le ou les sites de nom Votremailp. L’identifiant du site est inconnu ; triez les lignes par date de renouvellement croissante.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Comptes fictifs donnés dans le sujet : Les données sont les comptes fictifs imprimés dans le PDF. Le compte renouvelé en 2018 doit précéder celui de 2022.
  • Cas complémentaire : ordre physique trompeur : Jeu complémentaire pédagogique, distinct des données officielles. Ces secrets sont des chaînes factices de test. Le nom peut désigner deux lignes de site ; l’identifiant 4 ne convient plus et l’insertion est dans l’ordre inverse du résultat.
Comprendre la correction
SELECT compte.utilisateur, compte.mot_de_passe
FROM compte JOIN site ON compte.id_site = site.id
WHERE site.nom_site = 'Votremailp'
ORDER BY compte.renouvellement;

La jointure permet de filtrer avec le nom du site tout en affichant les données des comptes. Le tri croissant met le renouvellement de 2018 avant celui de 2022. Ne pas coder directement l’identifiant 4 : la requête doit rester valable si cet identifiant change.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 12

#

Donner un avantage d’avoir séparé site et compte plutôt que tout mettre dans une seule table.

Comprendre la correction

Les données d’un site, son nom et son URL, ne sont stockées qu’une fois même si Alice y possède plusieurs comptes. Si l’URL change, une seule mise à jour suffit et tous les comptes retrouvent la même valeur via la référence. Cela réduit la redondance et les incohérences possibles.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Partie C : protéger le fichier

Alice travaille sur un système Linux multi-utilisateur et possède les droits nécessaires dans l’arborescence donnée. La fonction chiffrement(f_source, f_dest, f_cle) lit la source, écrit un fichier chiffré et utilise la clé du troisième fichier ; les trois arguments sont des chemins de type str.

/homeDocumentsPersochiffrement.pygestionnaire.dbcle
Figure 2 : arborescence des fichiers, répertoire courant Documents
Lire les connexions du schéma
  • / relié à home
  • home relié à Documents
  • home relié à Perso
  • Documents relié à chiffrement.py
  • Documents relié à gestionnaire.db
  • Perso relié à cle

Question 13

#

Depuis le répertoire Documents, écrire l’appel chiffrant gestionnaire.db avec la clé cle pour créer secret.db dans Perso.

Comprendre la correction
chiffrement('gestionnaire.db', '../Perso/secret.db', '../Perso/cle')

La source se trouve dans le répertoire courant. .. remonte de Documents vers home, puis on entre dans Perso pour la destination et la clé. Un chemin est un argument de la fonction : il n’est pas nécessaire de déplacer le répertoire courant.

Voir la question dans le sujet PDF, p. 17 (nouvel onglet)

Le chiffrement applique XOR bit à bit entre le fichier source et une clé de même taille. La sortie possède aussi cette taille. Rappel : la base 16 utilise 0 à 9 puis AàF.

aba XOR b
000
011
101
110

Question 14

#

Donner en hexadécimal le XOR des octets A3 et 59.

Comprendre la correction
 A3 : 10100011
 59 : 01011001
XOR : 11111010 = FA

On compare les huit positions indépendamment : le résultat est 1 si les bits diffèrent. On regroupe ensuite en deux paquets de quatre :1111 vaut F et 1010 vaut A.

Voir la question dans le sujet PDF, p. 18 (nouvel onglet)

Question 15

#

Montrer que pour tous bits a, b, (a XOR b) XOR b = a.

Comprendre la correction
aba XOR b(a XOR b) XOR b
0000
0110
1011
1101

Les quatre possibilités donnent a dans la dernière colonne. On peut aussi utiliser l’associativité : (a XOR b) XOR b = a XOR (b XOR b) = a XOR0 = a.

Voir la question dans le sujet PDF, p. 18 (nouvel onglet)

Question 16

#

Le chiffrement est-il symétrique ou asymétrique ? Justifier.

Comprendre la correction

Il est symétrique : la même clé permet de chiffrer et déchiffrer. Réappliquer XOR avec cette clé à chaque bit du texte chiffré rend le bit source, d’après la propriété démontrée.

La seule égalité de taille ne suffit pas à prouver la sécurité : une clé de masque jetable doit être aléatoire, secrète et non réutilisée. Ces propriétés ne sont pas garanties par la simple description de la fonction.

Voir la question dans le sujet PDF, p. 18 (nouvel onglet)
-rw-r--r-- 1 alice eleves 42480 mars 25 11:57 secret.db

Question 17

#

Avec les droits affichés, pourquoi un attaquant peut-il tenter de déchiffrer secret.db et que devrait faire Alice ?

Indice

La première lettre de chaque bloc rwx indique la lecture.

Comprendre la correction
chmod 600 secret.db

-rw-r--r-- donne lecture-écriture à Alice, lecture au groupe eleves et lecture aux autres utilisateurs. Tout utilisateur autorisé à traverser les répertoires peut donc lire et copier le fichier pour l’analyser hors ligne.

chmod600 garde lecture et écriture pour Alice et retire les droits au groupe et aux autres. Elle doit aussi protéger la clé, le fichier source non chiffré et les répertoires concernés. Restreindre l’accès ne renforce pas mathématiquement le chiffrement, mais réduit les possibilités d’en récupérer une copie.

Voir la question dans le sujet PDF, p. 18 (nouvel onglet)

Partie D : pratiques à confronter au projet

Le sujet cite quatre pratiques issues de cyber.gouv.fr/bonnes-pratiques-protegez-vous : P1, utiliser un mot de passe distinct pour chaque accès ; P2, créer un mot de passe long, complexe et inattendu, au moins 8 caractères avec minuscules, majuscules, chiffres et spéciaux ; P3, ne jamais le communiquer à un tiers ; P4, utiliser un gestionnaire qui mémorise les mots de passe et permet d’en générer.

Question 18

#

En justifiant, indiquer si les choix d’Alice respectent chacune des pratiques P1à P4 du sujet.

Comprendre la correction
Pratique citée dans le sujetAnalyse du modèle d’Alice
P1 : un mot de passe différent par accèsLa clé primaire mot_de_passe interdit deux valeurs identiques dans compte. Cela encourage la différence entre les comptes stockés, sans contrôler d’autres comptes éventuels hors de la base.
P2 : au moins 8 caractères, minuscules, majuscules, chiffres et spéciauxPas garanti : longueur est libre, la présence des catégories autorisées n’est pas imposée et aucun chiffre ne figure dans le jeu de caractères.
P3 : ne jamais communiquer son mot de passe à un tiersL’énoncé ne dit pas qu’Alice les divulgue volontairement. En revanche, les permissions trop ouvertes exposent des fichiers et doivent être corrigées. On ne peut pas déduire son comportement personnel du seul code.
P4 : utiliser un gestionnaire capable de mémoriser et générerLe projet mémorise les comptes et génère des chaînes, mais cela ne certifie pas sa sécurité. La qualité du hasard, la clé et les accès demandent les contrôles étudiés.

Cette question analyse les quatre critères reproduits dans le PDF. Il ne faut pas transformer une recommandation datée de longueur minimale en garantie actuelle suffisante pour tous les comptes.

Voir la question dans le sujet PDF, p. 19 (nouvel onglet)
XOR deux fois : le secret réapparaîtUn atelier pour expérimenter

Choisissez deux octets. Observez chaque bit du message, de la clé et du texte chiffré, puis le second XOR qui restitue le message.

Lire le résultat de l’expérience initiale

A3 XOR 59 = FA ; FA XOR 59 = A3

Le même octet de clé inverse deux fois les mêmes bits : ceux à1dans la clé basculent, puis rebasculent. Une clé nulle laisse le message inchangé.

PositionMessageCléChiffréDéchiffré
71011
60110
51011
40110
30110
20000
11011
01101

La réversibilité du XOR démontre comment déchiffrer ; elle ne démontre pas que la clé est imprévisible ou protégée. Correction du calcul et sécurité de son usage sont deux vérifications distinctes.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Sur RIP, suivez la convention numérique des lignes déjà remplies au lieu de plaquer un autre comptage.
  • Pour le baguenaudier, écrivez sur la copie si vous utilisez les cases 1 à n ou les indices 0 à n-1.
  • Dans l’analyse de sécurité, distinguez ce que le code garantit, ce qu’il autorise et ce que l’énoncé ne permet pas de conclure.

Retrouver ces notions dans d’autres sujets

Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

Énoncé : sujet 25-NSIJ1PO1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.