web-dev-qa-db-fra.com

Comment Git gérerait-il une collision SHA-1 sur un blob?

Cela n’est probablement pas encore arrivé dans le monde réel, et cela ne se produira peut-être jamais, mais considérons ceci: supposons que vous ayez un dépôt git, faites un commit et soyez vraiment très malchanceux: une des blobs finit par avoir le même SHA-1. comme un autre qui est déjà dans votre référentiel. La question est, comment Git gérerait-il cela? Simplement échouer? Trouver un moyen de relier les deux blobs et vérifier lequel est nécessaire en fonction du contexte?

Plus un casse-tête qu'un problème réel, mais j'ai trouvé la question intéressante.

524
Gnurou

J'ai fait une expérience pour savoir exactement comment Git se comporterait dans ce cas. Ceci est avec la version 2.7.9 ~ rc0 + suivante.20151210 (version Debian). En gros, je viens de réduire la taille de hachage de 160 bits à 4 bits en appliquant le diff suivant et en reconstruisant git:

--- git-2.7.0~rc0+next.20151210.orig/block-sha1/sha1.c
+++ git-2.7.0~rc0+next.20151210/block-sha1/sha1.c
@@ -246,6 +246,8 @@ void blk_SHA1_Final(unsigned char hashou
    blk_SHA1_Update(ctx, padlen, 8);

    /* Output hash */
-   for (i = 0; i < 5; i++)
-       put_be32(hashout + i * 4, ctx->H[i]);
+   for (i = 0; i < 1; i++)
+       put_be32(hashout + i * 4, (ctx->H[i] & 0xf000000));
+   for (i = 1; i < 5; i++)
+       put_be32(hashout + i * 4, 0);
 }

Ensuite, j'ai fait quelques commits et remarqué ce qui suit.

  1. Si un blob existe déjà avec le même hash, vous ne recevrez aucun avertissement. Tout semble aller pour le mieux, mais si vous poussez, si quelqu'un clone ou si vous revenez en arrière, vous perdrez la dernière version (conformément à ce qui est expliqué ci-dessus).
  2. Si un objet tree existe déjà et que vous créez un blob avec le même hachage: tout semblera normal, jusqu'à ce que vous essayiez de Push ou que quelqu'un clone votre référentiel. Ensuite, vous verrez que le repo est corrompu.
  3. Si un objet de validation existe déjà et que vous créez un blob avec le même hash: identique à # 2 - corrompu
  4. Si un blob existe déjà et que vous créez un objet commit avec le même hachage, il échouera lors de la mise à jour du "ref".
  5. Si un blob existe déjà et que vous créez un objet arbre avec le même hachage. Cela échouera lors de la création du commit.
  6. Si un objet d'arborescence existe déjà et que vous créez un objet de validation avec le même hachage, il échouera lors de la mise à jour du "ref".
  7. Si un objet d'arborescence existe déjà et que vous en créez un avec le même hachage, tout vous semblera correct. Mais lorsque vous vous engagez, tout le référentiel fera référence à la mauvaise arborescence.
  8. Si un objet de validation existe déjà et que vous créez un objet de validation avec le même hachage, tout semblera correct. Mais lorsque vous validez, le commit ne sera jamais créé et le pointeur HEAD sera déplacé vers un ancien commit.
  9. Si un objet de validation existe déjà et que vous créez un objet d'arborescence avec le même hachage, il échouera lors de la création de la validation.

Pour # 2, vous obtiendrez généralement une erreur comme celle-ci lorsque vous exécuterez "git Push":

error: object 0400000000000000000000000000000000000000 is a tree, not a blob
fatal: bad blob object
error: failed to Push some refs to Origin

ou:

error: unable to read sha1 file of file.txt (0400000000000000000000000000000000000000)

si vous supprimez le fichier puis exécutez "git checkout file.txt".

Pour les numéros 4 et 6, vous obtiendrez généralement une erreur comme celle-ci:

error: Trying to write non-commit object
f000000000000000000000000000000000000000 to branch refs/heads/master
fatal: cannot update HEAD ref

lors de l'exécution de "git commit". Dans ce cas, vous pouvez généralement simplement taper à nouveau "git commit" car cela créera un nouveau hachage (en raison de l'horodatage modifié)

