Skip to content

Latest commit

 

History

History
324 lines (254 loc) · 17.1 KB

Password-encryption-algorithm-research.md

File metadata and controls

324 lines (254 loc) · 17.1 KB

Security research

Bcrypt data encryption

Onderzoeksvraag

Hoofdvraag:

Is het veilig om met Bcrypt wachtwoorden op te slaan?

Deelvragen:

  • Wat is Bcrypt?
  • Hoe werkt Bcrypt?
  • Waarom is Bcrypt veilig?
  • Hoe implementeer je Bcrypt?

Inleiding

Als je een applicatie maakt waarbij een gebruiker moet kunnen inloggen, krijg je waarschijnlijk te maken met het opslaan van wachtwoorden. Het opslaan van wachtwoorden brengt beveiligingsrisico's met zich mee. Bijvoorbeeld een hacker die in uw database inbreekt en alle wachtwoorden steelt. Hoe kun je het beste wachtwoorden opslaan op een veilige manier?

Tier Beveiliging
S-tier ???
A-tier Slow Hashing
B-tier Hashing + Salting
C-tier Hashing
D-tier Encrypting
F-tier Storing Plaintext

Hierboven staat een tabel, met onderaan de slechtste manier om dit te toen.
De F-tier manier om data op te slaan is net zoals normale text. Als er een inbraak plaats vind op uw database heeft de hacker gelijk toegang tot alle wachtwoorden. Hoe kunnen we het beter doen? In de D-tier kunnen we ervoor kiezen om encryptie toe te passen op onze wachtwoorden. Hierbij moet je op een veilige plaats een decryptie key opslaan. Als deze door een hacker buit word gemaakt zijn we weer terug bij de F-tier. Dan kan hij namelijk de wachtwoorden weer terug omzetten. Om bij de D-tier uit te komen willen we weten of het wachtwoord ingevoerd bij het registeren hetzelfde is als bij het inloggen. Om dit te verifiëren hoeven we het wachtwoord zelf niet te hebben. Hoe gaan we dit doen? Bij het registeren pakken we het wachtwoord, hiervan maken we een hash, deze slaan we op in de database in plaats van het echte wachtwoord. In de toekomst als de gebruiker probeert in te loggen pakken we het wachtwoord dat hij ingevoerd heeft. Deze sturen we door hetzelfde hashing proces heen. Hierna vergelijken we of de hashes gelijk aan elkaar zijn. Helaas zijn we hiermee nog niet helemaal veilig voor hacker. Je bent hiermee nog steeds vatbaar voor "dictionary attacks" of "rainbow tables" dit een lijst met veel voorkomende wachtwoorden gehasht, deze kan een hacker vergelijken met de wachtwoorden in de database. Laten we veder gaan naar de B-tier om te kijken hoe we ons hier tegen kunnen beschermen. Als de gebruiker zichzelf registreert, plaatsen we voor de hash een random array ook wel een salt genoemd. Hierdoor zijn "rainbow tables" niet meer te gebruiken omdat deze hashes bevatten zonder salt, en de wachtwoorden dus niet gelijk aan elkaar zullen zijn. Met krachtige hardware kan een hacker nog steeds alle mogelijkheden gaan uitproberen. Hoe beschermen we ons hiertegen? Daarvoor hebben we de A-tier. Bcrypt is bijvoorbeeld ontwikkeld om heel langzaam te zijn, veel energie te verbruiken & memory. Hierdoor kan het vrijwel onmogelijk worden om wachtwoorden te gaan gokken. Door Bcrypt zullen veel hacker afhaken.

Wat is Bcrypt?

Bcrypt is ontworpen door Niels Provos en David Mazières David Mazières en is gebaseerd op Blowfish. De B staat voor Blowfish en crypt voor de naam van de hashing functie gebruikt door het UNIX-wachtwoord systeem.

Crypt is een goed voorbeeld van het niet aanpassen aan technologische veranderingen. Volgens USENIX kon crypt in 1976 minder dan 4 wachtwoorden per seconden hashen. Voor hackers om een hash te inverten moeten ze eerst de pre-image vinden. Dit maakte het UNIX-team comfortabel over de kracht van crypt. Echter, 20 jaar later, kan een snelle computer met geoptimaliseerde software en hardware 200.000 wachtwoorden per seconden hashen met deze functie.

Inherent zou een hacker een dictionary attack kunnen uitvoeren met extreme efficiëntie. Dus moest er een cryptografie komen die exponentieel moeilijker te doorbreken is naarmate de hardware sneller wordt.

Hoe werkt Bcrypt?

Provos en Mazières ontwikkelde met het al bestaande Blowfish, een nieuw sleutelconfiguratie-algoritme genaamd "eksblowfish", wat staat voor "expensive key schedule Blowfish".

