Hashing, encryptie en encoding: drie verschillende dingen
Veel mensen gebruiken de termen hashing, encryptie en encoding door elkaar. Dat is begrijpelijk, want ze lijken op het eerste gezicht hetzelfde te doen: data omzetten naar een andere vorm. Maar de verschillen zijn fundamenteel en belangrijk voor de veiligheid van je applicaties.
Encoding is het omzetten van data naar een ander formaat voor transport of opslag, zonder enige beveiliging. Base64 encoding zet binaire data om naar tekst die veilig verstuurd kan worden via e-mail of in een URL. URL encoding vervangt speciale tekens door procentcodes zodat ze in een webadres passen. Encoding is volledig omkeerbaar en biedt geen bescherming. Het is geen beveiligingsmaatregel maar een transportmechanisme.
Hashing is een eenrichtingsproces. Een hash-functie neemt data van willekeurige grootte en produceert een vaste uitvoer, de hash of digest. Het cruciale verschil met encoding is dat hashing niet omkeerbaar is. Je kunt van een hash niet terug naar de originele data. Dit maakt hashing geschikt voor wachtwoordopslag en integriteitscontrole.
Encryptie is het versleutelen van data met een sleutel. In tegenstelling tot hashing is encryptie wél omkeerbaar, maar alleen als je de juiste sleutel hebt. Zonder de sleutel is de versleutelde data onleesbaar. Dit maakt encryptie geschikt voor het beschermen van gevoelige data in transit en opslag.
Hash-functies: MD5, SHA-1 en SHA-256
MD5 produceert een 128-bit hash van 32 hexadecimale tekens. Het is snel en wordt nog veel gebruikt voor checksums van bestandsdownloads. Je downloadt een bestand, berekent de MD5 hash en vergelijkt die met de hash die de aanbieder publiceert. Als ze overeenkomen, weet je dat het bestand onderweg niet is beschadigd of gemanipuleerd.
MD5 is echter niet meer veilig voor cryptografische doeleinden. Sinds 2004 zijn er praktische collision-aanvallen bekend: het is mogelijk om twee verschillende bestanden te maken die dezelfde MD5 hash opleveren. Voor wachtwoordopslag of digitale handtekeningen is MD5 daarom ongeschikt.
SHA-1 (160-bit, 40 hexadecimale tekens) is een stap beter maar ook niet meer als veilig beschouwd. Google demonstreerde in 2017 een praktische SHA-1 collision. SHA-1 wordt nog gebruikt in oudere systemen maar nieuwe implementaties moeten SHA-256 of hoger gebruiken.
SHA-256 is onderdeel van de SHA-2 familie en produceert een 256-bit hash van 64 hexadecimale tekens. Het is de huidige standaard voor cryptografische toepassingen. Bitcoin gebruikt SHA-256 voor mining. TLS certificaten gebruiken het voor handtekeningen. Git gebruikt het voor commit hashes in nieuwere versies.
De gouden regel: gebruik MD5 alleen voor niet-beveiligingskritische checksums. Gebruik SHA-256 of hoger voor alles waar beveiliging een rol speelt. En voor wachtwoorden gebruik je geen van beide maar een gespecialiseerde wachtwoord hash-functie als bcrypt of Argon2.
Base64 en URL encoding in de praktijk
Base64 encoding zet binaire data om naar een reeks van 64 leesbare tekens: A-Z, a-z, 0-9, + en /. Het resultaat is ongeveer 33% groter dan het origineel. Base64 wordt overal gebruikt: e-mailbijlagen (MIME), JSON Web Tokens (JWT), kleine afbeeldingen in HTML/CSS (data-URI's) en API authenticatie (Basic Auth).
Een praktisch voorbeeld: wanneer je inlogt bij een API met Basic Auth, worden je gebruikersnaam en wachtwoord gecombineerd tot gebruiker:wachtwoord, vervolgens Base64 gecodeerd en als header meegestuurd. De server decodeert de Base64 string om je credentials te lezen. Let op: dit is geen encryptie. Iedereen die de header onderschept kan de Base64 decoderen. Daarom mag Basic Auth alleen over HTTPS gebruikt worden.
URL encoding, ook wel percent-encoding genoemd, is nodig omdat URL's niet alle tekens ondersteunen. Een spatie wordt %20, een ampersand & wordt %26 en een schuine streep / wordt %2F. Dit is essentieel bij het doorgeven van zoektermen, formulierdata of parameters in URL's.
Een veelgemaakte fout is dubbele encoding: een string die al URL-gecodeerd is wordt nogmaals gecodeerd, waardoor %20 verandert in %2520. Dit resulteert in kapotte links en zoekresultaten. Een goede URL encoder detecteert of data al gecodeerd is en voorkomt dubbele encoding.
Key Takeaway
Base64 encoding zet binaire data om naar een reeks van 64 leesbare tekens: A-Z, a-z, 0-9, + en /.
Wachtwoorden veilig opslaan
De belangrijkste toepassing van hashing is wachtwoordopslag. Een goed beveiligde applicatie slaat nooit het wachtwoord zelf op, maar een hash van het wachtwoord. Wanneer een gebruiker inlogt, wordt het ingevoerde wachtwoord gehasht en vergeleken met de opgeslagen hash. Als ze overeenkomen, is het wachtwoord correct.
Maar een simpele SHA-256 hash van een wachtwoord is niet voldoende. Aanvallers gebruiken rainbow tables: voorberekende tabellen met hashes van miljoenen veelgebruikte wachtwoorden. Als jouw hash overeenkomt met een entry in de tabel, is het wachtwoord gekraakt.
De oplossing is salting: voeg een willekeurige string, het salt, toe aan het wachtwoord voordat je het hasht. Elk wachtwoord krijgt een uniek salt. Hierdoor zijn rainbow tables nutteloos omdat elke hash uniek is, zelfs als twee gebruikers hetzelfde wachtwoord hebben.
Moderne wachtwoord hash-functies als bcrypt, scrypt en Argon2 combineren salting met key stretching: ze voeren duizenden iteraties uit om de hash te berekenen. Dit maakt brute-force aanvallen praktisch onmogelijk. Waar een SHA-256 hash in nanoseconden berekend kan worden, kost een bcrypt hash bewust honderden milliseconden.
Voor developers: gebruik altijd de ingebouwde wachtwoord-hashfuncties van je framework. Django gebruikt standaard PBKDF2, Laravel gebruikt bcrypt, Node.js biedt bcrypt via npm. Bouw nooit je eigen wachtwoord-hashsysteem. De kans op fouten is te groot en de gevolgen bij een datalek desastreus.
UUID's, HMAC en praktische toepassingen
Een UUID (Universally Unique Identifier) is een 128-bit identifier in het formaat 8-4-4-4-12 hexadecimale tekens, bijvoorbeeld 550e8400-e29b-41d4-a716-446655440000. UUID's worden gebruikt als unieke sleutels in databases, als sessie-ID's, als transactie-referenties en als bestandsnamen om conflicten te voorkomen.
Het voordeel van UUID's boven oplopende nummers is dat ze onvoorspelbaar zijn en decentraal gegenereerd kunnen worden. Twee systemen kunnen tegelijkertijd UUID's aanmaken zonder coördinatie en toch gegarandeerd unieke waarden produceren. De kans op een collision is astronomisch klein: je zou miljarden UUID's per seconde moeten genereren om na 100 jaar een collision te verwachten.
HMAC (Hash-based Message Authentication Code) combineert hashing met een geheime sleutel om zowel integriteit als authenticiteit te waarborgen. Waar een gewone hash alleen controleert of data niet gewijzigd is, bewijst een HMAC dat de data is gemaakt of goedgekeurd door iemand met kennis van de geheime sleutel.
HMAC wordt gebruikt bij API authenticatie, webhook verificatie en token validatie. Stripe en GitHub gebruiken HMAC-SHA256 om webhooks te ondertekenen: ze berekenen een HMAC over de webhook body met een gedeeld geheim en sturen het resultaat mee als header. Jouw server berekent dezelfde HMAC en vergelijkt. Als ze overeenkomen, weet je dat de webhook echt van Stripe of GitHub komt en niet gemanipuleerd is.
Voor al deze toepassingen bestaan gratis online tools die je kunt gebruiken om hashes te berekenen, Base64 te coderen, UUID's te genereren en HMAC's te verifiëren. Ze zijn onmisbaar bij het testen van API integraties, het debuggen van authenticatieproblemen en het leren begrijpen van cryptografische concepten.
Key Takeaway
Een UUID (Universally Unique Identifier) is een 128-bit identifier in het formaat 8-4-4-4-12 hexadecimale tekens, bijvoorbeeld 550e8400-e29b-41d4-a716-446655440000.