{"id":79,"date":"2026-06-14T20:57:45","date_gmt":"2026-06-14T20:57:45","guid":{"rendered":"https:\/\/shattered.io\/no\/2026\/06\/14\/scrypt-passordhashing-nodejs\/"},"modified":"2026-06-15T04:46:01","modified_gmt":"2026-06-15T04:46:01","slug":"scrypt-passordhashing-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/no\/scrypt-passordhashing-nodejs\/","title":{"rendered":"scrypt Passordhashing i Node.js: 11 Steg [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>scrypt<\/strong> er en minnehard n\u00f8kkelderiveringsfunksjon som er innebygd i Node.js sin <code>crypto<\/code>-modul, og den er et av de tre algoritmevalgene OWASP fortsatt anbefaler for passordhashing i 2026. Denne veiledningen viser deg hvordan du bygger sikker <strong>scrypt passordhashing i Node.js<\/strong> fra bunnen av: salt, kostnadsparametre, lagringsformat, konstanttidssammenligning og en komplett, gjenbrukbar modul du kan koble rett inn i en Express-app. Du trenger ingen tredjepartspakker, kun det som f\u00f8lger med Node.js.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Regn med rundt 30 minutter p\u00e5 \u00e5 jobbe deg gjennom de 11 stegene. N\u00e5r du er ferdig, har du et komplett prosjekt som hasher passord med OWASP-godkjente parametre, verifiserer dem trygt mot tidsangrep, og oppgraderer gamle hasher automatisk n\u00e5r du \u00f8ker sikkerhetsmarginen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"scrypt-passordhashing-i-node-js-rask-oversikt\">scrypt passordhashing i Node.js: rask oversikt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Et passord skal aldri lagres i klartekst. Det skal heller ikke krypteres, for kryptering kan reverseres med en n\u00f8kkel. Riktig fremgangsm\u00e5te er \u00e5 lagre et envegs-avtrykk laget av en passordhashing-funksjon. scrypt ble designet av Colin Percival i 2009 nettopp for dette. Algoritmen er definert i <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7914\" target=\"_blank\" rel=\"noopener\">RFC 7914<\/a>, og den er minnehard, som betyr at den med vilje krever store mengder RAM. Det gj\u00f8r den dyr \u00e5 knekke med spesialisert maskinvare som GPU-er og ASIC-er, der minne er den begrensende ressursen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js har hatt en innebygd implementasjon av scrypt i <code>crypto<\/code>-modulen helt siden versjon 10. Det betyr at du i 2026 kan hashe passord trygt uten \u00e5 installere \u00e9n eneste npm-pakke. Du f\u00e5r tilgang til to funksjoner: <code>crypto.scrypt()<\/code> (asynkron, anbefalt for servere) og <code>crypto.scryptSync()<\/code> (synkron, nyttig for skript og tester). Begge bruker den samme algoritmen, men den asynkrone varianten blokkerer ikke event-loopen mens den maler gjennom hundretusenvis av runder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">scrypt h\u00f8rer hjemme i samme familie som <a href=\"\/no\/hashfunksjoner\/\">kryptografiske hashfunksjoner<\/a>, men det er en viktig forskjell. En r\u00e5 hashfunksjon som <a href=\"\/no\/sha-256\/\">SHA-256<\/a> er bygget for \u00e5 v\u00e6re rask. Passordhashing skal v\u00e6re tregt og minnekrevende med vilje, slik at en angriper som stjeler databasen din ikke kan gjette milliarder av passord i sekundet. Denne distinksjonen er hele poenget med <a href=\"\/no\/passordsikkerhet\/\">god passordsikkerhet<\/a>, og den er grunnen til at du aldri skal hashe passord med en vanlig SHA-funksjon alene.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I denne veiledningen bygger vi en produksjonsklar modul som dekker alt OWASP krever: tilfeldig salt per passord, kalibrerte kostnadsparametre, et selvbeskrivende lagringsformat, konstanttidssammenligning og automatisk rehashing. Vi avslutter med feils\u00f8king, benchmarking og en \u00e6rlig sammenligning mot bcrypt og Argon2id.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"scrypt-vs-bcrypt-vs-argon2id-hvilken-bor-du-velge\">scrypt vs bcrypt vs Argon2id: hvilken b\u00f8r du velge?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00f8r du skriver kode b\u00f8r du forst\u00e5 hvor scrypt st\u00e5r blant alternativene. OWASP anbefaler i 2026 tre algoritmer for passordlagring: Argon2id som f\u00f8rstevalg, scrypt der Argon2 ikke er tilgjengelig, og bcrypt for eldre systemer. Tabellen under oppsummerer de praktiske forskjellene for en Node.js-utvikler.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Egenskap<\/th><th>scrypt<\/th><th>bcrypt<\/th><th>Argon2id<\/th><\/tr><\/thead><tbody><tr><td>Innebygd i Node.js<\/td><td>Ja (crypto-modulen)<\/td><td>Nei (npm-pakke)<\/td><td>Nei (npm-pakke)<\/td><\/tr><tr><td>Minnehard<\/td><td>Ja<\/td><td>Nei<\/td><td>Ja<\/td><\/tr><tr><td>GPU-\/ASIC-motstand<\/td><td>H\u00f8y<\/td><td>Middels<\/td><td>Sv\u00e6rt h\u00f8y<\/td><\/tr><tr><td>Justerbart minne<\/td><td>Ja (N, r)<\/td><td>Nei<\/td><td>Ja<\/td><\/tr><tr><td>Maks passordlengde<\/td><td>Ingen reell grense<\/td><td>72 byte<\/td><td>Ingen reell grense<\/td><\/tr><tr><td>OWASP 2026-rangering<\/td><td>Andrevalg<\/td><td>Eldre systemer<\/td><td>F\u00f8rstevalg<\/td><\/tr><tr><td>Avhengigheter \u00e5 vedlikeholde<\/td><td>Null<\/td><td>\u00c9n (med bin\u00e6r)<\/td><td>\u00c9n (med bin\u00e6r)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">scrypt sin sterkeste fordel i Node.js er at den er innebygd. Du slipper \u00e5 vedlikeholde en npm-avhengighet som kompilerer en native bin\u00e6r, noe som ofte skaper tr\u00f8bbel i Docker-bilder, serverless-milj\u00f8er og ved Node-oppgraderinger. bcrypt har en beryktet begrensning: den ignorerer alt etter de f\u00f8rste 72 bytene av passordet, noe scrypt ikke gj\u00f8r. Argon2id vinner p\u00e5 ren motstandskraft, men krever pakken <code>argon2<\/code> og en byggekjede.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Velg scrypt n\u00e5r du vil ha minnehard sikkerhet uten eksterne avhengigheter, for eksempel i et lite team, et bibliotek du distribuerer videre, eller et milj\u00f8 der native moduler er vanskelige. Velg Argon2id n\u00e5r du har full kontroll over byggemilj\u00f8et og vil ha det aller sterkeste forsvaret. Uansett valg er prinsippene i denne veiledningen, salt, kalibrering, konstanttidssammenligning og rehashing, de samme.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"forutsetninger-versjoner-og-verktoy\">Forutsetninger: versjoner og verkt\u00f8y<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Du trenger lite for \u00e5 f\u00f8lge denne veiledningen, men versjonene betyr noe. scrypt-API-et er stabilt, men nyere Node.js-versjoner har bedre ytelse og sikkerhetsoppdateringer.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Node.js 24 LTS<\/strong> (anbefalt i 2026). Node.js 22 LTS fungerer ogs\u00e5. Alt fra Node.js 10 og oppover har <code>crypto.scrypt<\/code>, men bruk en aktiv LTS-linje for sikkerhetsoppdateringer. Se <a href=\"https:\/\/nodejs.org\/en\/about\/previous-releases\" target=\"_blank\" rel=\"noopener\">Node.js sin utgivelsesplan<\/a>.<\/li><li><strong>npm 10 eller nyere<\/strong> (f\u00f8lger med Node.js 24). Vi bruker npm kun til prosjektoppsett, ikke til avhengigheter for hashingen.<\/li><li><strong>En kodeeditor<\/strong> som VS Code, og en terminal.<\/li><li><strong>Grunnleggende JavaScript<\/strong> og kjennskap til async\/await og Promises.<\/li><li><strong>Valgfritt: Express 5<\/strong> for innloggings-eksempelet i steg 8.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Sjekk Node-versjonen din f\u00f8r du begynner. Kj\u00f8r kommandoen under, og bekreft at du har minst versjon 22.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --version\nv24.4.0\n\n$ npm --version\n10.9.2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hvis du ser en versjon eldre enn 22, installer en nyere fra nodejs.org eller via en versjonsmanager som nvm. Hele resten av veiledningen bruker kun den innebygde <code>node:crypto<\/code>-modulen, s\u00e5 det er ingen <code>npm install<\/code> for selve hashingen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"slik-fungerer-scrypt-kostnadsparametrene-n-r-og-p\">Slik fungerer scrypt: kostnadsparametrene N, r og p<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">scrypt styres av tre kostnadsparametre. \u00c5 forst\u00e5 dem er forskjellen mellom en trygg implementasjon og en falsk trygghet. I Node.js sitt options-objekt heter de <code>cost<\/code>, <code>blockSize<\/code> og <code>parallelization<\/code>, men du kan ogs\u00e5 bruke aliasene <code>N<\/code>, <code>r<\/code> og <code>p<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Parameter<\/th><th>Node-navn<\/th><th>Alias<\/th><th>Hva den styrer<\/th><th>Effekt<\/th><\/tr><\/thead><tbody><tr><td>CPU\/minne-kostnad<\/td><td>cost<\/td><td>N<\/td><td>Antall iterasjoner, m\u00e5 v\u00e6re en toer-potens<\/td><td>Dobling dobler b\u00e5de tid og minne<\/td><\/tr><tr><td>Blokkst\u00f8rrelse<\/td><td>blockSize<\/td><td>r<\/td><td>St\u00f8rrelsen p\u00e5 minneblokkene<\/td><td>\u00d8ker minnebruk line\u00e6rt<\/td><\/tr><tr><td>Parallellisering<\/td><td>parallelization<\/td><td>p<\/td><td>Antall uavhengige beregninger<\/td><td>\u00d8ker CPU-bruk line\u00e6rt<\/td><\/tr><tr><td>Minnegrense<\/td><td>maxmem<\/td><td>&#8211;<\/td><td>Tak for minnebruk i byte<\/td><td>Standard 32 MiB, m\u00e5 heves<\/td><\/tr><tr><td>N\u00f8kkellengde<\/td><td>keylen<\/td><td>&#8211;<\/td><td>Lengden p\u00e5 avledet n\u00f8kkel i byte<\/td><td>64 byte er et godt valg<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Minnebruken til scrypt f\u00f8lger formelen <code>128 * N * r<\/code> byte. Med OWASP sin 2026-anbefaling p\u00e5 N = 131072 (2^17) og r = 8 blir det <code>128 * 131072 * 8 = 134 217 728<\/code> byte, alts\u00e5 omtrent 128 MiB per hashing. Dette tallet er kritisk, for Node.js setter en standard <code>maxmem<\/code>-grense p\u00e5 32 MiB. Hvis du ber om mer minne enn det uten \u00e5 heve <code>maxmem<\/code>, kaster Node en feil i stedet for \u00e5 fortsette med svakere parametre. Det er en sikkerhetsfunksjon, ikke en bug.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Den viktigste innsikten er at N er den dominerende sikkerhetsspaken. En dobling av N dobler b\u00e5de regnetiden og minnebruken, noe som dobler kostnaden for en angriper. r og p justerer du sjelden bort fra 8 og 1. Vi setter konkrete tall i steg 4, men forst\u00e5 n\u00e5 at h\u00f8yere N gir bedre sikkerhet helt til hashing tar s\u00e5 lang tid at det g\u00e5r ut over brukeropplevelsen din.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-1-sett-opp-prosjektet\">Steg 1: Sett opp prosjektet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Opprett en ny mappe og initialiser et Node-prosjekt. Vi bruker ES-moduler (<code>type: \"module\"<\/code>) slik at vi kan bruke moderne <code>import<\/code>-syntaks.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir scrypt-passord && cd scrypt-passord\n$ npm init -y\n$ npm pkg set type=module\n$ node --version\nv24.4.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Det er hele oppsettet. Vi installerer ingen avhengigheter for selve hashingen, fordi alt ligger i <code>node:crypto<\/code>. Opprett en fil som heter <code>password.js<\/code>. Den blir den gjenbrukbare modulen vi bygger gjennom hele veiledningen, og vi tester den underveis fra et lite skript.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-2-generer-en-kryptografisk-salt\">Steg 2: Generer en kryptografisk salt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En salt er en tilfeldig verdi som legges til passordet f\u00f8r hashing. Den s\u00f8rger for at to brukere med samme passord f\u00e5r helt ulike hasher, og den gj\u00f8r forh\u00e5ndsberegnede tabeller (rainbow tables) ubrukelige. Hver bruker skal ha sin egen unike salt, og den m\u00e5 komme fra en kryptografisk sikker kilde.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bruk <code>crypto.randomBytes()<\/code>, aldri <code>Math.random()<\/code>. <code>Math.random()<\/code> er ikke kryptografisk sikker og kan forutsies. Vi bruker 16 byte (128 bit) salt, som er standarden OWASP anbefaler.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { randomBytes } from \"node:crypto\";\n\n\/\/ Generer 16 byte (128 bit) kryptografisk tilfeldig salt\nconst salt = randomBytes(16);\nconsole.log(salt.toString(\"base64\"));\n\/\/ Eksempel: \"9Qk2rN1pY7sV3xB0aZ4cFg==\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vi lagrer saltet sammen med hashen senere, i klartekst. Det er trygt og helt n\u00f8dvendig: du trenger n\u00f8yaktig samme salt for \u00e5 verifisere passordet ved innlogging. Salt er ingen hemmelighet, den skal bare v\u00e6re unik og uforutsigbar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-3-hash-passordet-med-crypto-scrypt\">Steg 3: Hash passordet med crypto.scrypt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e5 kobler vi salt og scrypt sammen. <code>crypto.scrypt()<\/code> er asynkron og bruker en callback, s\u00e5 vi pakker den i et Promise for \u00e5 kunne bruke async\/await. Den fulle signaturen er <code>crypto.scrypt(password, salt, keylen, options, callback)<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { scrypt, randomBytes } from \"node:crypto\";\nimport { promisify } from \"node:util\";\n\nconst scryptAsync = promisify(scrypt);\n\n\/\/ OWASP 2026-parametre for scrypt\nconst PARAMS = {\n  N: 2 ** 17,        \/\/ 131072, CPU\/minne-kostnad\n  r: 8,              \/\/ blokkst\u00f8rrelse\n  p: 1,              \/\/ parallellisering\n  keylen: 64,        \/\/ lengde p\u00e5 avledet n\u00f8kkel i byte\n  maxmem: 256 * 1024 * 1024, \/\/ 256 MiB, rom over 128 MiB-behovet\n};\n\nasync function hashRaw(password, salt) {\n  const derivedKey = await scryptAsync(password, salt, PARAMS.keylen, {\n    cost: PARAMS.N,\n    blockSize: PARAMS.r,\n    parallelization: PARAMS.p,\n    maxmem: PARAMS.maxmem,\n  });\n  return derivedKey; \/\/ Buffer p\u00e5 64 byte\n}\n\nconst salt = randomBytes(16);\nconst key = await hashRaw(\"riktig-hest-batteri-stift\", salt);\nconsole.log(key.toString(\"base64\"));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Legg merke til <code>maxmem: 256 * 1024 * 1024<\/code>. Uten den ville Node kastet feilen <code>Error: Invalid scrypt params<\/code>, fordi N = 131072 og r = 8 krever rundt 128 MiB, langt over standardgrensen p\u00e5 32 MiB. Vi setter taket til 256 MiB for \u00e5 gi rom til litt intern overhead. Output er en 64-byte Buffer, den avledede n\u00f8kkelen. Den alene er ikke nok \u00e5 lagre. Vi trenger ogs\u00e5 saltet og parametrene, noe vi l\u00f8ser i neste steg.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-4-velg-riktige-owasp-parametre-for-2026\">Steg 4: Velg riktige OWASP-parametre for 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Password_Storage_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">OWASP Password Storage Cheat Sheet<\/a> gir konkrete minimumsverdier for scrypt. Per 2026 er anbefalingen N = 2^17 (131072), r = 8 og p = 1. OWASP lister ogs\u00e5 likeverdige kombinasjoner med lavere minne og h\u00f8yere parallellisering, nyttige hvis maskinen din er minnebegrenset.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profil<\/th><th>N<\/th><th>r<\/th><th>p<\/th><th>Minne (ca.)<\/th><th>Bruk<\/th><\/tr><\/thead><tbody><tr><td>OWASP-anbefaling 2026<\/td><td>131072 (2^17)<\/td><td>8<\/td><td>1<\/td><td>128 MiB<\/td><td>Standard backend-servere<\/td><\/tr><tr><td>Minnesparende<\/td><td>65536 (2^16)<\/td><td>8<\/td><td>2<\/td><td>64 MiB<\/td><td>Begrenset RAM, beholdt styrke<\/td><\/tr><tr><td>Lett (likeverdig)<\/td><td>32768 (2^15)<\/td><td>8<\/td><td>3<\/td><td>32 MiB<\/td><td>Containere med stramt minne<\/td><\/tr><tr><td>For svak (unng\u00e5)<\/td><td>16384 (2^14)<\/td><td>8<\/td><td>1<\/td><td>16 MiB<\/td><td>Aldri i produksjon<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Den praktiske regelen er enkel: velg de h\u00f8yeste verdiene maskinvaren din t\u00e5ler uten at innlogging f\u00f8les treg. Et godt m\u00e5l er at \u00e9n hashing tar mellom 200 og 500 millisekunder p\u00e5 produksjonsserveren din. Det er raskt nok for en bruker som logger inn, men smertefullt dyrt for en angriper som pr\u00f8ver milliarder av gjetninger. Vi m\u00e5ler den faktiske tiden i steg 11.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ikke kopier parametre blindt. En kraftig server t\u00e5ler N = 2^18 uten problemer, mens en liten serverless-funksjon kanskje m\u00e5 ned p\u00e5 2^15. Det viktige er at du aldri g\u00e5r under 2^15 i produksjon, og at du lagrer parametrene sammen med hashen slik at du kan oppgradere dem senere.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-5-lagre-hash-salt-og-parametre-i-phc-format\">Steg 5: Lagre hash, salt og parametre i PHC-format<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Den avledede n\u00f8kkelen er verdil\u00f8s uten saltet og parametrene som lagde den. Den ryddigste m\u00e5ten \u00e5 lagre alt sammen p\u00e5 er PHC-strengformatet, den samme konvensjonen bcrypt og Argon2 bruker. Det pakker algoritme, parametre, salt og hash i \u00e9n selvbeskrivende streng. Da kan du senere lese hvilke parametre en gammel hash brukte og oppgradere den.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Bygg en PHC-lignende streng: $scrypt$n=...,r=...,p=...$salt$hash\nfunction encode(derivedKey, salt) {\n  const params = `n=${PARAMS.N},r=${PARAMS.r},p=${PARAMS.p}`;\n  const saltB64 = salt.toString(\"base64\");\n  const hashB64 = derivedKey.toString(\"base64\");\n  return `$scrypt$${params}$${saltB64}$${hashB64}`;\n}\n\n\/\/ Les en PHC-streng tilbake til komponentene\nfunction decode(phc) {\n  const parts = phc.split(\"$\"); \/\/ [\"\", \"scrypt\", \"n=...,r=...,p=...\", salt, hash]\n  if (parts.length !== 5 || parts[1] !== \"scrypt\") {\n    throw new Error(\"Ugyldig scrypt-hashformat\");\n  }\n  const params = Object.fromEntries(\n    parts[2].split(\",\").map((kv) => {\n      const [k, v] = kv.split(\"=\");\n      return [k, Number(v)];\n    })\n  );\n  return {\n    N: params.n,\n    r: params.r,\n    p: params.p,\n    salt: Buffer.from(parts[3], \"base64\"),\n    hash: Buffer.from(parts[4], \"base64\"),\n  };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En ferdig hash ser slik ut i databasen din:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$scrypt$n=131072,r=8,p=1$9Qk2rN1pY7sV3xB0aZ4cFg==$Tm8hX2...64byteIbase64...==<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lagre denne ene strengen i en <code>VARCHAR(255)<\/code>-kolonne. Du trenger ingen ekstra kolonner for salt eller parametre, alt ligger i strengen. Dette gj\u00f8r rehashing trivielt, for du kan lese de gamle parametrene rett fra hashen og sammenligne dem med dine gjeldende m\u00e5l.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-6-verifiser-passord-med-timingsafeequal\">Steg 6: Verifiser passord med timingSafeEqual<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Verifisering er der mange gj\u00f8r en farlig feil. Du skal aldri sammenligne hasher med <code>===<\/code> eller <code>Buffer.equals()<\/code>. Disse avslutter sammenligningen s\u00e5 snart de finner det f\u00f8rste byteparet som ikke stemmer. Forskjellen i tid er m\u00e5lbar, og en t\u00e5lmodig angriper kan bruke den til \u00e5 gjette hashen byte for byte. Dette kalles et timing-angrep.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L\u00f8sningen er <code>crypto.timingSafeEqual()<\/code>, som alltid bruker like lang tid uansett hvor i bufferne forskjellen ligger. Funksjonen krever at begge buffere har samme lengde, s\u00e5 vi rehasher det innsendte passordet med de lagrede parametrene f\u00f8r vi sammenligner.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { timingSafeEqual } from \"node:crypto\";\n\nasync function verify(password, phc) {\n  const { N, r, p, salt, hash } = decode(phc);\n\n  const derivedKey = await scryptAsync(password, salt, hash.length, {\n    cost: N,\n    blockSize: r,\n    parallelization: p,\n    maxmem: 256 * 1024 * 1024,\n  });\n\n  \/\/ Konstanttidssammenligning, beskytter mot timing-angrep\n  return timingSafeEqual(derivedKey, hash);\n}\n\nconst phc = \"$scrypt$n=131072,r=8,p=1$...salt...$...hash...\";\nconsole.log(await verify(\"riktig-hest-batteri-stift\", phc)); \/\/ true\nconsole.log(await verify(\"feil-passord\", phc));              \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Legg merke til at vi bruker <code>hash.length<\/code> som <code>keylen<\/code> og leser N, r og p fra den lagrede hashen, ikke fra de gjeldende globale parametrene. Det er avgj\u00f8rende: en bruker som registrerte seg for et \u00e5r siden kan ha en hash laget med svakere parametre, og verifisering m\u00e5 bruke akkurat de gamle verdiene for \u00e5 regne ut samme resultat.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-7-bygg-en-komplett-gjenbrukbar-passordmodul\">Steg 7: Bygg en komplett, gjenbrukbar passordmodul<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e5 samler vi alt i \u00e9n ferdig <code>password.js<\/code> du kan importere hvor som helst. Modulen eksporterer tre funksjoner: <code>hash()<\/code>, <code>verify()<\/code> og <code>needsRehash()<\/code>. Dette er det komplette, fungerende prosjektet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ password.js\nimport { scrypt, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { promisify } from \"node:util\";\n\nconst scryptAsync = promisify(scrypt);\n\n\/\/ Gjeldende OWASP-m\u00e5l for nye hasher\nexport const CURRENT = {\n  N: 2 ** 17,\n  r: 8,\n  p: 1,\n  keylen: 64,\n  saltBytes: 16,\n  maxmem: 256 * 1024 * 1024,\n};\n\nexport async function hash(password) {\n  if (typeof password !== \"string\" || password.length === 0) {\n    throw new Error(\"Passordet m\u00e5 v\u00e6re en ikke-tom streng\");\n  }\n  const salt = randomBytes(CURRENT.saltBytes);\n  const key = await scryptAsync(password, salt, CURRENT.keylen, {\n    cost: CURRENT.N,\n    blockSize: CURRENT.r,\n    parallelization: CURRENT.p,\n    maxmem: CURRENT.maxmem,\n  });\n  return `$scrypt$n=${CURRENT.N},r=${CURRENT.r},p=${CURRENT.p}$` +\n    `${salt.toString(\"base64\")}$${key.toString(\"base64\")}`;\n}\n\nexport async function verify(password, phc) {\n  if (typeof phc !== \"string\" || !phc.startsWith(\"$scrypt$\")) {\n    return false;\n  }\n  const parts = phc.split(\"$\");\n  if (parts.length !== 5) return false;\n\n  const params = Object.fromEntries(\n    parts[2].split(\",\").map((kv) => kv.split(\"=\"))\n  );\n  const N = Number(params.n);\n  const r = Number(params.r);\n  const p = Number(params.p);\n  const salt = Buffer.from(parts[3], \"base64\");\n  const hash = Buffer.from(parts[4], \"base64\");\n\n  const key = await scryptAsync(password, salt, hash.length, {\n    cost: N, blockSize: r, parallelization: p,\n    maxmem: CURRENT.maxmem,\n  });\n  return key.length === hash.length && timingSafeEqual(key, hash);\n}\n\nexport function needsRehash(phc) {\n  if (typeof phc !== \"string\" || !phc.startsWith(\"$scrypt$\")) return true;\n  const params = Object.fromEntries(\n    phc.split(\"$\")[2].split(\",\").map((kv) => kv.split(\"=\"))\n  );\n  return Number(params.n) &lt; CURRENT.N ||\n         Number(params.r) &lt; CURRENT.r ||\n         Number(params.p) &lt; CURRENT.p;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test modulen med et lite skript. Opprett <code>test.js<\/code> og kj\u00f8r <code>node test.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ test.js\nimport { hash, verify, needsRehash } from \".\/password.js\";\n\nconst phc = await hash(\"S0lsikke-Sommer-2026!\");\nconsole.log(\"Lagret hash:\\n\", phc);\nconsole.log(\"Riktig passord:\", await verify(\"S0lsikke-Sommer-2026!\", phc));\nconsole.log(\"Feil passord: \", await verify(\"feil\", phc));\nconsole.log(\"Trenger rehash:\", needsRehash(phc));<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node test.js\nLagret hash:\n $scrypt$n=131072,r=8,p=1$k3Jd...==$Tm8h...==\nRiktig passord: true\nFeil passord:  false\nTrenger rehash: false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Modulen er n\u00e5 komplett og produksjonsklar. Den genererer unik salt, bruker OWASP-parametre, lagrer alt i ett felt, verifiserer i konstant tid og kan oppdage gamle hasher som b\u00f8r oppgraderes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-8-integrer-med-express-registrering-og-innlogging\">Steg 8: Integrer med Express-registrering og innlogging<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La oss koble modulen inn i en ekte innloggingsflyt med Express 5. Installer Express med <code>npm install express<\/code>. Eksempelet bruker et enkelt minneobjekt som database, men i produksjon bytter du dette mot PostgreSQL, MySQL eller en annen vedvarende lagring.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport express from \"express\";\nimport { hash, verify, needsRehash } from \".\/password.js\";\n\nconst app = express();\napp.use(express.json());\n\nconst users = new Map(); \/\/ brukernavn -> { passwordHash }\n\napp.post(\"\/register\", async (req, res) => {\n  const { username, password } = req.body;\n  if (!username || !password || password.length &lt; 12) {\n    return res.status(400).json({ feil: \"Passord m\u00e5 v\u00e6re minst 12 tegn\" });\n  }\n  if (users.has(username)) {\n    return res.status(409).json({ feil: \"Brukernavn er opptatt\" });\n  }\n  const passwordHash = await hash(password);\n  users.set(username, { passwordHash });\n  res.status(201).json({ ok: true });\n});\n\napp.post(\"\/login\", async (req, res) => {\n  const { username, password } = req.body;\n  const user = users.get(username);\n\n  \/\/ Samme svar uansett om bruker finnes, mot brukeropptelling\n  const ok = user ? await verify(password, user.passwordHash) : false;\n  if (!ok) {\n    return res.status(401).json({ feil: \"Feil brukernavn eller passord\" });\n  }\n\n  \/\/ Oppgrader hashen i bakgrunnen hvis parametrene er utdaterte\n  if (needsRehash(user.passwordHash)) {\n    user.passwordHash = await hash(password);\n  }\n  res.json({ ok: true, melding: \"Innlogget\" });\n});\n\napp.listen(3000, () => console.log(\"Kj\u00f8rer p\u00e5 http:\/\/localhost:3000\"));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To detaljer her er viktige for sikkerheten. For det f\u00f8rste svarer innlogging med n\u00f8yaktig samme feilmelding enten brukeren ikke finnes eller passordet er feil. Det hindrer en angriper i \u00e5 finne ut hvilke brukernavn som eksisterer. For det andre oppgraderer vi hashen ved vellykket innlogging hvis <code>needsRehash()<\/code> sier at parametrene er utdaterte. Det er det eneste tidspunktet vi har passordet i klartekst, s\u00e5 det er da rehashing m\u00e5 skje.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Test det med curl. Registrer en bruker, og logg deretter inn.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ curl -X POST localhost:3000\/register \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"username\":\"kari\",\"password\":\"Fjord-Vinter-2026!\"}'\n{\"ok\":true}\n\n$ curl -X POST localhost:3000\/login \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"username\":\"kari\",\"password\":\"Fjord-Vinter-2026!\"}'\n{\"ok\":true,\"melding\":\"Innlogget\"}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-9-handter-maxmem-og-ytelse-riktig\">Steg 9: H\u00e5ndter maxmem og ytelse riktig<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>maxmem<\/code> er den parameteren som oftest velter en scrypt-implementasjon. Node.js setter standardgrensen til 32 MiB (33 554 432 byte). OWASP-parametrene v\u00e5re krever rundt 128 MiB, s\u00e5 uten \u00e5 heve <code>maxmem<\/code> f\u00e5r du en feil. Men du kan heller ikke sette <code>maxmem<\/code> vilk\u00e5rlig h\u00f8yt, for da risikerer du at mange samtidige innlogginger spiser opp serverens RAM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Regn ut det faktiske behovet og legg p\u00e5 en margin. Behovet er <code>128 * N * r<\/code> byte. For N = 131072 og r = 8 er det 128 MiB. Sett <code>maxmem<\/code> til rundt det dobbelte, 256 MiB, slik vi gjorde, for \u00e5 dekke intern overhead. Husk s\u00e5 at hver samtidige hashing bruker dette minnet. Hvis 10 brukere logger inn samtidig med 128 MiB hver, er det 1,28 GiB. Dette er en reell begrensning p\u00e5 trafikktunge tjenester.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Beregn minnebehov og sett maxmem dynamisk\nfunction memoryFor(N, r) {\n  const needed = 128 * N * r;          \/\/ faktisk behov i byte\n  return Math.ceil(needed * 2);         \/\/ dobbel margin\n}\n\nconsole.log(memoryFor(2 ** 17, 8) \/ (1024 * 1024)); \/\/ 256 (MiB)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Fordi <code>crypto.scrypt()<\/code> er asynkron, kj\u00f8rer den tunge beregningen i Node sin tr\u00e5dpool (libuv) og blokkerer ikke event-loopen. Det betyr at serveren fortsatt kan svare p\u00e5 andre foresp\u00f8rsler mens en hashing p\u00e5g\u00e5r. Bruk derfor alltid den asynkrone varianten i en webserver. <code>scryptSync()<\/code> blokkerer hele prosessen og h\u00f8rer kun hjemme i skript, migrasjonsverkt\u00f8y og tester.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">P\u00e5 tjenester med sv\u00e6rt h\u00f8y innloggingsrate b\u00f8r du vurdere \u00e5 begrense antall samtidige hashinger med en k\u00f8, slik at du ikke utl\u00f8ser en utilsiktet minneeksplosjon. En enkel semafor som tillater for eksempel fire samtidige hashinger gir forutsigbar minnebruk.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-10-rehashing-og-parameteroppgradering\">Steg 10: Rehashing og parameteroppgradering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Maskinvare blir raskere hvert \u00e5r, og det som var trygt i 2024 kan v\u00e6re for svakt i 2027. Derfor m\u00e5 du kunne \u00f8ke kostnadsparametrene over tid uten \u00e5 be alle brukere bytte passord. Det er nettopp dette PHC-formatet og <code>needsRehash()<\/code> l\u00f8ser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Strategien er enkel. N\u00e5r du bestemmer deg for \u00e5 heve N fra 2^17 til 2^18, oppdaterer du bare <code>CURRENT.N<\/code> i modulen. Eksisterende hasher fortsetter \u00e5 verifisere riktig fordi <code>verify()<\/code> leser de gamle parametrene fra selve hashen. Ved neste vellykkede innlogging ser <code>needsRehash()<\/code> at den lagrede N er lavere enn den nye, og du lager en fersk hash med de sterkere parametrene.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Inne i login-handleren, etter vellykket verify:\nif (needsRehash(user.passwordHash)) {\n  user.passwordHash = await hash(password); \/\/ bruker nye CURRENT-parametre\n  await db.updatePasswordHash(user.id, user.passwordHash);\n  console.log(`Oppgraderte hash for bruker ${user.id}`);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Denne tiln\u00e6rmingen er gradvis og usynlig for brukeren. Aktive brukere f\u00e5r automatisk sterkere hasher, og du slipper en stor migrasjon. For brukere som ikke har logget inn p\u00e5 lenge, beholder du de gamle hashene helt trygt, fordi selv de gamle parametrene fortsatt er over OWASP-minimum hvis du har fulgt r\u00e5dene her.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Den samme mekanismen lar deg migrere fra en annen algoritme. Hvis du har gamle bcrypt-hasher, kan du la <code>verify()<\/code> gjenkjenne <code>$2b$<\/code>-prefikset, verifisere med bcrypt, og deretter rehashe til scrypt ved innlogging. Slik bytter du algoritme over tid uten nedetid.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-11-benchmark-og-kalibrer-pa-din-maskinvare\">Steg 11: Benchmark og kalibrer p\u00e5 din maskinvare<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Parametre som er trygge p\u00e5 papiret kan v\u00e6re for trege eller for raske p\u00e5 din konkrete server. M\u00e5l alltid den faktiske tiden p\u00e5 maskinvaren du skal kj\u00f8re i produksjon. M\u00e5let er 200 til 500 millisekunder per hashing.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ benchmark.js\nimport { scryptSync, randomBytes } from \"node:crypto\";\n\nconst salt = randomBytes(16);\nconst password = \"benchmark-passord\";\n\nfor (const exp of [15, 16, 17, 18]) {\n  const N = 2 ** exp;\n  const start = process.hrtime.bigint();\n  scryptSync(password, salt, 64, {\n    cost: N, blockSize: 8, parallelization: 1,\n    maxmem: 512 * 1024 * 1024,\n  });\n  const ms = Number(process.hrtime.bigint() - start) \/ 1e6;\n  console.log(`N=2^${exp} (${N}): ${ms.toFixed(1)} ms`);\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node benchmark.js\nN=2^15 (32768): 58.3 ms\nN=2^16 (65536): 116.9 ms\nN=2^17 (131072): 232.4 ms\nN=2^18 (262144): 467.1 ms<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tallene over er typiske for en moderne server, men dine vil variere. Velg den N-verdien som lander mellom 200 og 500 ms. P\u00e5 maskinen i eksempelet treffer N = 2^17 perfekt p\u00e5 rundt 232 ms, mens N = 2^18 fortsatt er akseptabelt p\u00e5 467 ms hvis du vil ha ekstra margin. Kj\u00f8r benchmarken p\u00e5 nytt etter hver maskinvareoppgradering, og juster <code>CURRENT.N<\/code> deretter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vanlige-fallgruver-ved-scrypt-i-node-js\">Vanlige fallgruver ved scrypt i Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Disse feilene dukker opp gang p\u00e5 gang i ekte kodebaser. Unng\u00e5 dem, s\u00e5 har du en solid implementasjon.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Glemmer \u00e5 heve maxmem.<\/strong> Med OWASP-parametrene kaster Node <code>Error: Invalid scrypt params<\/code> fordi standardgrensen p\u00e5 32 MiB er for lav. Sett alltid <code>maxmem<\/code> til minst det dobbelte av <code>128 * N * r<\/code>.<\/li><li><strong>Bruker === eller Buffer.equals().<\/strong> Disse er s\u00e5rbare for timing-angrep. Bruk alltid <code>crypto.timingSafeEqual()<\/code> til \u00e5 sammenligne hasher.<\/li><li><strong>Gjenbruker salt mellom brukere.<\/strong> Hver bruker skal ha sin egen tilfeldige salt fra <code>crypto.randomBytes()<\/code>. Et fast eller delt salt \u00f8delegger hele poenget.<\/li><li><strong>Lagrer kun hashen uten parametre.<\/strong> Uten N, r, p og salt kan du verken verifisere eller rehashe. Bruk PHC-formatet og lagre alt i ett felt.<\/li><li><strong>Bruker Math.random() til salt.<\/strong> Den er ikke kryptografisk sikker og kan forutsies. Kun <code>crypto.randomBytes()<\/code> duger.<\/li><li><strong>Bruker scryptSync() i en webserver.<\/strong> Den blokkerer event-loopen og fryser hele serveren under hashing. Bruk den asynkrone <code>crypto.scrypt()<\/code>.<\/li><li><strong>Verifiserer med gjeldende parametre i stedet for de lagrede.<\/strong> En gammel hash m\u00e5 verifiseres med akkurat de N, r og p den ble laget med, lest fra selve hashen.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"feilsoking-8-vanlige-feil-og-losninger\">Feils\u00f8king: 8 vanlige feil og l\u00f8sninger<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Symptom<\/th><th>\u00c5rsak<\/th><th>L\u00f8sning<\/th><\/tr><\/thead><tbody><tr><td>Error: Invalid scrypt params<\/td><td>maxmem er for lav for valgt N og r<\/td><td>Sett maxmem til minst 128 * N * r, gjerne det dobbelte<\/td><\/tr><tr><td>Input buffers must have the same byte length<\/td><td>timingSafeEqual fikk buffere av ulik lengde<\/td><td>Bruk hash.length som keylen, og sjekk lengde f\u00f8r sammenligning<\/td><\/tr><tr><td>verify() returnerer alltid false<\/td><td>Leser nye parametre i stedet for lagrede<\/td><td>Hent N, r, p og salt fra PHC-strengen, ikke fra CURRENT<\/td><\/tr><tr><td>Innlogging tar flere sekunder<\/td><td>N er satt for h\u00f8yt for maskinvaren<\/td><td>Kj\u00f8r benchmark og senk N til 200-500 ms<\/td><\/tr><tr><td>Serveren fryser under last<\/td><td>scryptSync blokkerer event-loopen<\/td><td>Bytt til asynkron crypto.scrypt med promisify<\/td><\/tr><tr><td>Minnet renner over ved mye trafikk<\/td><td>For mange samtidige hashinger med h\u00f8y maxmem<\/td><td>Innf\u00f8r en semafor som begrenser samtidige hashinger<\/td><\/tr><tr><td>cost must be a power of 2<\/td><td>N er ikke en toer-potens<\/td><td>Bruk verdier som 2 ** 17, ikke vilk\u00e5rlige tall<\/td><\/tr><tr><td>Gamle hasher feiler etter parameterendring<\/td><td>Endret format uten bakoverkompatibilitet<\/td><td>Behold PHC-parsing slik at gamle parametre fortsatt leses<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">De fleste problemer koker ned til to ting: minnegrensen <code>maxmem<\/code> og forvekslingen mellom lagrede og gjeldende parametre. Hvis verify alltid feiler, legg inn en logglinje som skriver ut N, r og p fra b\u00e5de den lagrede hashen og den ferske beregningen. Da ser du raskt om de stemmer overens.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"avanserte-tips-for-produksjon\">Avanserte tips for produksjon<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"legg-pa-en-server-pepper\">Legg p\u00e5 en server-pepper<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En pepper er en hemmelig n\u00f8kkel som ikke ligger i databasen, men i milj\u00f8variabler eller en secrets-tjeneste. Du kombinerer passordet med pepperet f\u00f8r hashing, ofte med en HMAC. Hvis databasen lekker uten at applikasjonsserveren gj\u00f8r det, er alle hasher fortsatt ubrukelige for angriperen. Pepper supplerer salt, det erstatter det ikke.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { createHmac } from \"node:crypto\";\n\nconst PEPPER = process.env.PASSWORD_PEPPER; \/\/ last fra secrets, aldri hardkod\n\nfunction withPepper(password) {\n  return createHmac(\"sha256\", PEPPER).update(password).digest();\n}\n\/\/ Send withPepper(password) inn i scrypt i stedet for r\u00e5tt passord<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"sett-en-maks-passordlengde-mot-tjenestenekt\">Sett en maks passordlengde mot tjenestenekt<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">scrypt har ingen 72-byte-grense slik bcrypt har, men det betyr at en angriper kan sende inn et passord p\u00e5 flere megabyte for \u00e5 tvinge serveren til tung beregning. Sett en fornuftig \u00f8vre grense, for eksempel 128 eller 256 tegn, f\u00f8r du sender passordet til scrypt. Det stenger en enkel tjenestenektsvektor.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kombiner-med-tofaktor-og-overvaking\">Kombiner med tofaktor og overv\u00e5king<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Selv perfekt passordhashing beskytter ikke mot et lekket eller gjenbrukt passord. Legg p\u00e5 tofaktorautentisering, overv\u00e5k for mange mislykkede innlogginger fra samme IP, og sjekk passord mot kjente lekkasjelister ved registrering. Hashing er fundamentet, men en helhetlig <a href=\"\/no\/passordsikkerhet\/\">passordsikkerhetsstrategi<\/a> trenger flere lag. For \u00e5 forst\u00e5 hvordan selve avtrykket fungerer under panseret, se v\u00e5r gjennomgang av <a href=\"\/no\/digitale-signaturer\/\">digitale signaturer<\/a> og kryptografisk tillit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"lagring-i-database-og-valg-av-kolonnetype\">Lagring i database og valg av kolonnetype<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e5r modulen din produserer en PHC-streng, trenger du ett tekstfelt i databasen til \u00e5 lagre den. Strengen inneholder algoritmenavn, parametrene n, r og p, et base64-kodet salt p\u00e5 16 byte og en base64-kodet hash p\u00e5 64 byte. Med OWASP-parametrene blir den typisk rundt 130 til 140 tegn lang, men la deg ikke friste til \u00e5 bruke et felt som er n\u00f8yaktig s\u00e5 stort. Du vil \u00f8ke parametrene over tid, og du vil kanskje st\u00f8tte flere algoritmer, s\u00e5 gi deg selv slingringsmonn.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Database<\/th><th>Anbefalt kolonnetype<\/th><th>Merknad<\/th><\/tr><\/thead><tbody><tr><td>PostgreSQL<\/td><td>TEXT eller VARCHAR(255)<\/td><td>TEXT har ingen ytelsesstraff i Postgres<\/td><\/tr><tr><td>MySQL \/ MariaDB<\/td><td>VARCHAR(255)<\/td><td>Bruk utf8mb4 og en bin\u00e6rsikker collation<\/td><\/tr><tr><td>SQLite<\/td><td>TEXT<\/td><td>Ingen lengdegrense \u00e5 tenke p\u00e5<\/td><\/tr><tr><td>MongoDB<\/td><td>String<\/td><td>Lagre som vanlig strengfelt<\/td><\/tr><tr><td>Redis (cache)<\/td><td>String<\/td><td>Aldri som eneste varig lagring<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">En vanlig feil er \u00e5 splitte hashen i flere kolonner, \u00e9n for salt, \u00e9n for parametrene og \u00e9n for selve hashen. Det gj\u00f8r koden mer komplisert og gj\u00f8r migrering vanskeligere. Hold deg til \u00e9n streng i ett felt. Hele poenget med PHC-formatet er at alt du trenger for \u00e5 verifisere og oppgradere ligger samlet p\u00e5 ett sted, akkurat slik bcrypt og Argon2 ogs\u00e5 gj\u00f8r det.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00f8rg ogs\u00e5 for at kolonnen aldri logges eller eksporteres ved et uhell. Passordhasher h\u00f8rer ikke hjemme i applikasjonslogger, feilrapporter eller analyseeksporter. Marker feltet som sensitivt i ORM-en din, og dobbeltsjekk at det ikke havner i serialiserte API-svar. En lekket hash er ikke like ille som et lekket klartekstpassord, men med svake parametre kan den fortsatt knekkes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"enhetstesting-av-passordmodulen\">Enhetstesting av passordmodulen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En passordmodul fortjener tester, for feil her er stille og farlige. Node.js har en innebygd testl\u00f8per siden versjon 18, s\u00e5 du trenger ingen ekstra pakke. Opprett <code>password.test.js<\/code> og kj\u00f8r den med <code>node --test<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ password.test.js\nimport { test } from \"node:test\";\nimport assert from \"node:assert\/strict\";\nimport { hash, verify, needsRehash } from \".\/password.js\";\n\ntest(\"riktig passord verifiserer til true\", async () => {\n  const phc = await hash(\"hemmelig-passord-123\");\n  assert.equal(await verify(\"hemmelig-passord-123\", phc), true);\n});\n\ntest(\"feil passord verifiserer til false\", async () => {\n  const phc = await hash(\"hemmelig-passord-123\");\n  assert.equal(await verify(\"feil-passord\", phc), false);\n});\n\ntest(\"samme passord gir ulike hasher (unik salt)\", async () => {\n  const a = await hash(\"samme-passord\");\n  const b = await hash(\"samme-passord\");\n  assert.notEqual(a, b);\n});\n\ntest(\"ugyldig hashformat gir false, ikke unntak\", async () => {\n  assert.equal(await verify(\"passord\", \"ikke-en-gyldig-hash\"), false);\n});\n\ntest(\"needsRehash er true for svakere parametre\", () => {\n  const gammel = \"$scrypt$n=16384,r=8,p=1$c2FsdA==$aGFzaA==\";\n  assert.equal(needsRehash(gammel), true);\n});<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --test\n\u2714 riktig passord verifiserer til true (241.7ms)\n\u2714 feil passord verifiserer til false (238.4ms)\n\u2714 samme passord gir ulike hasher (unik salt) (480.1ms)\n\u2714 ugyldig hashformat gir false, ikke unntak (0.6ms)\n\u2714 needsRehash er true for svakere parametre (0.4ms)\n\u2139 tests 5\n\u2139 pass 5\n\u2139 fail 0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">De viktigste testene er at samme passord gir to ulike hasher, som beviser at saltet faktisk er unikt per kall, og at et ugyldig hashformat returnerer false i stedet for \u00e5 kaste et unntak. Det siste er en sikkerhetsdetalj: en innloggingsfunksjon som krasjer p\u00e5 en korrupt hash kan lekke informasjon eller skape en tjenestenektmulighet. Testene bekrefter ogs\u00e5 at <code>needsRehash()<\/code> fanger opp gamle, svake parametre slik at oppgraderingsmekanismen faktisk sl\u00e5r inn.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"hvorfor-scrypt-fortsatt-holder-mal-i-2026\">Hvorfor scrypt fortsatt holder m\u00e5l i 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Et naturlig sp\u00f8rsm\u00e5l er om en algoritme fra 2009 fortsatt er trygg i 2026. Svaret er ja, forutsatt at du bruker riktige parametre. scrypt sin minnehardhet er fortsatt en effektiv barriere mot GPU- og ASIC-baserte angrep, og det er nettopp derfor OWASP fremdeles lister den som et godkjent valg. Den eneste grunnen til at Argon2id rangeres over scrypt er at Argon2 er nyere, vant Password Hashing Competition i 2015, og gir enda finere kontroll over minne kontra tid.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For en Node.js-utvikler er det avgj\u00f8rende argumentet at scrypt er innebygd. Du eliminerer en hel klasse av problemer knyttet til native npm-pakker: byggefeil i CI, inkompatibilitet ved Node-oppgraderinger, og forsinkelser i sikkerhetspatcher fra tredjeparter. I et milj\u00f8 der forsyningskjedeangrep mot npm har blitt et tilbakevendende tema, er det en reell fordel \u00e5 holde antallet avhengigheter nede.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Det som derimot ikke holder m\u00e5l, er \u00e5 hashe passord med en r\u00e5 hashfunksjon. Hvis du fortsatt ser <code>crypto.createHash(\"sha256\")<\/code> brukt p\u00e5 passord i kodebasen din, er det en alvorlig s\u00e5rbarhet. SHA-256 regner ut milliarder av hasher i sekundet p\u00e5 en GPU, s\u00e5 en lekket database med slike hasher kan knekkes nesten umiddelbart. Bytt til scrypt, bcrypt eller Argon2id med en gang. Bakgrunnen for hvorfor rene hashfunksjoner er upassende her, dekkes grundig i artikkelen om <a href=\"\/no\/hashfunksjoner\/\">kryptografiske hashfunksjoner<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ofte-stilte-sporsmal-om-scrypt-i-node-js\">Ofte stilte sp\u00f8rsm\u00e5l om scrypt i Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"trenger-jeg-en-npm-pakke-for-scrypt-i-node-js\">Trenger jeg en npm-pakke for scrypt i Node.js?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nei. scrypt er innebygd i <code>node:crypto<\/code> via <code>crypto.scrypt()<\/code> og <code>crypto.scryptSync()<\/code> og har v\u00e6rt det siden Node.js 10. Du trenger ingen avhengigheter for selve hashingen. Det er en av scrypt sine st\u00f8rste fordeler over bcrypt og Argon2, som begge krever native npm-pakker.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hvilke-scrypt-parametre-bor-jeg-bruke-i-2026\">Hvilke scrypt-parametre b\u00f8r jeg bruke i 2026?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">OWASP anbefaler N = 131072 (2^17), r = 8 og p = 1 som utgangspunkt. Disse krever rundt 128 MiB minne, s\u00e5 husk \u00e5 heve <code>maxmem<\/code>. Kj\u00f8r deretter en benchmark og juster N slik at \u00e9n hashing tar 200 til 500 ms p\u00e5 din maskinvare. G\u00e5 aldri under N = 2^15 i produksjon.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hvorfor-far-jeg-invalid-scrypt-params\">Hvorfor f\u00e5r jeg &#8220;Invalid scrypt params&#8221;?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Feilen kommer nesten alltid av at <code>maxmem<\/code> er for lav. Node setter standardgrensen til 32 MiB, men OWASP-parametrene trenger rundt 128 MiB. Sett <code>maxmem<\/code> til minst <code>128 * N * r<\/code>, gjerne det dobbelte for \u00e5 dekke overhead. Sjekk ogs\u00e5 at N er en toer-potens.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"er-scrypt-sikrere-enn-bcrypt\">Er scrypt sikrere enn bcrypt?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For de fleste form\u00e5l, ja. scrypt er minnehard, noe bcrypt ikke er, og det gir bedre motstand mot GPU- og ASIC-angrep. bcrypt har dessuten en grense p\u00e5 72 byte for passordlengde, som scrypt ikke har. Argon2id regnes likevel som det aller sterkeste valget i 2026.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hvordan-lagrer-jeg-saltet\">Hvordan lagrer jeg saltet?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Saltet lagres i klartekst sammen med hashen, gjerne i PHC-strengformatet sammen med parametrene. Salt er ikke hemmelig, det skal bare v\u00e6re unikt per bruker og uforutsigbart. Du trenger n\u00f8yaktig samme salt for \u00e5 verifisere passordet ved innlogging, s\u00e5 det m\u00e5 lagres.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kan-jeg-bytte-fra-bcrypt-til-scrypt-uten-a-nullstille-passord\">Kan jeg bytte fra bcrypt til scrypt uten \u00e5 nullstille passord?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ja. La verify-funksjonen gjenkjenne det gamle bcrypt-prefikset (<code>$2b$<\/code>), verifisere med bcrypt, og deretter rehashe til scrypt ved vellykket innlogging. Over tid migreres alle aktive brukere automatisk uten at de merker noe og uten nedetid.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"bor-jeg-bruke-scrypt-eller-argon2id-til-et-nytt-prosjekt\">B\u00f8r jeg bruke scrypt eller Argon2id til et nytt prosjekt?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hvis du vil ha det sterkeste forsvaret og har full kontroll over byggemilj\u00f8et, velg Argon2id. Hvis du verdsetter null avhengigheter og enkel drift, er scrypt et utmerket og OWASP-godkjent valg. Begge er trygge n\u00e5r de er riktig konfigurert. Prinsippene i denne veiledningen gjelder uansett hvilken du velger.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"relatert-innhold\">Relatert innhold<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/no\/passordsikkerhet\/\">Passordsikkerhet: sterke passord, hashing og tofaktor<\/a><\/li><li><a href=\"\/no\/hashfunksjoner\/\">Kryptografiske hashfunksjoner: egenskaper og bruk<\/a><\/li><li><a href=\"\/no\/sha-256\/\">SHA-256 forklart: 256-bits avtrykk i praksis<\/a><\/li><li><a href=\"\/no\/digitale-signaturer\/\">Digitale signaturer: hashfunksjoner og asymmetriske n\u00f8kler<\/a><\/li><li><a href=\"\/no\/https-og-tls\/\">HTTPS og TLS: slik beskyttes forbindelsen din p\u00e5 nett<\/a><\/li><li><a href=\"\/no\/cryptography-hub\/\">Kryptografi: hashfunksjoner, SHA og digital tillit<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kilder og videre lesning:<\/strong> <a href=\"https:\/\/nodejs.org\/api\/crypto.html\" target=\"_blank\" rel=\"noopener\">Node.js crypto-dokumentasjon<\/a>, <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Password_Storage_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">OWASP Password Storage Cheat Sheet<\/a>, <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7914\" target=\"_blank\" rel=\"noopener\">RFC 7914 (scrypt)<\/a>, <a href=\"https:\/\/nodejs.org\/en\/about\/previous-releases\" target=\"_blank\" rel=\"noopener\">Node.js utgivelsesplan<\/a> og <a href=\"https:\/\/en.wikipedia.org\/wiki\/Scrypt\" target=\"_blank\" rel=\"noopener\">scrypt p\u00e5 Wikipedia<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>scrypt er en minnehard n\u00f8kkelderiveringsfunksjon som er innebygd i Node.js sin crypto-modul, og den er et av de tre algoritmevalgene OWASP fortsatt anbefaler for passordhashing i 2026. Denne veiledningen viser\u2026<\/p>\n","protected":false},"author":6,"featured_media":80,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-79","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts\/79","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/comments?post=79"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts\/79\/revisions"}],"predecessor-version":[{"id":81,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/posts\/79\/revisions\/81"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/media\/80"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/media?parent=79"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/categories?post=79"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/no\/wp-json\/wp\/v2\/tags?post=79"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}