In der letzten Episode des Risikozone-Podcasts habe ich ganz kurz das Thema der verkürzten Hashwerte und auftretenden Kollisionen angesprochen. Jetzt gibt es ein Proof of Concept zu der Thematik.
Worum es geht
Ein kurzer Refresher, worum es in der Thematik überhaupt geht: Als Git entwickelt wurde, musste ein Weg gefunden werden, wie man die Referenzen auf Commits, die Dateien und die Dateibäume realisiert. In vielen Systemen wie z. B. Issue-Trackern werden aufsteigende Indizes verwendet. In einem verteilten System wie Git ist das aus verschiedenen Gründen der Synchronisation nicht so einfach möglich, da sonst Kollisionen, also gleiche IDs für unterschiedliche Objekte entstehen könnten. Eine Alternative wäre UUIDs, da die IDs einen randomisierten Anteil haben und die Wahrscheinlichkeit für Kollisionen gesenkt wird.
Noch besser als UUIDs ist allerdings Content-adressable Storage, bei dem Inhalte einzig durch ihren Inhalt adressiert werden. Das ist so, als würde man den gesamten Inhalt der Datei nochmal in den Dateinamen schreiben. Der Clou dabei ist jedoch, dass der gesamten Dateiinhalt gar nicht in den Dateinamen geschrieben werden muss, um die Datei durch ihren Inhalt identifizierbar zu machen.
Mit kryptographischen Hashfunktionen wie SHA-1 oder SHA-256 existiert ein Mittel, um einen beliebig langen Dateiinhalt zu einem immer gleich langen Wert, dem Hash, umzuwandeln. Dabei sind kryptographische Hashfunktionen so konstruiert, dass die Wahrscheinlichkeit für Kollisionen durch verschiedene Mechanismen stark minimiert wird. Ein SHA-1-Hash für den Dateiinhalt "Hallo Welt" wäre z. B. 28cbbc72d6a52617a7abbfff6756d04bbad0106a
. Ein netter Nebeneffekt ist, dass Dateien mit gleichem Inhalt im Content-adressable Storage auch nur einmal abgespeichert werden, wodurch sogar Deduplikation ermöglicht wird.
Git nutzt dieses Verfahren, um die eingechekten Dateien abzuspeichern. Diese Dateien werden dann in Trees zusammengebunden und in Commits mit den jeweiligen Vorgänger-Commits (parents) in einem sog. Merkle-Tree verheiratet.
Commits werden somit auch durch einen SHA-1-Hash identifiziert, der im hexadezimalen Format 40 Zeichen lang ist.
Das Problem
Das Problem bei dem Verfahren liegt jetzt darin, dass dieser Hash üblicherweise noch weiter abgekürzt wird, um ihn benutzerfreundlicher in Oberflächen oder E-Mails darzustellen. Damit wird natürlich die Wahrscheinlichkeit für Kollisionen erhöht.
Habe ich einen Hash 28cbbc72d6a52617a7abbfff6756d04bbad0106a
und nutze nur 28cbbc
zur Referenz, reicht das in den meisten kleinen Repositories aus, um einen Commit eindeutig zu referenzieren. In großen Repositories mit vielen Dateien und Commits kann es auf einmal passieren, dass ein weiterer Commit 28cbbcc00aa8ef4e80596c16ecfdb4bc92656cd3
auftaucht, sodass 28cbbc
nicht mehr eindeutig einen Commit beschreibt.
Um das Risiko zu verringern, sollte die Mindestanzahl der Zeichen für einen abgekürzten Commit erhöht werden.
Das Kernel-Repository
Genau darum geht es in der aktuellen Diskussion. Aktuell nutzen die Linux-Entwickler zur Referenz von Commits in ihren E-Mails 12 Zeichen lange Hashes. Die Diskussion dreht sich um die Frage, ob die Zahl weiter erhöht werden sollte. Linus Torvalds ist bisher dagegen, weil er das Risiko für Kollisionen gering sieht und er die Position vertritt, dass ein Commit immer mit dem Commit Message Title angegeben werden sollte, was ungewollte Kollisionen ausreichend verhindere.
Gestern veröffentlichte Kees Cook einen Blogpost, indem er eine Commit-Kollision mit dem Werkzeug lucky-commit bewusst herbeiführte, um darauf aufmerksam zu machen, dass die Git- und Kernel-Entwicklungstools mit solchen Situationen klarkommen sollten. Es ist unwahrscheinlich, dass solche Kollisionen bei 12 Zeichen versehentlich entstehen, aber ein Angreifer könnte dies ausnutzen.
Dies sollte ein Apell an alle Entwickler sein, deren Tooling auf abgekürzte Commit-Hashes setzt. Schauen wir mal, wie sich das weiterentwickelt.
Ein Kommentar zu SHA-1
Abschließend ein Kommentar noch zu SHA-1. Wie viele von euch wissen, ist SHA-1 selbst nicht mehr vertretbar kollisionssicher. Das bedeutet, es kann passieren, dass auf einmal zwei Dateiinhalte sich doch den gleichen Hash teilen könnten, wenn ein Angreifer es drauf anlegt.
Da dies natürlich Git massiv stören könnte, gibt es schon Bestrebungen, das Verfahren auf SHA-2 zu aktualisieren, wodurch sich die Hashlänge auch vergrößert. Das ist aber gar nicht so einfach, da SHA-1 an vielen Stellen in die Struktur eines Git-Repos hartkodiert wurde.
Hier geht es aber nicht um das unsichere SHA-1. Durch die Abkürzung des Hashes von 40 auf 12 Zeichen wird die Kollisionssicherheit bewusst und massiv zugunsten der Benutzerfreundlichkeit geschwächt. Und das erfordert immer eine regelmäßige Evaluation, welches Niveau noch vertretbar ist.