← Startseite
🔐

Vorlesung 8

Sicherheit I — Hashing & Passwörter
Wie speichert man Passwörter, ohne sie zu speichern?

Fortgeschrittene Algorithmen und Programmierung
Prof. Dr. Alexandra Mikityuk
HTW Berlin

Hashing Passwörter Salt

Lernziele

  • Verstehen, was eine Hash-Funktion ist — und warum sie für Passwörter unverzichtbar ist
  • Den Login-Ablauf mit Hashing nachvollziehen können
  • Wissen, was Salt macht — und warum jedes Passwort einen eigenen braucht
  • Erkennen, welche Hash-Algorithmen man für Passwörter nehmen soll — und welche nicht

Stell dir vor: deine Lieblings-Webseite wird gehackt

Die Hacker laden die Datenbank mit allen Nutzern herunter. Was finden sie?

😱 Die schlechte Version

username | passwort
---------|---------
anna     | sommer2024
bob      | passwort123
carla    | mein_kater_max
...

Die Hacker haben jetzt alle Passwörter im Klartext. Sie probieren dieselben Logins auf Banking, E-Mail, Amazon — viele Leute nutzen ja dasselbe Passwort.

Die gute Version

username | hash
---------|---------------
anna     | 5e88489...d80f9
bob      | 8d969ef...e7140
carla    | 2cf24db...e6a8b
...

Die Hacker sehen nur Hashes — Zahlensalat. Aus diesen können sie die echten Passwörter nicht zurückrechnen.

Heute lernen wir, wie man die rechte Version baut.

Was ist ein Hash?

Ein Fingerabdruck für Daten. Du steckst Text rein → bekommst eine feste Zahl raus.

"passwort123"
SHA-256
ef92b778...c9c

🔁 Gleicher Input

Immer derselbe Hash. "hallo" ergibt heute und morgen denselben Wert.

Schnell vorwärts

Hash ausrechnen: Mikrosekunden. Ein Computer macht Millionen pro Sekunde.

🚫 Unmöglich rückwärts

Hash → Original: geht nicht. Hash ist eine Einbahnstraße.

Ein Buchstabe anders → komplett anderer Hash

Das nennt man Avalanche-Effekt — eine Mini-Änderung wird zur Lawine.

SHA-256("Hallo Welt")  → d6e3...8f2b
SHA-256("Hallo welt")  → 9c4a...e1d7     // nur "W" zu "w"
SHA-256("Hallo Welt!") → 7b1c...3a05     // nur ein "!" angefügt
Warum gut? Niemand kann aus einem Hash raten, was das Original ungefähr war. Auch nicht „die ersten 3 Buchstaben sind gleich, also war's wohl ähnlich" — das funktioniert nicht.
Probier's selbst: auf emn178.github.io/online-tools/sha256.html kannst du SHA-256 live ausprobieren. Ändere einen Buchstaben — du siehst den Avalanche.

🤔 Halt — wie würden WIR das in C lösen?

Wir wissen jetzt: aus jedem Text kommt am Ende eine Zahl raus. Aber wie würdet ihr selbst so eine Funktion in C schreiben?

💭 Die Aufgabe:

Wir haben einen String wie "Hallo". Wir wollen eine Zahl daraus machen.

Wie würdet ihr das versuchen?

🤔 Denkfrage 1

Wie kommt man von einem Buchstaben überhaupt zu einer Zahl? Können wir mit 'H' rechnen?

💭 Denkfrage 2

Und wenn wir die Zahlen aller Buchstaben hätten — wie würden wir sie zu einer Hash-Zahl kombinieren?

Kurze C-Auffrischung: char ist eigentlich eine Zahl

Bevor wir Hashes selbst bauen, ein C-Trick, den ihr vielleicht bisher übersehen habt.

In C ist char technisch eine kleine Ganzzahl (1 Byte = 0-127 für ASCII). Ein „Buchstabe" ist nichts anderes als eine Zahl in Verkleidung.

📋 ASCII-Tabelle (Auszug)