Pour les numéros 5 et 9, vous obtiendrez généralement une erreur comme celle-ci:

fatal: 1000000000000000000000000000000000000000 is not a valid 'tree' object

lors de l'exécution de "git commit"

Si quelqu'un essaie de cloner votre référentiel corrompu, il verra généralement quelque chose comme:

git clone (one repo with collided blob,
d000000000000000000000000000000000000000 is commit,
f000000000000000000000000000000000000000 is tree)

Cloning into 'clonedversion'...
done.
error: unable to read sha1 file of s (d000000000000000000000000000000000000000)
error: unable to read sha1 file of tullebukk
(f000000000000000000000000000000000000000)
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry the checkout with 'git checkout -f HEAD'

Ce qui me "préoccupe", c’est que dans deux cas (2,3) le référentiel est corrompu sans avertissement, et dans 3 cas (1,7,8), tout semble aller pour le mieux, mais le contenu du référentiel est différent de ce que vous attendez. être. Les personnes clonant ou tirant auront un contenu différent de celui que vous avez. Les cas 4, 5, 6 et 9 sont acceptables, car cela va s'arrêter avec une erreur. Je suppose que ce serait mieux s'il échouait avec une erreur au moins dans tous les cas.

690
Ruben

Réponse originale (2012) (voir _shattered.io_ Collision SHA1 2017 ci-dessous)

Cette ancienne réponse (2006) de Linus pourrait toujours être pertinente:

Nan. S'il a le même SHA1, cela signifie que lorsque nous recevons l'objet de l'autre extrémité, nous allons not ​​écraser l'objet que nous avons déjà.

Donc, ce qui se passe, c'est que si jamais nous voyons une collision, l'objet "antérieur" dans un référentiel particulier finira toujours par être prioritaire. Mais notez que "plus tôt" est évidemment pour chaque référentiel, en ce sens que le réseau d'objets git génère un DAG qui n'est pas entièrement ordonné. Ainsi, alors que différents référentiels s'accorderont sur ce qui est "plus tôt" dans le cas d'une ascendance directe, si objet est passé par des branches distinctes et non directement liées, deux pensions différentes peuvent évidemment avoir obtenu les deux objets dans un ordre différent.

Cependant, le "remplacement antérieur" correspond exactement à ce que vous voulez du point de vue de la sécurité: rappelez-vous que le modèle git est que vous devez principalement faire confiance à votre référentiel propre.
Donc, si vous faites un "_git pull_", les nouveaux objets entrants sont par définition moins fiables que les objets que vous avez déjà, et il serait donc erroné de permettre à un nouvel objet de remplacer un ancien un.

