Kodning, hashning och kryptering är tre skilda saker

Hur kodning, hashning och kryptering skiljer sig åt, vad Base64, procentkodning och HTML-escaping faktiskt är till för, och hur du känner igen en okänd sträng på synen.

En sträng som ser förvrängd ut är inte en sträng som är skyddad. Tre skilda operationer ger oläsbar utdata, och att blanda ihop dem är där verkliga säkerhetsbuggar uppstår.

Vändbar?Kräver nyckel?Vad den är till för
KodningJa, av vem som helstNejAtt få bytes genom en kanal som inte kan bära dem råa
HashningNejNejFingeravtryck, integritetskontroller, uppslagsnycklar
KrypteringJa, med nyckelnJaSekretess

Kodning är ett byte av alfabet. Base64, procentkodning, hexadecimal notation och HTML-entiteter skriver samma information på ett annat sätt så att en parser längre fram inte sätter den i halsen. Ingen hemlighet är inblandad, så det finns ingenting att bevara. En caesarförskjutning hör också hemma här: verktyget Caesar Cipher provar alla 25 förskjutningarna på en gång, en rättvis sammanfattning av hur mycket en fast substitution skyddar. Hashning avbildar godtycklig indata på en digest med fast längd och kan inte köras baklänges. Kryptering är den enda av de tre som ger konfidentialitet, och den involverar alltid en nyckel. Om du inte kan peka på nyckeln krypteras ingenting.

Base64 och vad det kostar

Base64 delar tre bytes (24 bitar) i fyra grupper om sex och skriver varje grupp som ett tecken ur ett alfabet med 64 tecken. Fyra tecken per tre bytes betyder att utdata blir en tredjedel större. Det spelar störst roll när filer bäddas in: verktyget File to Base64 producerar en data-URL, så en bild på 300 KB hamnar i din HTML på ungefär 400 KB, omhämtad vid varje sidvisning.

När indata inte är en multipel av tre bytes fylls den sista gruppen ut med =:

"abc"  ->  YWJj      (3 bytes, no padding)
"ab"   ->  YWI=      (2 bytes, one =)
"a"    ->  YQ==      (1 byte, two =)

Standardalfabetet slutar med + och /, vilket är ett problem i en URL, eftersom + avkodas som ett mellanslag i formulärdata och / är en sökvägsavgränsare. Därför finns ett andra alfabet: Base64url byter + mot - och / mot _, och släpper vanligtvis utfyllnaden. Det är därför en token som kopierats ur en URL ibland faller i en avkodare inställd på standardalfabetet. Base64 Encoder hanterar båda, och hanterar Unicode korrekt, vilket naiva implementationer ofta inte gör: text måste UTF-8-kodas före Base64-steget.

Inget av detta är en säkerhetsåtgärd. Ett värde som en produkt kallar en "krypterad" identifierare i en URL är mycket ofta Base64 av ett vanligt heltal som vem som helst kan läsa, ändra och koda om på några sekunder. Detsamma gäller nyttolasten i en JSON Web Token: JWT Decoder läser den utan nyckel, eftersom det inte finns något att låsa upp.

Procentkodning, och vilken funktion du ska använda

Procentkodning ersätter en byte med % och två hexadecimala siffror. Den arbetar på bytes snarare än på tecken, så text som inte är ASCII UTF-8-kodas först: é blir %C3%A9.

Oreserverade tecken (A-Z, a-z, 0-9, -, ., _, ~) behöver aldrig kodas. Reserverade tecken (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) är de avgränsare som ger en URL dess struktur, och om ett av dem behöver kodas beror på om det används som avgränsare eller som data. Det är skillnaden mellan de två JavaScript-funktionerna:

encodeURI("https://ex.com/s?q=cats & dogs")
// https://ex.com/s?q=cats%20&%20dogs   the & still splits the query

encodeURIComponent("cats & dogs")
// cats%20%26%20dogs                    safe as a single value

encodeURI är till för en hel URL som du vill göra tillåten, så den lämnar avgränsarna orörda. encodeURIComponent är till för ett enskilt värde som ska in i ett sökvägssegment eller en frågeparameter, så den kodar dem. Använd den andra för allt du stoppar in, och notera att den ändå lämnar !, ', (, ) och * i fred.

Sedan har vi plustecknet. Formulärkroppar som skickas som application/x-www-form-urlencoded kodar ett mellanslag som +, och konventionen läckte in i frågesträngar, så de flesta servrar avkodar + i en frågesträng som ett mellanslag. Ett bokstavligt plus måste skickas som %2B, vilket är varför alex+billing@example.com så ofta anländer som alex billing@example.com med taggen förstörd. I ett sökvägssegment är + däremot bara ett plus, och ett mellanslag är %20. URL Encoder gör det synligt när ett värde överlever en rundresa och går sönder på nästa.

Escaping beror på var värdet hamnar

Det finns ingen enda form av en sträng som är "escapad för webben". Sammanhanget avgör.