'A' = 65     'a' = 97
'B' = 66     'b' = 98
'0' = 48     ' ' = 32
'9' = 57     '\n' = 10

🧪 Selber ausprobieren

char c = 'a';

printf("%d\n", c);
// → 97 (die Zahl)

printf("%c\n", c);
// → a  (als Zeichen)
Konsequenz: wenn ihr schreibt str[i] + 1 oder summe += str[i], rechnet C einfach mit dem ASCII-Wert. Das nutzen wir auf der nächsten Slide für unsere erste (sehr schlechte) Hash-Funktion.

Eine eigene Hash-Funktion bauen? Bloß nicht.

Sieht in C harmlos aus — ist aber katastrophal als Krypto-Hash. Wir summieren die ASCII-Werte (s. vorige Slide) zu einer einzigen Zahl:

// "Schlechter" Hash: einfach die ASCII-Werte addieren
unsigned int bad_hash(char str[]) {
    unsigned int hash = 0;
    for (int i = 0; str[i] != '\0'; i++) {
        hash += str[i];        // ASCII-Wert addieren
    }
    return hash;
}

bad_hash("abc");   // → 294 (= 97 + 98 + 99)
bad_hash("bac");   // → 294   ✗ KOLLISION!
bad_hash("cab");   // → 294   ✗ noch eine

Was hier schief läuft

  • Kollisionen sind trivial zu finden
  • Kein Avalanche-Effekt
  • Reverse Engineering trivial
  • Output passt nicht in 256 Bit

Goldene Regel

Niemals selbst eine kryptografische Hash-Funktion schreiben. Eine gute Hash zu bauen, hat 30 Jahre Forschung gekostet. Eure Aufgabe: eine bestehende Library richtig aufrufen.

🤔 Eine Frage, die euch verrückt machen sollte

Bevor wir SHA-256 in C aufrufen — eine Sache verstehen wir noch nicht.

💭 Das Mysterium:

echte_hash("Hi") → 32 Bytes

echte_hash(5 MB Buch) → 32 Bytes

Beides ergibt genau 32 Bytes. Wie?

Wie kann ein 5-MB-Input auf dieselbe Größe wie ein 2-Zeichen-Input eingedampft werden — ohne dass die Hashes gleich werden?

💭 Denkfrage — warum würde bad_hash überhaupt überlaufen?

unsigned int hat in C nur Platz für eine begrenzte Zahl — typisch 4 294 967 295 (≈ 4,3 Mrd., also 2³² − 1). Mehr passt nicht rein.

Wir summieren aber immer weiter ASCII-Werte auf. Irgendwann ist der Platz alle → die Zahl wickelt sich um zurück auf 0 und zählt wieder hoch.

// Bei langem Text:
hash = 4 294 967 290
hash += 10;
// → 4 (statt 4 294 967 300)
// alles davor: verloren!

Folge: bei einem 5-MB-Buch summieren wir Millionen Werte auf, überlaufen mehrfach, und am Ende ist die Zahl praktisch zufällig — aber schlecht zufällig. Eine richtige Hash-Funktion macht das nicht. Was kann sie anders machen?

Wie funktioniert das eigentlich? — die Idee

Wie schafft SHA-256 es, aus einem 5-Zeichen-Wort UND aus einem 5-MB-Buch jeweils genau 256 Bit rauszuholen?

🪣 Das Geheimnis: ein „State" (ein Eimer fester Größe)

Wir haben einen kleinen Speicher (den State) — bei SHA-256 sind das 256 Bit. Der State wächst nicht, egal wie lang der Input ist.

'H'
State mixen
'a'
State mixen
'l'
final State = HASH

1️⃣ State starten

Mit einem festen Anfangswert. Bei SHA-256 sind das 256 Bit, vorgegebene Zahlen.

2️⃣ Pro Input-Stück mischen

Wild kombinieren: addieren, mischen, durcheinanderwerfen. Aus state + char wird neuer state.

3️⃣ Final = Hash

Wenn alle Input-Zeichen durch sind, ist der State unser Hash.

