| 1 | * Tech-Talk - Hashes / IDs / Lebenszyklen / FlexoEntity
|
|---|
| 2 | :PROPERTIES:
|
|---|
| 3 | :CUSTOM_ID: hashes-ids-lifecycle
|
|---|
| 4 | :END:
|
|---|
| 5 |
|
|---|
| 6 | ** Was ist ein Hash?
|
|---|
| 7 |
|
|---|
| 8 | Ein Hash ist wie eine Art eindeutige Kennnummer oder ein "Fingerabdruck" für Daten, der
|
|---|
| 9 | sich aus eben diesen Daten über den Algorithmus einer Hash-Funktion errechnet.
|
|---|
| 10 |
|
|---|
| 11 | ** Was ist kein Hash?
|
|---|
| 12 |
|
|---|
| 13 | Die ISBN identifiziert ein Buch eindeutig. Sie ist nicht das Buch selbst, sondern eher ein
|
|---|
| 14 | Identifier (eine geordnete Nummer mit Prüfziffer). Sie ist aber kein Hash, weil sie nicht
|
|---|
| 15 | aus dem Inhalt des Buches berechnet wird. Ähnliches gilt für die IBAN
|
|---|
| 16 |
|
|---|
| 17 | |-------------------------+---------------------------+-------------------------------------|
|
|---|
| 18 | | Merkmal | Hash | Identifier (z. B. ISBN, IBAN, UUID) |
|
|---|
| 19 | |-------------------------+---------------------------+-------------------------------------|
|
|---|
| 20 | | Abhängig vom Inhalt | ja | nein |
|
|---|
| 21 | | Ziel | Integrität / Vergleich | Identität / Referenz |
|
|---|
| 22 | | Länge | fix z. B. 32 Hex-Zeichen | frei definierbar |
|
|---|
| 23 | | Zufällig oder berechnet | deterministisch berechnet | oft generiert oder vergeben |
|
|---|
| 24 | |-------------------------+---------------------------+-------------------------------------|
|
|---|
| 25 |
|
|---|
| 26 | ** Warum verwenden wir Hashes?
|
|---|
| 27 |
|
|---|
| 28 | Hashes sind super praktisch, weil sie uns eine Menge Zeit und Rechenleistung sparen.
|
|---|
| 29 | Anstatt zwei große Dateien oder ganze Bücher direkt miteinander zu vergleichen,
|
|---|
| 30 | vergleichen wir einfach ihre Hashes. Wenn die Hashes gleich sind, wissen wir,
|
|---|
| 31 | dass die Daten sehr wahrscheinlich identisch sind. Wenn sie unterschiedlich sind,
|
|---|
| 32 | hat sich etwas geändert. Das spart enorm viel Aufwand.
|
|---|
| 33 |
|
|---|
| 34 | ** Ein wichtiger Punkt
|
|---|
| 35 |
|
|---|
| 36 | Wichtig ist auch zu verstehen, dass Hashes sogenannte "One-Way-Funktionen" sind.
|
|---|
| 37 | Das heißt, man kann das Hashing nicht einfach umkehren, um das ursprüngliche Objekt wiederherzustellen.
|
|---|
| 38 | Ein Hash ist also eine Einbahnstraße: Er dient nur dazu, die Daten zu repräsentieren,
|
|---|
| 39 | aber man kann nicht vom Hash auf die originalen Daten zurückrechnen.
|
|---|
| 40 |
|
|---|
| 41 | ** Beispiele für den Einsatz von Hashes
|
|---|
| 42 |
|
|---|
| 43 | - **Passwort-Hashes**: Wenn du ein Passwort eingibst, wird oft nur der Hash gespeichert, nicht das
|
|---|
| 44 | eigentliche Passwort. So bleibt es sicher, selbst wenn jemand die Datenbank stiehlt.
|
|---|
| 45 |
|
|---|
| 46 | Beispiel aus der /etc/shadow-Datei
|
|---|
| 47 |
|
|---|
| 48 | root:$6$tsicHaoV3Q$YtAbiIvrHGXFtAJYz9tcEYWHXiGVQ40sJAgzPAbc57lIq9jH8eYjWXwctSW6YQnrMznRFcm6yXLnnY9mHhso20
|
|---|
| 49 | enno:$6$sdygzfEgx0$YpaZJMQdkZgxGPclphz6RojqNG.PSNEq1oIHRP4kvZRN2iuS5MQrxt0nCkrYQIcpDGyohrb1o0S/GkWrFriWL1
|
|---|
| 50 |
|
|---|
| 51 | - **Git-Commits**: In Versionskontrollsystemen wie Git werden Hashes benutzt, um jeden Schwung an
|
|---|
| 52 | Änderungen mit einer eindeutigen Kennung zu versehen
|
|---|
| 53 |
|
|---|
| 54 | - **URL-Shortener: hash("https://example.com") → 8a3f12
|
|---|
| 55 |
|
|---|
| 56 | ** FlexOID
|
|---|
| 57 |
|
|---|
| 58 | Die FlexOID ist eine Mischform. Sie selbst ist ein Identifier, der als Bestandteil aber einen Hash
|
|---|
| 59 | enthält, um möglichst eindeutig, aber nicht zu lang zu sein.
|
|---|
| 60 |
|
|---|
| 61 | AF-Q251022-70F759@001D
|
|---|
| 62 | │ │ │ │ │ │
|
|---|
| 63 | │ │ │ │ │ └── State (Draft, Approved, Signed, Published, Obsolete)
|
|---|
| 64 | │ │ │ │ └──── Version
|
|---|
| 65 | │ │ │ └────────── Hash (3 bytes, 6 Stellen)
|
|---|
| 66 | │ │ └────────────────── Date (YYMMDD)
|
|---|
| 67 | │ └──────────────────── Entity type (Question)
|
|---|
| 68 | └─────────────────────── Domain (e.g. Air force)
|
|---|
| 69 |
|
|---|
| 70 | In der ersten Variante der FlexOID habe ich mich mit einem 6-stelligen Hash zufrieden gegeben,
|
|---|
| 71 | weil das einfach lesbarer ist. Warum reicht das nicht?
|
|---|
| 72 |
|
|---|
| 73 | ** Das Geburtstags-Paradoxon
|
|---|
| 74 |
|
|---|
| 75 | Der obige 3-Byte Hash liefert mir etwas über 16 Millionen unterschiedlich Varianten.
|
|---|
| 76 | Das klingt viel. Ist es aber nicht. Wieviele Schüler müssen nacheinander die Klasse
|
|---|
| 77 | betreten bis sich zwei finden, die mit mehr als 50 Prozent Wahrscheinlichkeit am gleichen
|
|---|
| 78 | Tag Geburtstag (also ein identisches Merkmal) haben?
|
|---|
| 79 |
|
|---|
| 80 | ** Lösung
|
|---|
| 81 |
|
|---|
| 82 | Ab 23 Schülern beträgt die Wahrscheinlichkeit 50 % (näherungsweise Wurzel 365 Tagen)
|
|---|
| 83 |
|
|---|
| 84 | Bei 47 Schülern beträgt die Wahrscheinlichkeit bereits 95 Prozent
|
|---|
| 85 |
|
|---|
| 86 | Unsere 3 Bytes ergeben zwar 16.000.000 mögliche Varianten - im Gegenzug zu den 365 Tagen im Jahr
|
|---|
| 87 | aus dem Geburtstagsbeispiel - aber da die Wahrscheinlichkeit zur Kollision hier bei
|
|---|
| 88 | etwa Wurzel 16 Mio. liegt, bekommt man bereits bei 4000 Neuzugängen die ersten
|
|---|
| 89 | Übereinstimmungen im Hash.
|
|---|
| 90 |
|
|---|
| 91 | Der Hash ist also nicht gut genug, weil man sehr schnell und sehr häufig, diese
|
|---|
| 92 | Übereinstimmungen feststellen und behandeln müsste, wenn man weiterhin eindeutige
|
|---|
| 93 | Zuordnungen treffen will.
|
|---|
| 94 |
|
|---|
| 95 | Wenn man die Ausgabe der Hashfunktion auf 6 Bytes erweitert, kommen die ersten Kollisionen
|
|---|
| 96 | erst bei etwa 20 Mio erzeugten Hashes (Fingerabdrücken) und die kann man dann ohne
|
|---|
| 97 | Einbußen gesondert behandeln (Salzen), weil es so selten passiert.
|
|---|
| 98 |
|
|---|
| 99 | Übrigens, wenn man beim Menschen den Daumenabdruck nimmt und sich dabei auf 12 Merkmale
|
|---|
| 100 | beschränkt und sehr lockere Toleranzen ansetzt (was man in der Praxis nicht macht),
|
|---|
| 101 | hat man bereits nach 14000 Menschen eine Übereinstimmung. Bei 24 Merkmalen und sehr
|
|---|
| 102 | lockeren Toleranzen, hat man bei etwa nach 9 Mio. Menschen eine ungefähre Übereinstimmung.
|
|---|
| 103 | Da muss die Polizei schon sehr schlampig arbeiten, damit man fälschlicherweise beschuldigt wird.
|
|---|
| 104 | Die Zahlen in der Realität sind sogar noch deutlich höher.
|
|---|
| 105 |
|
|---|
| 106 | ** FlexoEntity
|
|---|
| 107 |
|
|---|
| 108 | Nun haben wir gesehen, dass wir mit der FlexOID (mit 6-Byte Hash) sehr viele unterschiedliche
|
|---|
| 109 | Dinge eindeutig bestimmen können. Da unsere FlexOID erstmal nur eine Zeichenfolge ist,
|
|---|
| 110 | brauchen wir etwas das damit umgehen kann und was dafür verantwortlich ist. Das ist die FlexoEntity.
|
|---|
| 111 |
|
|---|
| 112 | Sie beinhaltet zusätzlich eine Signatur und ein Origin-Feld, wo festgehalten wird, woher diese
|
|---|
| 113 | Entität stammt (beispielsweise aus einer Hashkollision oder einer Änderung an den weiteren Daten)
|
|---|
| 114 |
|
|---|
| 115 | Jede Klasse, die von FlexoEntity erbt, muss zwingend die Method "text_seed" implementieren, mit der
|
|---|
| 116 | der Algorithmus einer Hash-Funktion gefüttert wird, aus der dann der 6-Byte Hash herauspurzelt.
|
|---|
| 117 | Hashfunktionen sind z.B. MD5, SHA1, SHA256 oder wie von mir genutzt Blake2s.
|
|---|
| 118 | Die Mathematik dahinter ist recht aufwändig, aber wer sich mal einlesen möchte
|
|---|
| 119 |
|
|---|
| 120 | - Hashing in Smalltalk: Theory and Practice von Andres Valloud
|
|---|
| 121 |
|
|---|
| 122 | Damit die Hash-Funktion genug Eingabedaten pro Entity hat, muss man sich überlegen, welche Merkmale
|
|---|
| 123 | einer Entität man durch "text_seed" übermittelt.
|
|---|
| 124 |
|
|---|
| 125 | ** text_seed
|
|---|
| 126 |
|
|---|
| 127 | Man kann beliebige Klassen von FlexoEntity ableiten und erhält ohne Aufwand die Funktionalität
|
|---|
| 128 | zur eindeutigen Identifizierung und zur Lebenszyklus-Verwaltung
|
|---|
| 129 |
|
|---|
| 130 |
|
|---|
| 131 | Das ist das Beispiel einer OptionQuestion, also einer Testfrage, wo mögliche Antworten enthalten sind
|
|---|
| 132 |
|
|---|
| 133 | @property
|
|---|
| 134 | def text_seed(self) -> str:
|
|---|
| 135 | """Include answer options (and points) for deterministic ID generation."""
|
|---|
| 136 | base = super().text_seed
|
|---|
| 137 | if not self.options:
|
|---|
| 138 | return base
|
|---|
| 139 |
|
|---|
| 140 | joined = "|".join(
|
|---|
| 141 | f"{opt.text.strip()}:{opt.points}"
|
|---|
| 142 | for opt in sorted(self.options, key=lambda o: o.text.strip().lower())
|
|---|
| 143 | )
|
|---|
| 144 | return f"{base}::{joined}"
|
|---|
| 145 |
|
|---|
| 146 | ** Lebenszyklus
|
|---|
| 147 |
|
|---|
| 148 | Der Lebenszyklus einer Entität folgt dieser Reihenfolge und ist nicht umkehrbar
|
|---|
| 149 |
|
|---|
| 150 | - Entwurf (DRAFT)
|
|---|
| 151 | - Genehmigt (APPROVED)
|
|---|
| 152 | - Unterschrieben (SIGNED)
|
|---|
| 153 | - Veröffentlicht (PUBLISHED)
|
|---|
| 154 | - Veraltet (OBSOLETE)
|
|---|
| 155 |
|
|---|
| 156 | Eine Entität, die bereits die Stufe Veröffentlicht erreicht hat, kann nicht in die Stufe
|
|---|
| 157 | (nur) Unterschrieben zurückkehren. Daher ist auch die Lebenszyklusstufe in der ID kodiert
|
|---|
| 158 | (letztes Symbol der FlexOID)
|
|---|
| 159 |
|
|---|
| 160 | Beispiele für Entitäten:
|
|---|
| 161 |
|
|---|
| 162 | - Testfrage
|
|---|
| 163 | - Fragenkatalog
|
|---|
| 164 | - Einstufungstest
|
|---|
| 165 | - Zertifikat
|
|---|
| 166 |
|
|---|
| 167 | Ein veröffentlichter Einstufungstest kann nur Fragen beinhalten, die ihrerseits die Stufe Veröffentlicht
|
|---|
| 168 | erreicht haben. Ein Zertifikat kann nur ausgestellt werden, wenn der passende Einstufungstest die Stufe
|
|---|
| 169 | Veröffentlicht hat. Das origin-Feld des Zertifikats sollte sinnvollerweise die ID des Tests enthalten.
|
|---|
| 170 |
|
|---|
| 171 | ** Erhöhung der Versionsnummer oder neue ID
|
|---|
| 172 |
|
|---|
| 173 | Sobald - aus Gründen - eine neue ID vergeben werden muss, wird ggf. die Ursprungs-ID
|
|---|
| 174 | im Feld origin der neuen FlexoEntity gespeichert. So ist immer ein Suchen im Stammbaum möglich.
|
|---|
| 175 |
|
|---|
| 176 | Eine einfache Erhöhung der Versionsnummer (am Ende der ID) ist unter Umständen auch ausreichend,
|
|---|
| 177 | damit nicht zu häufig komplett neue IDs erzeugt werden müssen. Der Entscheidungsbaum dazu ist
|
|---|
| 178 | für das Projekt FlexoGrader recht umfangreich und soll hier nicht weiter besprochen werden.
|
|---|
| 179 | Da es aber im Hintergrund passiert und vom Endanwender nicht bemerkt wird, behindert der
|
|---|
| 180 | Mechanismus die Nutzung des FlexoGrader (TM) nicht.
|
|---|
| 181 |
|
|---|
| 182 | ** Lizenz
|
|---|
| 183 |
|
|---|
| 184 | Da ich die FlexoEntity-Bibliothek in meiner Freizeit entworfen und programmiert habe,
|
|---|
| 185 | bin ich alleiniger Urheber. Als Lizenz habe ich MIT gewählt, somit kann - unter Nennung
|
|---|
| 186 | des Urhebers - jeder damit machen, was er möchte
|
|---|
| 187 |
|
|---|
| 188 | Der FlexoGrader ist ein Bundeswehrprojekt, welches ich im Dienst programmiere und somit
|
|---|
| 189 | gehen die Nutzungsrechte uneingeschränkt auf den Dienstherren über.
|
|---|
| 190 |
|
|---|
| 191 | Das betrifft auch etwaige Folgeprojekte wie FlexoVault und FlexoDrill
|
|---|