SHA-1 from scratch in assembly
D’où vient ce projet ?
Section titled “D’où vient ce projet ?”Lors de ma deuxième année d’étude, dans le cadre d’un cours d’assembleur j’ai eu l’opportunité de me lancer un défi : créer un système d’authentification sécurisé simple, permettant à un utilisateur de se connecter à une application fictive. Le coeur principalement intérêssant de ce projet résidait dans le traitement sécurisé de mots de passe, ce qui signifiait implémenter un algorithme de hashage.
Pour cela je me suis orienté vers l’algorithme SHA-1, bien qu’il soit considéré comme cryptographiquement dépassé aujourd’hui, celui-ci reste proche algorithmiquement de son évolution SHA-2 (qui lui est toujours valide à ce jour) tout en limitant sa complexité d’implémentation, et évitant tout de même d’être ridiculement faible à la manière du MD5.
Méthodologie
Section titled “Méthodologie”Mon approche pour ce projet s’est déroulée en trois étapes clés :
-
Compréhension théorique : Pour la compréhension conceptuelle de l’algorithme je me suis servis de cette page de Brilliant, qui explique rigouresement d’un point de vue mathématique comment fonctionne l’algorithme.
-
Prototypage : Partir d’une page blanche et convertir directement l’algo de la théorie à l’assembleur est une tâche difficile. Pour créer une étape intermédiaire, je me suis basé sur une implémentation Javascript de référence. A partir de celle-ci, j’ai ensuite utilisé ChatGPT comme un outil d’assistance pour traduire la logique en C. L’objectif était d’obtenir rapidement un code “proche de la machine”, qui manipulait directement les octets et les mots, afin d’avoir un modèle clair avant de descendre au niveau des registres.
-
Conversion en Assembleur : Cette étape a représenté le coeur du projet. J’ai traduit la logique du programme du C vers l’assembleur, me confrontant évidemment à des défis inattendu, bien au delà de la simple traduction.
Démonstration
Section titled “Démonstration”Voici un texte hashé avec mon implémentation :

Et le même texte hashé sur un site en ligne :

Un point technique intéressant : Hexadécimal vs sa Représentation
Section titled “Un point technique intéressant : Hexadécimal vs sa Représentation”Créer un programme de ce type en n’ayant aucune couche d’abstraction nous met parfois face à des problématiques innatendu, généralement implicite dans les langages plus haut niveau.
En effet par exemple, l’algorithme nous permet de générer un hash du contenu d’entrée, qui est affiché en tant qu’une représentation hexadécimale des valeurs réellement utilisé dans le calcul (et donc présente mémoire).
Pour clarifier cela, notez que le chiffre hexa 0x11 est stocké en mémoire tel quel (en tant que 11 hexa), mais sa représentation est stocké avec les charactères texte 1, ayant pour valeur ascii 0x31. Donc la représentation de la valeur 0x11 en mémoire est 0x3131 lorsqu’elle doit être affiché en ascii.
Il y a donc une étape de convertion supplémentaire nécessaire à la fin de l’algorithme, cela est habituellement pris en charge par les fonctions de formatage en C :
// h0 - h4 sont des valeurs hexa pure// les "%x" indique implicitement à sprintf d'effectuer la conversion discutéssprintf(output, "%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4);Réimplémenter ce comportement en assembleur a été un exellent exercice de manipulation de chaîne de caractères au plus bas niveau.
Limites et portée du Projet
Section titled “Limites et portée du Projet”Ce projet a été réalisé dans un cadre académique avec une contrainte de temps. L’objectif était de produire une preuve de concept fonctionnelle pour un système d’authentification, en se concentrant sur l’algorithme SHA-1 en assembleur pour des cas d’usage simples.
Pour valider mon implémentation, je l’ai testée en comparant sa sortie avec celle de l’algorithme SHA-1 standard pour différentes entrées. J’ai alors fait une observation intéressante :
Pour les entrées courtes (jusqu’à 31 caractères), mon programme produisait un hash parfaitement identique à la référence. Cela validait la bonne implémentation du coeur de l’algorithme (les boucles de compression, les rotations de bits, et la gestion de l’endianness).
Pour les entrées plus longues, les résultats commençaient à diverger.
Cette divergence indique probablement que ma gestion du “padding” et du traitement des messages qui s’étendent sur plusieurs blocs de 512 bits, bien que fonctionnelle pour le cas normal, n’était pas complète ou correcte pour tous les cas limites.
Dans le contexte du projet (hachage de mots de passe, généralement courts), cette limitation était acceptable. Cependant, cela a été une leçon très concrète : la différence entre une implémentation qui “marche en surface” et une librairie cryptographique robuste réside entre autre dans la gestion exhaustive et rigoureuse de tous ces cas particuliers.
Vous trouverez le code source sur le répository suivant :