Genial: der State hat eine feste Größe. Bei 1 Zeichen Input → State wird 1× mal gemischt. Bei 1 Mio. Zeichen → 1 Mio. Mischungen. Aber der State bleibt 256 Bit. Daher: Input beliebig groß, Output immer 256 Bit.

Selbst rechnen — unsere Mini-Hash auf Papier

Vereinfachte Hash-Funktion, die ihr auf Papier mitrechnen könnt. Idee wie SHA-256, nur viel kleiner.

💡 Auffrischung — was ist „mod"? a mod b = der Rest beim Teilen. In C: der %-Operator. Beispiel: 2337 mod 1000 = 337 → behält nur die letzten 3 Ziffern. Genau so bleibt unser State immer 3-stellig.

🎯 Der Algorithmus

// State: eine Zahl von 0 bis 999
state = 0

// Für jeden Buchstaben:
state = (state × 31 + ASCII(c)) mod 1000

Warum genau × 31 und mod 1000? → schauen wir auf der nächsten Slide an.

🧮 Beispiel: Hash("Hi")

// 'H' hat ASCII 72
state = (0 × 31 + 72) mod 1000
state = 72

// 'i' hat ASCII 105
state = (72 × 31 + 105) mod 1000
state = 2337 mod 1000
state = 337

Hash("Hi") = 337
Probiert es selbst: rechnet Hash("OK") mit Bleistift. Tipp: 'O' = 79, 'K' = 75.

Mini-Hash dekonstruiert — warum genau so?

Unsere Formel: state = (state × 31 + ASCII(c)) mod 1000. Jeder Teil hat eine konkrete Aufgabe:

× 31 — der Mixer

Was tut es? Multipliziert den alten State, bevor der neue Buchstabe drauf addiert wird. Das wirbelt den State durcheinander → die Reihenfolge der Buchstaben zählt (ohne den × würden "abc" und "cab" denselben Hash haben).

Warum eine Primzahl? Sie teilt nichts glatt → kein Muster beim Modulo. Bei × 10 hätten alle Hashes ähnliche Endziffern.

Warum gerade 31? Eigentlich beliebig — 7, 13, 31, 37 würden alle ähnlich funktionieren. Wir nehmen 31, weil Javas String.hashCode() es seit 30 Jahren tut: groß genug zum Mixen, klein genug, dass nicht sofort alles überläuft.

+ ASCII(c) — der Input

Hier kommt der neue Buchstabe in den State rein. Jeder Buchstabe verändert das Ergebnis — sonst wäre es ja keine Hash-Funktion vom Inhalt.

Die ASCII-Werte kennt ihr von der vorigen Slide: 'H' = 72, 'i' = 105.

mod 1000 — der Eimer

Schneidet alles über 999 ab → State bleibt 3-stellig, egal wie lang der Input ist.

Genau diese feste Größe macht SHA-256 in groß — dort: 256 Bit statt 3 Ziffern.

⚠️ Wichtige Einordnung: unsere × 31-Formel ist eine Lehrbuch-Vereinfachung — kein Krypto-Standard. Echte Hash-Funktionen wie SHA-256 nutzen keine simple Multiplikation, sondern komplexe Bit-Operationen (Rotation, XOR, viele Konstanten). Das Prinzip „State + pro Buchstabe mischen" ist aber dasselbe — nur die Misch-Mathematik ist bei SHA-256 viel ausgefuchster.

Avalanche live — kleine Änderung, anderer Hash

Mit derselben Mini-Hash rechnen wir drei ähnliche Wörter. Schaut, was passiert.

🅰️ Hash("HALLO")

H (72)  → 72
A (65)  → 297
L (76)  → 283
L (76)  → 849
O (79)  → 398

🅱️ Hash("HALLI")

H (72)  → 72
A (65)  → 297
L (76)  → 283
L (76)  → 849
I (73)  → 392   // nur 6 anders

🅲 Hash("BALLO")