De functie EksBlowfishSetup wordt ingesteld met een cost, salt en het wachtwoord. Dit om de state van eksblowfish te initialiseren. Vervolgens besteedt Bcrypt veel tijd aan het uitvoeren van een sleutelschema dat bestaat uit het uitvoeren van een sleutelafleiding waarbij een reeks subsleutels afleiden van een primaire sleutel. Hierbij wordt het wachtwoord gebruikt als de primaire sleutel.

Als de gebruiker een slecht of kort wachtwoord gekozen heeft rekken we dat uit tot een langer wachtwoord/sleutel. Het bovengenoemde staat ook bekend als key stretching.

Een 192-bits "magic value" OrpheanBeholderScryDoubt wordt 64 keer versleuteld met eksblowfish in ECB-modus, en samengevoegd met de uitkomst van de vorige fase. De uitkomst van deze fase is de cost en een 128-bit salt waarde samen met het resultaat van de encryptie loop.

bcrypt(cost, salt, pwd)
    state <- EksBlowfishSetup(cost, salt, pwd)
    ctext <- "OrpheanBeholderScryDoubt"
    repeat(64)
        ctext <- EncryptECB(state, ctext)
    return Concatenate(cost, salt, ctext)

De resulterende hash start altijd met $2a$, $2y$ of $2b$. Deze voorvoegsels worden toegevoegd om het gebruik van Bcrypt en de gebruikte versie aan te geven.

$2b$15$Wx.kUJY72dw5fkMxMm4R5ejJ7NReyF7iB3A0mZtfX6BpvH0UXi9m6
\__/\__/\____________________/\____________________________/
 Id Cost        Salt                      Hash

In 1999 starte de originele Bcrypt hash met $2$. Hierna kwam versie $2a$ hierin zaten nieuwe functies zoals hoe een non-ASCII character te verwerken en hoe een null terminator afgehandeld moet worden. In 2011 kwamen de versies $2x$ en $2y$. In juni van dit jaar werd een bug ontdekt in crypt_blowfish, dit is een PHP-implementatie van Bcrypt. Alle systeembeheerders werd aangeraden hun bestaande wachtwoord databases te updaten en $2a$ te vervangen met $2x$, om aan te geven dat die hashes slecht zijn (en het oude kapotte algoritme moeten gebruiken). Alle nieuwe hashen van crypt_blowfish starten voortaan met $2y$. In 2014 is de meeste recente versie van Bcrypt uitgekomen namelijk versie $2b$. Er was namelijk een bug ontdekt in de OpenBSD-implementatie van Bcrypt.

Waarom is Bcrypt veilig?

Om Bcrypt veilig te houden is het aan de software engineer om te bepalen welke cost te kiezen. De cost staat ook wel bekend als de work factor. OWASP beveelt als vuistregel voor het instellen van de werkfactor aan om de kosten zo af te stemmen dat de functie zo langzaam mogelijk werkt zonder de gebruikerservaring te beïnvloeden en zonder de noodzaak om extra hardware te gebruiken die mogelijk boven het budget ligt.

Laten we de OWASP aanbevelingen beter bekijken:

  • Voer UX-onderzoek uit om te achterhalen wat acceptabele wachttijden voor uw gebruikers zijn voor registratie en authenticatie.
  • Als de geaccepteerde wachttijd 1 seconde is, stem dan de kosten van bcrypt af zodat deze binnen 1 seconde op uw hardware wordt uitgevoerd.
  • Analyseer of de rekentijd voldoende is om aanvallen af te zwakken en te vertragen.

Gebruikers zijn meestal tevreden met een wachttijd van 1 tot 2 seconden. Zolang zij zichzelf niet constant hoeven te authenticeren. Het proces wordt nog steeds als snel ervaren. Terwijl deze vertraging de inspanningen van een aanvaller zullen frustreren om snel een regenboogtabel te berekenen.

Doordat we de cost van Bcrypt kunnen afstemmen kunnen we schalen op nieuwe hardware optimalisatie.
Volgens een definitie van Moore's Law verdubbelt het aantal transistors per vierkante inch op geïntegreerde systemen ongeveer elke 18 maanden. Elke 2 jaar kunnen we standaard de cost verhogen om aan elke verandering tegemoet te komen. Je moet hier voorzichtig mee zijn, want als we gewoon de werkfactor van Bcrypt in onze code verhogen, wordt iedereen buitengesloten. Een migratieproces is in dit geval noodzakelijk.

Hieronder volgt een voorbeeld van hoe de cost de werktijd verhoogt. Het voorbeeld is een Node.js script dat de hash berekend van Wachtwoord12345 gebruik makende van de cost 10 tot en met 20.

const bcrypt = require("bcrypt");
const password = "Wachtwoord12345";