SammanhangKorrekt behandling
HTML-textEntiteter för &, < och >
Citerat attributEntiteter för & och för det citattecken som omger det, och citera alltid attributet
Inuti <script>JavaScript-strängescaping, inte entiteter
URL i href eller srcProcentkoda värdet, attributescapa det, kontrollera sedan schemat
CSS-värdeCSS-escaping, och bygg aldrig url() av användarindata

Skriptfallet lurar folk. Skriptinnehåll entitetsavkodas inte, så en entitet som skrivs där förblir bokstavlig, och HTML-parsern stänger blocket vid sekvensen </script var den än förekommer, i citerad sträng eller inte:

<script>var s = "</script>";</script>   <!-- block ends early -->
<script>var s = "<\/script>";</script>  <!-- correct -->

Escaping ensamt räcker inte för URL:er heller. Ett värde som börjar med javascript: körs när det klickas hur omsorgsfullt det än kodades, så validera schemat mot en lista över tillåtna. Verktyget HTML Entities täcker text- och attributfallen och avkodar entiteter tillbaka, det vanliga behovet när ett API har dubbelescapat något till &amp;amp;.

Hashning: vilken algoritm, och till vad

AlgoritmDigestStatus
MD5128 bitarKnäckt. Kollisioner är triviala
SHA-1160 bitarKnäckt. Kollisioner med valt prefix är praktiskt genomförbara och billiga
SHA-256, SHA-512256, 512 bitarBra för integritet och signaturer
bcrypt, scrypt, Argon2varierarEndast lösenord

MD5 och SHA-1 faller på kollisionsresistens, vilket betyder att en angripare kan konstruera två olika indata med samma digest. Överallt där en hash står i stället för ett dokument är de oanvändbara: signaturer, certifikat, innehållsadressering, avdubblering av något en angripare tillhandahåller. Ingen av dem är knäckt för urbilder, så en gammal MD5-kontrollsumma fångar fortfarande oavsiktlig förvanskning, men ingen av dem hör hemma i nytt arbete. Använd SHA-256, som Hash Generator beräknar för filer såväl som för text.

Lösenord är ett annat problem, och en snabb hash är fel verktyg just för att den är snabb. Vanlig hårdvara gör miljarder SHA-256-operationer per sekund, så en läckt tabell med SHA-256-lösenordshashar är en läckt tabell med lösenord. bcrypt, scrypt och Argon2id är avsiktligt långsamma och justerbara, med en arbetsfaktor (och för de två sistnämnda en minneskostnad) som du höjer när hårdvaran förbättras, och var och en lagrar ett unikt slumpmässigt salt i sin utdata.

En hash är inte heller en signatur. För att bevisa att ett meddelande kom från någon som håller en nyckel, använd HMAC-SHA-256 i stället för att hasha hemligheten och meddelandet hopklistrade, vilket är sårbart för längdförlängning.

Vad en hash inte kan göra

En hash kan inte vändas genom beräkning. Den kan vändas genom sökning så snart mängden möjliga indata är liten nog att räkna upp: hasha varje fyrsiffrig PIN-kod och du har alla tiotusen digester på ett ögonblick, och detsamma gäller varje postnummer, telefonnummer eller adress i en läckt lista. Att hasha en identifierare anonymiserar den inte.

Saltning, ett unikt slumpmässigt värde som lagras med varje post och blandas in i indata, gör inte ett enskilt mål svårare att gissa, men det tvingar en angripare att angripa varje post för sig och gör förberäknade tabeller värdelösa. För värden med låg entropi är en peppar (en hemlig nyckel som hålls utanför databasen) den del som verkligen hjälper.

Att känna igen en okänd sträng

FormKänneteckenExempel
HexadecimalBara 0-9a-f, jämn längd. 32 tecken är MD5, 40 är SHA-1, 64 är SHA-2565d41402abc4b2a76b9719d911017c592
Base64Blandat skiftläge med + och /, längd delbar med 4, kan sluta på = eller ==SGVsbG8sIHdvcmxkIQ==
Base64urlDetsamma fast med - och _, oftast utan utfyllnadeyJzdWIiOiIxMjM0NSJ9
Procentkodad% följt av två hexadecimala siffrora%20b%26c
JWTTre Base64url-block åtskilda av punkter, med början eyJeyJhbGciOi...
bcryptExakt 60 tecken, med början $2a$, $2b$ eller $2y$ och en kostnad$2b$12$...
Argon2$argon2id$v=19$m=...,t=...,p=...$salt$hash

eyJ är värt att lära sig utantill: det är Base64 av {", så varje block som börjar så är ett kodat JSON-objekt, token eller inte.

Att avkodningen lyckas är inget bevis för att gissningen var rätt, eftersom Base64 gör bytes av vilken indata som helst. Kontrollera att resultatet är rimlig text, eller att det börjar med en känd filsignatur: Base64 som börjar med iVBORw0KGgo är en PNG, /9j/ en JPEG, JVBERi0 en PDF och UEsDB en zip. Om byten inte ser ut som något alls, kör utdata genom verktyget Text to Binary för att läsa koderna direkt, eller kontrollera om värdet var procentkodat innan det Base64-kodades, en vanlig dubbelinpackning.