B (66)  → 66
A (65)  → 111
L (76)  → 517
L (76)  → 103
O (79)  → 272   // 126 anders!
Was wir sehen:
  • Letzten Buchstaben ändern (O → I): nur kleine Änderung im Hash (398 → 392).
  • Ersten Buchstaben ändern (H → B): riesige Änderung (398 → 272).
Unsere Mini-Hash hat schwachen Avalanche-Effekt, vor allem am Ende. Aber das Prinzip ist da.
SHA-256 macht das gleiche — nur viel besser:
  • State ist 256 Bit (statt 3 Ziffern) → 32 mal mehr Möglichkeiten
  • Pro Block 64 Misch-Runden statt 1 → starker Avalanche überall, auch am Ende
  • Output: 32 Bytes (deshalb das unsigned char hash[32] auf der nächsten Slide)

SHA-256 in C — die richtige Library nutzen

Mit openssl/sha.h ist es fünf Zeilen. Kompilieren mit gcc datei.c -lcrypto.

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

int main(void) {
    char passwort[] = "sommer2024";
    unsigned char hash[32];        // SHA-256 liefert 32 Bytes

    SHA256((unsigned char *)passwort, strlen(passwort), hash);

    // Hash als Hex-Zahlen ausgeben (jedes Byte → 2 Zeichen)
    for (int i = 0; i < 32; i++) {
        printf("%02x", hash[i]);
    }
    printf("\n");
    return 0;
}

// Ausgabe: 5e88489dd80f9bc99b5e80f5b8df...
💡 Drei C-Sachen, die hier neu sind:
  • unsigned char = ein einzelnes Byte (Werte 0-255). Hash ist Rohdaten, keine lesbaren Buchstaben.
  • %02x bei printf = Zahl als Hex mit führender Null. 505, 255ff.
  • Cast (unsigned char *) = „OpenSSL erwartet eine Byte-Folge, kein char-Array — sind aber technisch dasselbe, daher der Cast."
Kompilieren: gcc datei.c -lcrypto · das -lcrypto verlinkt die OpenSSL-Library. Ohne das findet der Linker SHA256 nicht.
Aber Achtung: dieser Code ist nur eine Demo. Für echte Passwörter fehlt der Salt, und SHA-256 ist zu schnell — Brute Force machbar. Reparieren wir gleich.

Warum hashen wir Passwörter überhaupt?

Die Webseite muss prüfen können, ob du das richtige Passwort eingibst — ohne dein echtes Passwort gespeichert zu haben.

Der Trick:

  1. Beim Registrieren: dein Passwort wird gehasht, der Hash wird gespeichert.
  2. Beim Login: dein eingegebenes Passwort wird auch gehasht. Stimmen die Hashes überein? → Login erfolgreich.
  3. Die Webseite hat nie dein echtes Passwort gespeichert. Auch sie kennt es nicht.
Konsequenz: wenn ein Admin sagt „ich kann dir dein Passwort per E-Mail schicken" — das System ist schlecht gebaut. Eine vernünftige Seite kennt dein Passwort selbst nicht.

Der Login-Ablauf — visuell

📥 Registrierung

User tippt "sommer2024"
SHA-256
5e88489d80f9...
in DB speichern
⋮ später kommt der User wieder zurück ⋮

🔑 Login

User tippt "sommer2024"
SHA-256
5e88489d80f9...
Hash aus DB lesen
vs.
5e88489d80f9...
stimmen überein? Login!
Beachte: die DB speichert nur den Hash. Wenn jemand sie klaut, hat er nichts Brauchbares.

Login-Verifikation in C — die naive Version

So sieht die einfachste Variante aus — funktioniert, aber wir werden sie gleich kritisieren.

// --- vereinfacht: C-Details ausgelassen, Logik im Fokus ---