Donc, vous avez deux cas de collision:

  • le type par inadvertance , où vous êtes en quelque sorte très malchanceux, et deux fichiers finissent par avoir le même SHA1.
    À ce stade, il se produit que lorsque vous validez ce fichier (ou effectuez un "_git-update-index_" pour le déplacer dans l'index, mais pas encore validé), le SHA1 du nouveau contenu sera calculé, mais puisqu’il correspond à un ancien objet, aucun nouvel objet ne sera créé et le commit-or-index finira par pointer vers l’objet old.
    Vous ne le remarquerez pas immédiatement (car l'index correspondra à l'ancien objet SHA1, ce qui signifie que quelque chose comme "_git diff_" utilisera la copie extraite), mais si vous faites un Si vous faites un clone ou un tirage, ou forcez une commande, vous remarquerez soudain que ce fichier a changé en quelque chose complètement ​​différent de ce que vous attendiez.
    Ainsi, vous remarqueriez généralement ce genre de collision assez rapidement.
    Dans les nouvelles, la question est de savoir quoi faire à propos de la collision par inadvertance.
    Tout d’abord, permettez-moi de rappeler aux gens que le type de collision par inadvertance est vraiment vraiment ​​sacrément improbable, donc nous ne le verrons probablement jamais dans toute l’histoire de l’univers.
    Mais if cela arrive, ce n'est pas la fin du monde: ce que vous devrez probablement faire est de changer le fichier qui est entré en collision légèrement, et forcez simplement un nouveau commit avec le contenu modifié (ajoutez un commentaire disant "_/* This line added to avoid collision */_"), puis expliquez à git la magie SHA1 qui s'est révélée dangereuse.
    Donc, après quelques millions d’années, nous devrons peut-être ajouter une ou deux valeurs SHA1 "empoisonnées" à git. Il est très peu probable que ce soit un problème de maintenance;)

  • Le type de collision de l'attaquant parce que quelqu'un a cassé SHA1 (ou l'a forcé brutalement).
    Celui-ci est clairement un lot ​​plus probable que le type par inadvertance, mais par définition, il s'agit toujours d'un référentiel "distant". Si l'attaquant avait accès au référentiel local, il disposerait de moyens beaucoup plus simples pour vous bousiller.
    Donc, dans ce cas, la collision est entièrement un non-problème : vous obtiendrez un "mauvais" référentiel différent de l'intention de l'attaquant, mais , puisque vous n'utiliserez jamais son objet en collision, c'est littéralement ​​pas différent de l'attaquant qui n'a tout simplement pas trouvé de collision , mais en utilisant simplement l'objet que vous aviez déjà (c'est-à-dire qu'il équivaut à 100% à la collision "triviale" du fichier identique générant le même SHA1).

Le question de l'utilisation de SHA-256 est régulièrement mentionné, mais ne fait pas l'objet d'une action pour l'instant (2012).
Remarque: à partir de 2018 et de Git 2.19 , le code est en cours de refactoring pour utiliser SHA-256.


Note (Humour): vous pouvez forcer un commit sur un SHA1 particulier préfixe, avec le projet gitbrute de Brad Fitzpatrick (bradfitz) .

gitbrute brute-force une paire d’horodatages auteur + commetteur de telle sorte que le git commit résultant ait le préfixe souhaité.

Exemple: https://github.com/bradfitz/deadbeef


Daniel Dinnyes souligne dans les commentaires à 7.1 Outils Git - Sélection de révision , qui comprend:

Il existe une probabilité plus élevée que chaque membre de votre équipe de programmation soit attaqué et tué par des loups lors d'incidents sans lien le même soir.


Même plus récemment (février 2017) shattered.io a démontré la possibilité de falsifier une collision SHA1:
(voir beaucoup plus dans mon réponse séparée, y compris le message Google+ de Linus Torvalds)

  • a/still nécessite plus de 9 223 372 036 854 775 808 calculs SHA1. Cela a pris la puissance de traitement équivalente à 6 500 ans de calculs à processeur unique et à 110 ans de calculs à processeur unique.
  • b/allait forger n fichier (avec le même SHA1), mais avec la contrainte supplémentaire, son contenu et ​​taille produirait le SHA1 identique (une collision sur le contenu seul n'est pas assez): voir " Comment calcule-t-on le hachage git? "): a le blob SHA1 est calculé en fonction du contenu et ​​taille .

Voir " Durée de vie des fonctions de hachage cryptographique " dans Valerie Anita Aurora pour plus d'informations.
Dans cette page, elle note:

Google a passé 6500 années de processeur et 110 années de GPU pour convaincre tout le monde que nous devions arrêter d'utiliser SHA-1 pour des applications critiques pour la sécurité.
Aussi parce que c'était cool

Voir plus dans mon réponse séparée ci-dessous.

238
VonC

Selon Pro Git :

S'il vous arrive de valider un objet dont le hachage correspond à la même valeur SHA-1 qu'un objet précédent de votre référentiel, Git verra déjà l'objet précédent dans votre base de données Git et supposera qu'il a déjà été écrit. Si vous essayez de récupérer cet objet à un moment donné, vous obtiendrez toujours les données du premier objet.

Donc, cela n'échouerait pas, mais cela ne sauverait pas non plus votre nouvel objet.
Je ne sais pas à quoi cela ressemblerait sur la ligne de commande, mais ce serait certainement déroutant.

Un peu plus loin, cette même référence tente d’illustrer la probabilité d’une telle collision:

Voici un exemple pour vous donner une idée de ce qu’il faudrait faire pour obtenir une collision SHA-1. Si les 6,5 milliards d'humains sur Terre programmaient, et chaque seconde, chacun produisait un code équivalant à l'historique du noyau Linux dans son intégralité (1 million d'objets Git) et le plaçant dans un énorme référentiel Git, il faudrait 5 ans avant ce référentiel contenait suffisamment d'objets pour avoir une probabilité de 50% d'une collision d'objet SHA-1 unique. Il existe une probabilité plus élevée que chaque membre de votre équipe de programmation soit attaqué et tué par des loups lors d'incidents sans lien le même soir.

41
Mat

Pour ajouter à ma réponse précédente de 2012 , il y a maintenant (février 2017, cinq ans plus tard), un exemple de collision réelle entre SHA-1 et shattered.io , où vous pouvez créer deux fichiers PDF en conflit: c’est-à-dire obtenir une signature numérique SHA-1 sur le premier PDF fichier pouvant également être utilisé comme signature valide sur le deuxième fichier PDF.
Voir aussi " Au seuil de la mort depuis des années, la fonction SHA1 largement utilisée est maintenant morte ", et cette illustration .

Mise à jour du 26 février: Linus a confirmé les points suivants dans un message Google+ :

(1) Tout d’abord, le ciel ne tombe pas. Il y a une grande différence entre utiliser un hachage cryptographique pour des tâches telles que la signature de sécurité et en utiliser un pour générer un "identificateur de contenu" pour un système adressable par le contenu tel que git.

(2) Deuxièmement, la nature de cette attaque SHA1 particulière signifie qu’il est en fait assez facile d’atténuer les effets de l’attaque, et deux ensembles de correctifs ont déjà été publiés pour cette atténuation.

(3) Enfin, il existe en fait une transition relativement simple vers un autre hachage qui ne brisera pas le monde - ou même les anciens dépôts git.

En ce qui concerne cette transition, voir le Q1 2018 Git 2.16 ajoutant une structure représentant l'algorithme de hachage. La mise en œuvre de cette transition a commencé.

à partir de Git 2.19 (Q3 2018) , Git a choisi SHA-256 comme NewHash et est en train de l'intégrer. vers le code (SHA1 est toujours la valeur par défaut (T2 2019, Git 2.21), mais SHA2 sera son successeur)


Réponse originale (25 février) Mais:

Joey Hess essaie ces pdf en n repo Git et il a trouvé :

Cela inclut deux fichiers avec le même SHA et la même taille, qui obtiennent des blobs différents grâce à la façon dont git ajoute l'en-tête au contenu.

_joey@darkstar:~/tmp/supercollider>sha1sum  bad.pdf good.pdf 
d00bbe65d80f6d53d5c15da7c6b4f0a655c5a86a  bad.pdf
d00bbe65d80f6d53d5c15da7c6b4f0a655c5a86a  good.pdf
joey@darkstar:~/tmp/supercollider>git ls-tree HEAD
100644 blob ca44e9913faf08d625346205e228e2265dd12b65    bad.pdf
100644 blob 5f90b67523865ad5b1391cb4a1c010d541c816c1    good.pdf
_

Bien que l’ajout de données identiques à ces fichiers en conflit génère d’autres collisions, ce n’est pas le cas des données ajoutées.

Donc le principal vecteur d’attaque (falsification d’un commit) serait :

  • Générer un objet commit régulier;
  • utiliser tout l'objet commit + NUL comme préfixe choisi, et
  • utilisez l'attaque par collision à préfixe identique pour générer les objets bons/mauvais qui entrent en collision.
  • ... et cela est inutile car les bons et les mauvais objets commit pointent toujours vers le même arbre!

De plus, vous pouvez déjà détecter les attaques par collision cryptanalytique contre SHA-1 présentes dans chaque fichier avec cr-marcstevens/sha1collisiondetection

Ajouter un chèque similaire dans Git lui-même aurait un coût de calcul .

En changeant le hachage, commentaires Linux :

La taille du hachage et le choix de l'algorithme de hachage sont des problèmes indépendants.
Ce que vous feriez probablement, c’est de passer à un hachage 256 bits, de l’utiliser en interne et dans la base de données git native, puis, par défaut, uniquement afficher le hachage comme une chaîne hexagonale de 40 caractères (un peu comme si nous abrégions déjà les choses dans de nombreuses situations).
De cette manière, les outils autour de git ne voient même pas la modification, sauf s’ils sont passés dans un argument spécial "_--full-hash_" (ou "_--abbrev=64_" ou peu importe, la valeur par défaut étant 40. ).

Néanmoins, un plan de transition (de SHA1 à une autre fonction de hachage) serait toujours complexe , mais activement étudié.
ne campagne _convert-to-object_id_ est en cours :


Mise à jour du 20 mars: GitHub détaille une éventuelle attaque et sa protection :

Les noms SHA-1 peuvent être attribués en toute confiance via différents mécanismes. Par exemple, Git vous permet de signer de manière cryptographique un commit ou une balise. Cela ne fait que signer l'objet commit ou tag lui-même, qui pointe à son tour vers d'autres objets contenant les données du fichier en utilisant leurs noms SHA-1. Une collision dans ces objets pourrait produire une signature qui semble valide, mais qui pointe vers des données différentes de celles prévues par le signataire. Dans une telle attaque, le signataire ne voit qu'une moitié de la collision et la victime voit l'autre moitié.

Protection:

L'attaque récente utilise des techniques spéciales pour exploiter les faiblesses de l'algorithme SHA-1 qui détectent une collision en beaucoup moins de temps. Ces techniques laissent dans les octets un motif qui peut être détecté lors du calcul du SHA-1 de l'une ou l'autre moitié d'une paire en collision.

GitHub.com effectue maintenant cette détection pour chaque SHA-1 qu'il calcule et abandonne l'opération s'il est prouvé que l'objet est la moitié d'une paire en collision. Cela empêche les attaquants d’utiliser GitHub pour convaincre un projet d’accepter la moitié "innocente" de leur collision, ainsi que de les empêcher d’héberger la moitié malveillante.

Voir " sha1collisiondetection " par Marc Stevens


Encore une fois, avec Q1 2018 Git 2.16 ajoutant une structure représentant l'algorithme de hachage, la mise en œuvre d'une transition vers un nouveau hachage a commencé.
Comme mentionné ci-dessus, le nouveau hachage pris en charge sera SHA-256 .

22
VonC

Je pense que les cryptographes feraient la fête.

Citation de article de Wikipedia sur SHA-1 :

En février 2005, une attaque de Xiaoyun Wang, Yiqun Lisa Yin et Hongbo Yu a été annoncée. Les attaques peuvent trouver des collisions dans la version complète de SHA-1, nécessitant moins de 2 ^ 69 opérations. (Une recherche par force brute nécessiterait 2 ^ 80 opérations.)

6
Willem Hengeveld

Il existe plusieurs modèles d'attaque différents pour les hachages tels que SHA-1, mais celui qui est généralement discuté est la recherche par collision, y compris l'outil HashClash de Marc Stevens.

"Depuis 2012, l’attaque la plus efficace contre SHA-1 est considérée comme celle de Marc Stevens [34] avec un coût estimé à 2,77 M $ pour casser une valeur de hachage unique en louant de l’énergie CPU à des serveurs en nuage. "

Comme les gens l'ont souligné, vous pouvez forcer une collision de hachage avec git, mais cela ne remplacera pas les objets existants dans un autre référentiel. J'imagine que même git Push -f --no-thin ne remplacera pas les objets existants, mais pas à 100%.

Cela dit, si vous piratez un référentiel distant, vous pourrez alors transformer votre faux objet en ancien , en intégrant éventuellement du code piraté dans un projet open source sur github ou similaire. Si vous faisiez attention, alors vous pourriez peut-être introduire une version piratée que les nouveaux utilisateurs ont téléchargée.

Je pense cependant que les développeurs du projet pourraient faire beaucoup de choses, soit exposer, soit détruire accidentellement votre piratage de plusieurs millions de dollars. En particulier, c'est beaucoup d'argent par le passé si un développeur, que vous n'avez pas piraté, exécute le git Push --no-thin susmentionné après avoir modifié les fichiers affectés, parfois même sans le --no-thin dépendant.

5
Jeff Burdges