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