// Vergleicht eingegebenes Passwort mit dem gespeicherten Hash
verify_naiv(eingabe, gespeicherter_hash) {

    // 1. Eingabe hashen
    hash_neu = SHA256(eingabe);

    // 2. Mit gespeichertem Hash vergleichen
    return hash_neu == gespeicherter_hash;
}
Zwei Probleme verstecken sich hier — wir reparieren beide:
  1. Kein Salt → anfällig für Rainbow Tables (gleich)
  2. SHA-256 ist zu schnell → Brute Force möglich (kommt mit bcrypt)

⚠️ Aber — Hacker sind auch nicht doof

Sie haben eine Idee: vorberechnete Tabellen — Hashes von Millionen Standard-Passwörtern.

🌈 Die Rainbow Table (vereinfacht):

PasswortSHA-256
1234568d969ef...e7140
passworte2c569be...da1d
sommer20245e88489...d80f9
… 1 Milliarde weitere …

Wenn der Hacker den Hash 5e88489...d80f9 in der DB findet → er schaut nur nach. Passwort ist „sommer2024".

Konsequenz: wenn viele User dasselbe Passwort haben, haben sie auch denselben Hash. Eine Tabelle reicht für die gesamte Datenbank.

🤔 Wie würdet IHR Rainbow Tables aushebeln?

Das Problem ist klar: gleiche Passwörter → gleiche Hashes → ein Hacker baut eine Tabelle und schaut nach. Wie löst man das?

💭 Die Frage:

Anna und Bob nutzen beide das Passwort "sommer2024". In der DB stehen ihre Hashes nebeneinander — und beide sind identisch.

Wie könnten wir dafür sorgen, dass die zwei Hashes verschieden sind — obwohl das Passwort identisch ist?

🤔 Denkfrage 1

Was müsste sich beim Hashen zwischen Anna und Bob unterscheiden, damit unterschiedliche Hashes rauskommen?

💭 Denkfrage 2

Und wenn wir was hinzufügen — woher nehmen wir das? Muss es geheim sein? Muss es bei jedem User gleich sein?

Die Lösung: Salt (Zufalls-Würze)

Für jeden User wird eine zufällige Zahl generiert (das Salt) — und vor dem Hashen ans Passwort angehängt.

// Anna registriert sich mit "sommer2024"
salt_anna = "X9q!2pZ"                          // zufällig generiert
hash_anna = SHA-256("sommer2024" + "X9q!2pZ")  // → a3f5...c2d1

// Bob registriert sich auch mit "sommer2024"
salt_bob = "L#8mNqW"                           // anderes Salt!
hash_bob = SHA-256("sommer2024" + "L#8mNqW")    // → 9b41...f78e

// Gleiches Passwort → KOMPLETT VERSCHIEDENE Hashes!
Folge: die Rainbow Table funktioniert nicht mehr. Der Hacker müsste für jedes Salt eine eigene Tabelle bauen — bei Millionen Salts unmöglich.
Wo wird das Salt gespeichert? Im Klartext in der DB neben dem Hash. Es muss nicht geheim sein — es muss nur einmalig pro User sein.

Salt generieren in C — die richtige Quelle

Salt muss kryptografisch zufällig sein. Das ist wichtig — nicht jede Zufalls-Quelle taugt.

Falsch — rand()

srand(time(NULL));
int salt = rand();    // vorhersagbar!

Ein Angreifer kennt den Sekunden-Zeitpunkt der Registrierung → kann das Salt nachrechnen.

Richtig — RAND_bytes

#include <openssl/rand.h>

unsigned char salt[16];
RAND_bytes(salt, 16);
// nutzt intern /dev/urandom
💡 Warum 16 Bytes? Salt soll einzigartig sein, nicht geheim. 16 Bytes = 128 Bit = 2¹²⁸ Möglichkeiten. Selbst bei Milliarden User-Konten ist eine Salt-Kollision praktisch ausgeschlossen.
Merke: für alles Sicherheits-Relevante (Salts, Tokens, Session-IDs) nie rand(). Immer eine echte Zufalls-Quelle: OpenSSL (RAND_bytes), libsodium (randombytes_buf), oder direkt /dev/urandom.

So sieht die DB jetzt aus