for (let saltRounds = 10; saltRounds < 21; saltRounds++) {
  console.time(`cost: ${saltRounds}, hash time`);
  bcrypt.hashSync(password, saltRounds);
  console.timeEnd(`cost: ${saltRounds}, hash time`);
}

Specificaties van mijn computer:

Dit zijn de resultaten:

cost: 10, hash time: 68.948ms
cost: 11, hash time: 141.034ms
cost: 12, hash time: 283.87ms
cost: 13, hash time: 577.135ms
cost: 14, hash time: 1.127s
cost: 15, hash time: 2.300s
cost: 16, hash time: 4.546s
cost: 17, hash time: 9.063s
cost: 18, hash time: 18.131s
cost: 19, hash time: 37.101s
cost: 20, hash time: 74.413s

Als we de data plotten in een Wolfram Alpha diagram, zien we dat de tijd om een wachtwoord te hashen exponentieel groeit als de cost verhoogt wordt met de hierboven genoemde hardware specificaties.

https://www.wolframalpha.com/input/?i2d=true&i=exponential+fit+68.948%5C%2844%29+141.034%5C%2844%29+283.87%5C%2844%29+577.135%5C%2844%29+1127%5C%2844%29+2300%5C%2844%29+4546%5C%2844%29+9063%5C%2844%29+18131%5C%2844%29+37101%5C%2844%29+74413

Voor deze dataset geeft Wolfram Alpha de best passende vergelijking met de kleinste kwadraten:

33.3494 e^(0.700986x)

Als we willen voorspellen hoe lang het duurt om een wachtwoord te hashen als we de cost op 30 zetten, kunnen we eenvoudig deze waarde voor x invullen.

33.3494 e^(0.700986(30)) = 45302125152.6

Met een cost van 30 zou het 45302125152.6 milliseconden duren om de hash te berekenen. Dit komt overeen met 755035.42 minuten of 524.33 dagen! Een veel snellere machine die geoptimaliseerd is met de nieuwste en beste technologie die tegenwoordig beschikbaar is, zou kortere rekentijden kunnen hebben. Gelukkig kunnen we met Bcrypt makkelijk schalen om snellere hardware tegemoet te komen.

Als een bedrijf ontdekt of vermoedt dat er een datalek van wachtwoorden heeft plaatsgevonden, ook in de hash vorm, moet het zijn gebruikers vragen om meteen hun wachtwoord te wijzigen. Met hashing en slating kan een brute force aanval succesvol voorkomen worden, is een enkele wachtwoordkraak is rekenkundig zeker haalbaar. Een aanvaller kan, met een enorme hoeveelheid rekenkracht, of door puur geluk, een enkel wachtwoord kraken, maar zelfs dan zou het proces zeer zeker traag zijn vanwege de kenmerken van bcrypt, waardoor het bedrijf en zijn gebruikers kostbare tijd krijgen om hun wachtwoorden te wijzigen.

Hoe implementeer je Bcrypt?

Hieronder een korte omschrijving over hoe de implementatie van Bcrypt met gebruik van Node.js in zijn werk gaat. Alle code staat ook in volgende GitHub repository.

Node.bcrypt.js wordt geïnstalleerd via npm (Node.js package manager), met het volgende command:

npm install bcrypt

Hierna, in de app.js creëren we een set van variabelen hiernaar verwijzen we tijdens de implementatie.

// app.js
const bcrypt = require("bcrypt");
const saltRounds = 15;
const password = "Wachtwoord12345";

Bcrypt geeft ons toegang tot een Node.js bibliotheek met hulpprogramma's om het hash-proces te vergemakkelijken. saltRounds geeft de cost aan. In het voorbeeld gebruiken we Wachtwoord12345 als variable password.

//app.js
const bcrypt = require("bcrypt");
const saltRounds = 15;
const password = "Wachtwoord12345";

bcrypt
  .genSalt(saltRounds)
  .then(salt => {
    console.log(`Salt: ${salt}`);
    return bcrypt.hash(password, salt);
  })
  .then(hash => {
    console.log(`Hash: ${hash}`);
    // Wachtwoord opslaan in uw database.
  })
  .catch(err => console.error(err.message));

Als eerste maken we een salt aan met de functie bcrypt.genSalt deze functie vraagt ons om de cost saltRounds. Eenmaal geslaagd krijgen we de salt waarde terug, deze sturen we samen met het wachtwoord password door naar de functie bcrypt.hash. Bij succes krijgen we een hash terug, deze kunnen we in onze database opslaan.

In de code staat twee keer een console.log een toont de salt de ander de hash.

Tijdens de eerste run zijn dit de resultaten:

Salt: $2b$15$Wx.kUJY72dw5fkMxMm4R5e
Hash: $2b$15$Wx.kUJY72dw5fkMxMm4R5ejJ7NReyF7iB3A0mZtfX6BpvH0UXi9m6

