Passord skal aldri ligge i klartekst
Hash et passord selv med Web Crypto i nettleseren — og forstå hvorfor bcrypt vinner i ekte systemer.
Her er kursets gullregel: passord skal aldri ligge i klartekst — ikke i databasen, ikke i loggene, ikke i en config-fil du glemte. Og denne leksjonen er spesiell: kursets eneste kjørbare øvelse ligger i arbeidsområdet nederst på siden, der du skal hash et passord for hånd, med Web Crypto-API-et i nettleseren din.
Lagringens gullregel: hash, ikke krypter
Først ordene, for de blander seg hele tiden: kryptering er to veier — med riktig nøkkel kan teksten hentes frem igjen. Hashing er én vei — samme inndata gir alltid samme utdata, men utdataet kan ikke snus tilbake. Et passord er noe du aldri trenger å lese igjen, bare sammenligne. Derfor: hash, aldri krypter. Og en hash passer perfekt på jobben: fast lengde (et passord på 3 eller 60 tegn gir 64 hex-tegn), determinisme (samme inndata, samme utdata — derfor kan den sammenlignes), og snøskred-egenskapen: endrer du én bokstav, endres *alle* 64 tegnene fullstendig. En database med hashede passord kan lekke uten at ett eneste passord lekkes med den.
async function sha256Hex(tekst) { // 1) Streng → bytes: const data = new TextEncoder().encode(tekst); // 2) Bytes → hash (kallet er asynkron — husk await): const digest = await crypto.subtle.digest("SHA-256", data); // 3) Hash-svaret er en ArrayBuffer — pakk det inn i en Uint8Array: const bytes = new Uint8Array(digest); // 4) Bytes → hex: hver byte til 16-tallsskrift, to tegn hver: return Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) .join("");} // sha256Hex("kanelbolle42") →// "1b64c3a87729218354a9b2398f5f98fe4b25ee23381f636d7c0ba922b3d435bc"Linje for linje — og en klassisk felle
Trinn 1 er Web Cryptos inngangsdør: TextEncoder gjør teksten om til UTF-8-byter, for kryptografi jobber med byter, aldri med tegn. Trinn 2 er selve hashing: digest er asynkron, så du venter med await. Trinn 3 er innpakning — svaret er en ArrayBuffer, en rå byte-samling du ikke kan løpe gjennom direkte, så du legger den i en Uint8Array.
Trinn 4 er hex-skrivingen, og der bor fellen: Uint8Array har en egen map — men den *returnerer en ny Uint8Array*, og der må alt være tall igjen. Strengene fra toString(16) tvinges rett tilbake til tall, og hexen din blir avsydd. Derfor Array.from først, så map på en vanlig array. Og padStart(2, "0") er ikke pynt: uten den mister byter under 16 sin ledende null, og hashen får vilkårlig lengde.
Obs
SHA-256 her er prinsippet, ikke praksisen
Ekte passordlagring bruker aldri SHA-256, og hasher aldri i nettleseren. Grunnen er fart: SHA-256 er bygget for å være rask, og en rask hash er billig å gjette seg gjennom. Ekte systemer hasher med bcrypt, argon2 eller scrypt på serveren — algoritmer som er *trege med vilje*, med en kostnadsfaktor du kan øke, og med salt innebygd: en tilfeldig verdi lagret sammen med hashen, så to like passord aldri gir samme lagerverdi, og ferdigbygde gjetningstabeller blir verdiløse. Websikkerhet-kurset går i dybden på dette.
// Serverkode med pakken bcrypt installert — ikke tilgjengelig i nettleseren:import bcrypt from "bcrypt"; // Ved lagring: hash med kostnadsfaktor (her 12) — saltet følger med automatisk:const passordhash = await bcrypt.hash(passord, 12); // Ved innlogging: sammenlign forsøket mot den lagrede hashen:const ok = await bcrypt.compare(innskrevetPassord, passordhash);Merk deg compare: den *dekrypterer* ingenting — den hasher forsøket med hashens egen salt og ser om det gir samme resultat. Klartekst-passordet finnes aldri i systemet etter inntastingen, ikke engang i minnet på serverens lager.
Øvelsen i arbeidsområdet
Nederst på siden ligger starteren med seks hull, og oppgavene svarer direkte til sjekkene i arbeidsområdet:
- Gjør teksten om til bytes — encoder-trinnet fra figuren over.
- Hash bytene med digest — husk at kallet er asynkron.
- Pakk svaret inn i en byte-visning slik at du kan løpe gjennom det.
- Bygg hex-strengen: 16-tallsskrift, fyll på med nuller, lim sammen — og returner.
- Skriv hashen ut i konsollen.
- Vis hashen i DOM-en, slik en ekte side ville gjort det.
Tips
Prøv snøskredet
Er øvelsen på plass: endre én bokstav i passordet og kjør igjen. «kanelbolle42» gir 1b64c3a8…435bc; «kanelbolle43» gir 1d2ecadd…8cd. Alle 64 tegnene forandret seg. Det er derfor en hash aldri kan snus tilbake — det finnes ingen kort vei fra den ene til den andre.
Prøv selv
Hash eller kryptering?
Tre teknikker fra en kodelogg. Trykk på hver — hvilken hører hjemme i passordlagringen, og hvorfor gjør ikke de to andre det?
Det finnes ikke feil her — trykk på alle tre hvis du vil.
Du kan nå
- forklare forskjellen på hashing (én vei) og kryptering (to veier med nøkkel)
- gjenta løpet streng → bytes → digest → bytes → hex, og si hvorfor Array.from må til
- si hvorfor SHA-256 i øvelsen er demo — og at ekte systemer bruker bcrypt/argon2 på serveren
Arbeidsområdet
JavaScriptHash-maskinen i nettleseren din: her skal du gjennom hele løpet streng → bytes → hash → bytes → hex, med den samme Web Crypto-API-en som en ekte side bruker. crypto.subtle bor i nettleseren og trenger verken server eller nøkler. Ett ærlig forbehold fra leksjonen: SHA-256 her er prinsippet i demostørrelse — ekte passordhashing skjer på serveren med trege algoritmer som bcrypt og argon2. Oppgavene i leksjonen svarer 1:1 til sjekkene: skriv koden, trykk «Kjør», se hashen i konsollen, og trykk «Sjekk løsningen» når alt står.
Tips: Ctrl + Enter kjører koden, Ctrl + Shift + Enter sjekker løsningen (Cmd på Mac).
Framdriften din
0 av 6
- Gjør teksten om til bytes
- Kaller crypto.subtle.digest med SHA-256
- Legger hash-svaret i en Uint8Array
- Bygger hex-strengen: toString(16) og padStart(2)
- Skriver hashen ut i konsollen
- Viser hashen i DOM-en med textContent
Konsoll
Kjør koden for å se output her.