usernamesalthash
annaX9q!2pZa3f5...c2d1
bobL#8mNqW9b41...f78e
carlak7Vp@3H7c2e...4b8a

🔑 Login mit Salt:

  1. User Anna gibt „sommer2024" ein.
  2. System holt Annas Salt X9q!2pZ aus der DB.
  3. System berechnet SHA-256("sommer2024" + "X9q!2pZ").
  4. Vergleicht mit dem gespeicherten Hash. Stimmt überein → Login.
Genial einfach: drei Zeilen Code-Änderung, riesige Sicherheits-Verbesserung.

Welchen Hash-Algorithmus für Passwörter?

Es gibt viele — aber für Passwörter sind die meisten nicht die richtige Wahl.

AlgorithmusFür Passwörter?Warum?
MD5❌ NEINkaputt — Kollisionen bekannt, sekundenschnell zu knacken
SHA-1❌ NEINseit 2017 als unsicher eingestuft
SHA-256⚠️ Geht, aber…zu schnell! Moderne GPUs probieren Milliarden Hashes/Sek
bcrypt✅ JAabsichtlich langsam — schützt vor Brute Force
Argon2✅ JA (modern)aktueller Sieger, gewinnt seit 2015 alle Wettbewerbe
Spalte 3 zeigt einen Trend: die schnellen Hashes sind schlecht für Passwörter — die langsamen sind gut. Das klingt verkehrt. Auf der nächsten Slide schauen wir, warum genau das genial ist.

Aha — für Passwörter wollen wir langsam

Klingt verkehrt. Der Trick liegt in der Asymmetrie zwischen Login-User und Angreifer.

😊 Der Login-User

Loggt sich einmal ein. Wartet 250 ms — merkt er gar nicht.

250 ms = ein viertel Wimpernschlag.

😈 Der Angreifer

Probiert Milliarden Passwörter durch. Wenn jeder Versuch 250 ms dauert, dauert das Knacken plötzlich länger als ein Menschenleben.

Die Rechnung: Hash 100 ms statt 1 µs → Angreifer macht statt 1 Mrd. nur noch 10 Versuche/Sek. Genau dieselbe Verlangsamung, die den ehrlichen User nicht stört, macht Brute Force unmöglich. Konkrete Zahlen kommen auf der nächsten Slide mit bcrypt.

bcrypt — der Spezialist für Passwörter

In der Tabelle stand „absichtlich langsam — schützt vor Brute Force". Schauen wir mal, wer das gebaut hat und wie er das macht.

📜 Was ist bcrypt?

Eine Hash-Funktion, die nur für Passwörter entwickelt wurde. 1999 von Niels Provos & David Mazières für OpenBSD erfunden.

Genau wie SHA-256 nimmt sie Text + Salt rein und gibt einen Hash raus. Der Unterschied: sie tut das absichtlich langsam.

🐢 Der Trick: Cost Factor

Ihr legt vorher fest, wie langsam sie sein soll — über den Parameter Cost:

  • Cost 10 → 2¹⁰ = 1 024 Runden → ~ 60 ms
  • Cost 12 → 2¹² = 4 096 Runden → ~ 250 ms
  • Cost 14 → 2¹⁴ = 16 384 Runden → ~ 1 s
Warum „absichtlich langsam" genial ist: für den ehrlichen Login spürt ihr 250 ms nicht. Für einen Angreifer, der Milliarden Passwörter durchprobieren will, wird's katastrophal:
HashBrute-Force-Versuche/Sek (GPU)8-Zeichen-PW knacken
SHA-256~ 10 Mrd.~ 16 Stunden
bcrypt (Cost 12)~ 40~ 450 000 Jahre
Mitwachsend mit Moore's Law: wenn Hardware in 5 Jahren doppelt so schnell ist, erhöht ihr Cost von 12 auf 13. Hash dauert wieder 250 ms — Angreifer bleibt da, wo er war. bcrypt skaliert mit.

bcrypt in C — wie ihr's wirklich macht

Auf Linux liefert crypt.h (aus libxcrypt) bcrypt direkt mit. Kein selbst-bauen, kein Pfusch.