Het is niet mogelijk om deze resultaten te reproduceren, dit omdat de salt compleet random is elke keer dat genSalt uitgevoerd wordt. Tijdens een tweede run zijn dit de resultaten:

Salt: $2b$15$qH74g5/5ttl9iafc7XjYXu
Hash: $2b$15$qH74g5/5ttl9iafc7XjYXuJL8CdV96KPPqYa40KbImemgzAWdl/w6

Door gebruik te maken van de bcrypt.hash methode kunnen we een wachtwoord vergelijken met een opgeslagen hash.

//app.js
const bcrypt = require("bcrypt");
const password = "Wachtwoord12345";
const hash = "$2b$15$yjRTPHS0CN3mjVhe6YxFAu6.rnjQMAh8f/3DZckYDZBQtBLdGiPC2";

bcrypt
  .compare(password, hash)
  .then(res => {
    console.log(res);
  })
  .catch(err => console.error(err.message));

In het bovenstaande voorbeeld is de res true, dit geeft aan dat het password na dat het gehasht is gelijk is aan het opgeslagen wachtwoord hash.

Als we het password vervangen met een ander wachtwoord verwachten we als res false is.

//app.js
const bcrypt = require("bcrypt");
const password = "Foutwachtwoord123";
const hash = "$2b$15$yjRTPHS0CN3mjVhe6YxFAu6.rnjQMAh8f/3DZckYDZBQtBLdGiPC2";

bcrypt
  .compare(password, hash)
  .then(res => {
    console.log(res);
  })
  .catch(err => console.error(err.message));

En inderdaad de res zou nu false moeten zijn.

Iets wat opvalt, is dat we nooit de salt opslaan. Maar hoe weet bcrypt.compare dan welke salt te gebruiken? Kijkende naar het vorige hash/salt resultaat, kun je zien dat de hash de salt is met hieraan de wachtwoord hash toegevoegd.

Salt: $2b$15$Wx.kUJY72dw5fkMxMm4R5e
Hash: $2b$15$Wx.kUJY72dw5fkMxMm4R5ejJ7NReyF7iB3A0mZtfX6BpvH0UXi9m6

bcrypt.compare scheid het salt van de hash. En hashed vervolgens het verstrekte wachtwoord en vergelijkt deze.

Conclusie

Geen enkel opgeslagen wachtwoord is veilig voor hackers, wat programmeurs wel kunnen doen is het hun zo lastig mogelijk maken. Bcrypt is zeer intelligente software welke kan helpen bij het veilig opslaan van wachtwoorden in een database. Wel moet hierbij een (klein) onderzoek gedaan worden voor welke work factor gebruikt moet worden. Met een te hoge work factor krijgen gebruikers met lage hardware specificaties lange wachttijden. Bcrypt komt nieuwe hardware extreem goed tegemoet, waardoor wachtwoorden nog lang veilig blijven. In de tabel in de inleiding is nog een tier leeg de S-tier. Wat houd deze dan in? Het aller veiligste is om helemaal geen wachtwoorden op te slaan in je database. Dit kun je doen door gebruik te maken van externe authenticatie systemen bijvoorbeeld Google, Facebook & Auth0. Dan zorgen zij voor de wachtwoorden en kunnen deze ook niet gehackt worden.

Bibliografie

Wikipedia. (2021, 11 02). www.wikipedia.org. Opgehaald van wikipedia.org: https://en.wikipedia.org/wiki/Blowfish_(cipher)

USENIX. (1999, 04 28). www.usenix.org. Opgehaald van usenix.org: https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html

Qvault. (2020, 08 24). www.qvault.io. Opgehaald van qvault.io: https://qvault.io/golang/bcrypt-step-by-step/

Wikipedia. (2021, 11 21). www.wikipedia.org. Opgehaald van wikipedia.org: https://en.wikipedia.org/wiki/Bcrypt

NPMjs. (2021, 01 01). www.npmjs.com. Opgehaald van npmjs.com: https://www.npmjs.com/package/bcrypt

OWASP. (2021, 01 01). www.cheatsheetseries.owasp.org. Opgehaald van cheatsheetseries.owasp.org: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#work-factors

Investopedia. (2021, 02 23). www.investopedia.com. Opgehaald van investopedia.com: https://www.investopedia.com/terms/m/mooreslaw.asp

YouTube. (2014, 06 27). www.youtube.com. Opgehaald van YouTube.com: https://www.youtube.com/watch?v=O6cmuiTBZVs

StackExchange. (2018, 04, 01) www.security.stackexchange.com. Opgehaald van Security.stackexchange.com https://security.stackexchange.com/questions/184799/bcrypt-no-need-to-store-salt