#include <crypt.h>
#include <string.h>

// --- vereinfacht: Typ-Angaben weggelassen, alles als „String" denken ---

// Registrierung — Passwort hashen
setting = "$2b$12$abcdefghijklmnopqrstuv";
//          │   │  └─ Salt (22 Zeichen base64)
//          │   └──── Cost Factor (12 = 2^12 Runden)
//          └──────── Algorithmus: bcrypt 2b

hash = crypt("sommer2024", setting);
// → "$2b$12$abcdefghijklmnopqrstuv9.l4f8...kCe2"
// in der DB speichern (komplett, inkl. Salt und Cost)

// Login — Passwort prüfen
check = crypt("sommer2024", stored_hash);
if (strcmp(check, stored_hash) == 0) {
    printf("Login OK\n");
}
💡 Was sagt der String "$2b$12$..."?
  • $2b$ = welcher Algorithmus (hier bcrypt Version 2b)
  • $12$ = Cost Factor (2¹² = 4 096 interne Runden)
  • danach das Salt + Hash, alles in einem String
Praktisch: ihr speichert einfach den kompletten Rückgabe-String in der DB. Beim Login könnt ihr Algorithmus, Cost und Salt daraus zurücklesen.
Cost Factor 12 in Zahlen: ~ 250 ms pro Hash. Für den Login-User unmerklich. Für einen Brute-Force-Angreifer: 10 Versuche/Sek statt 1 Mrd./Sek → 100 Mio.× langsamer. Aktuell 2026 empfohlen: 12-14, wenn Hardware schneller wird, einfach erhöhen.
Kompilieren: gcc datei.c -lcrypt-lcrypt verlinkt libxcrypt. Ohne das findet der Linker crypt nicht.

Hashes — nicht nur für Passwörter

Wo ihr noch überall auf Hashes treffen werdet:

📦 Datei-Integrität

Beim Download zeigt eine Webseite oft den SHA-256-Hash an. Du rechnest ihn lokal nach → wenn er stimmt, kam die Datei unverändert an.

⛓️ Blockchain

Jeder Bitcoin-Block hat den Hash des vorherigen drin. So entsteht die unveränderliche Kette (siehe VS-Vorlesungen 4/5).

🔍 Schnelle Suche

Hash-Tabellen (kommen später) — der Schlüssel zu O(1)-Lookups in vielen Datenstrukturen.

Merke: Hash = Fingerabdruck für Daten. Wo immer ihr einen kompakten, eindeutigen Identifier für was Großes braucht — ist Hashing die Antwort.

Was passiert eigentlich bei einem Datenleck?

Echte Beispiele — und was die Konsequenzen waren.

📉 Adobe 2013

153 Millionen Accounts geleakt. Passwörter mit 3DES verschlüsselt (nicht gehasht!) — und der Schlüssel war auch in der Leak-Datei. Folge: alle Passwörter im Klartext lesbar.

📉 LinkedIn 2012

117 Millionen Accounts. Passwörter mit SHA-1 ohne Salt. Innerhalb von Tagen waren 90% der Hashes geknackt — wegen Rainbow Tables.

Lektion: beide Hacks hätten mit bcrypt + Salt kaum Wirkung gehabt. Die Datenbanken wären gestohlen worden, aber die Passwörter wären unknackbar geblieben. Das ist der Unterschied, den wir heute gelernt haben.

Was kann ich als User tun?

Du kannst nicht steuern, wie die Webseite deine Passwörter speichert. Aber du kannst deinen Schaden begrenzen.

🔑 Verschiedene Passwörter

Nie dasselbe Passwort für mehrere Seiten. Wenn eine geleakt wird, sind sonst alle anderen Logins auch in Gefahr.

🗝️ Passwort-Manager

Bitwarden, 1Password, KeePass. Generieren lange Zufalls-Passwörter — du musst dir nur eines merken.

📱 2FA aktivieren

Zweiter Faktor (TOTP-App, Hardware-Key). Selbst wenn das Passwort weg ist, kommt niemand rein ohne Handy.

Check: auf haveibeenpwned.com könnt ihr nachsehen, ob eure E-Mail in bekannten Leaks aufgetaucht ist. Wenn ja → Passwörter ändern!

Was kann ich als Entwickler tun?

Die drei einfachen Regeln:

  1. Nie Passwörter im Klartext speichern. Nie. Wirklich nie.
  2. Immer eine Library nutzen, die das richtig macht — bcrypt oder Argon2. Niemals selbst „mal kurz" hashen.
  3. HTTPS überall. Das Passwort muss vom Browser zum Server verschlüsselt unterwegs sein.
Goldene Regel: „Don't roll your own crypto." Auch wenn ihr glaubt, eine clevere Idee zu haben — fast immer ist sie unsicher. Nutzt etablierte Libraries, die von tausenden Experten geprüft wurden.

🤔 Mini-Quiz

Eine Webseite speichert Passwörter als SHA-256(passwort) ohne Salt. Ist das sicher?

A) Ja — SHA-256 ist ein moderner Algorithmus
B) Nein — Rainbow Tables knacken das in Sekunden
C) Nur wenn der Server gehackt wird
D) Egal — wir haben ja HTTPS
Warum B: Standard-Passwörter haben vorberechnete Hash-Tabellen. Ohne Salt kann der Hacker den Hash direkt nachschlagen → Passwort gefunden. Ohne Salt ist auch SHA-256 unsicher. Mit Salt + langsamer Hash (bcrypt) wäre es sicher.

Zusammenfassung — in einer Slide

  • Hash = Fingerabdruck für Daten. Schnell vorwärts, unmöglich rückwärts.
  • Passwörter werden gehasht gespeichert — die Webseite kennt euer echtes Passwort nicht.
  • Rainbow Tables knacken Hashes ohne Salt. Daher: Salt für jeden User einzeln.
  • Für Passwörter: bcrypt oder Argon2 — niemals MD5 oder SHA-1.
  • Als User: Passwort-Manager + 2FA. Als Entwickler: nie selbst krypto-en, immer Library.

📚 Quellen & weiterführende Literatur

Was ihr lesen solltet, wenn ihr tiefer einsteigen wollt:

📖 Standards & Guidelines

  • OWASP Password Storage Cheat Sheetcheatsheetseries.owasp.org
  • NIST SP 800-63B — Digital Identity Guidelines
  • BSI TR-02102-1 — Kryptografische Verfahren (Deutsch)

🔬 Akademisch & Tools

  • Provos & Mazières (1999): „A Future-Adaptable Password Scheme" — das bcrypt-Paper
  • Biryukov et al. (2016): „Argon2: the memory-hard function"
  • haveibeenpwned.com — eigene Konten prüfen
  • hashcat.net — der Standard-Hash-Cracker (für defensives Verständnis)

💻 C-Libraries

  • OpenSSLopenssl/sha.h, openssl/rand.h, openssl/hmac.h
  • libxcryptcrypt.h (bcrypt, yescrypt)
  • libsodium — modern, einfacher zu nutzen, crypto_pwhash (Argon2)

🎥 Zum Anschauen

  • Computerphile: SHA-256, Password Storage (YouTube — kurze, klare Erklärungen)
  • Crackstation.net: „Salted Password Hashing — Doing it Right" (ausführlicher Artikel)
  • Cryptopals Challenges: cryptopals.com — Übungs-Aufgaben, die zeigen wie krypto-Code angegriffen wird
Kurs-Tipp: wer's wirklich verstehen will, baut ein eigenes Mini-Login-System mit bcrypt — und versucht dann, es zu brechen. Praxis schlägt jede Theorie-Lektüre.

Vielen Dank!

Schönen Feierabend! 🌙

Prof. Dr. Alexandra Mikityuk

HTW Berlin · Büro Raum 308

Tel +49 30 5019-2664

Nächste Woche: Sicherheit II — Verschlüsselung

1 / …