Italiano

Codifica, escape e hashing

Base64 non è crittografia, btoa non è UTF-8, encodeURI non è encodeURIComponent e SHA-256 non è un hash della password. Ecco cosa fa effettivamente ciascuno di questi e gli errori specifici che derivano dal dare per scontato il contrario.

La codifica non è crittografia e non è compressione

La codifica modifica il modo in cui i dati vengono scritti. La crittografia cambia chi può leggerlo. La compressione modifica la quantità di spazio occupato. Si tratta di tre lavori diversi e base64 svolge solo il primo, male se speravi in ​​uno degli altri due.

Base64 prende tre byte alla volta e li riscrive come quattro caratteri estratti da un alfabeto di simboli 64. Quattro caratteri che trasportano tre byte indicano che l'output è sempre circa il 33% più grande dell'input, più il riempimento. Esiste perché una grande quantità di infrastrutture (intestazioni e-mail, intestazioni HTTP, valori stringa JSON, URL, attributi XML) sono state progettate per il testo e alterano o rifiutano byte arbitrari. Base64 è l'adattatore che ti consente di spingere byte attraverso una pipe a forma di testo.

Chiunque può invertirlo istantaneamente, senza chiave, perché la chiave non esiste. Se basi64 una password, hai pubblicato la password in un formato leggermente scomodo. Ciò è importante perché l'output base64 sembra criptato a un occhio umano, che è esattamente la proprietà che fa sì che le persone si fidino di esso per cose che non può fare.

Perché btoa() si interrompe e i due modi diversi in cui si interrompe

Il browser ti fornisce btoa() e atob() e sono più vecchi delle moderne API di testo. btoa è definito su "stringhe binarie": stringhe in cui ogni unità di codice è un singolo byte, da 0 a 255. Il testo non è quello.

Il primo fallimento è clamoroso. Chiama btoa("世界") e ottieni un InvalidCharacterError, perché U+4E16 non entra in un byte. I fallimenti rumorosi sono quelli positivi: li noti immediatamente e cerchi una soluzione.

Il secondo fallimento è silenzioso ed è quello che arriva alla produzione. Il carattere é è U+00E9, che sta in un byte. Quindi btoa("café") ritorna felicemente, codificando é come il singolo byte 0xE9. Ma é in UTF-8 è di due byte, 0xC3 0xA9. Il base64 che hai appena prodotto decodifica, in ogni altro sistema sulla terra, in qualcosa che non è il tuo testo. Scoprirai settimane dopo quando un nome in un database si è trasformato in un carattere sostitutivo.

La soluzione è smettere di trattare il testo come byte e convertirlo in modo esplicito. TextEncoder produce i UTF-8 byte; codificarli. TextDecoder trasforma nuovamente i byte in testo e costruendolo con { fatal: true } lo fa lanciare sequenze non valide invece di sostituire silenziosamente U+FFFD, quindi una decodifica che non può essere corretta fallisce anziché restituire un'assurdità dall'aspetto plausibile. Questa è la pipeline utilizzata da questo toolkit, motivo per cui le emoji, combinando segni e script da destra a sinistra, fanno tutto esattamente andata e ritorno.

  1. Converti testo in byte con TextEncoder: non indicizzare mai la stringa.
  2. Codifica i byte in base64.
  3. Per invertire: decodificare base64 in byte, quindi decodificare i byte come UTF-8 con fatal: true.
  4. Se il passaggio UTF-8 fallisce, il payload è binario, non testo. Mostralo come esadecimale piuttosto che fingere.

base64 contro base64url e la domanda sul riempimento

Base64 standard utilizza + e / come ultimi due simboli. Entrambi hanno significato negli URL: + può essere letto come uno spazio codificato nelle stringhe di query e / è un separatore di percorso. Quindi RFC 4648 definisce un secondo alfabeto, base64url, che sostituisce - e _. I JWT lo utilizzano, così come la maggior parte dei formati token e molte API.

L'imbottitura è l'altra variabile. Pad base64 standard con = quindi la lunghezza di output è sempre un multiplo di quattro. base64url di solito elimina il riempimento, perché = è esso stesso un carattere scomodo in URL e la lunghezza può essere recuperata aritmeticamente. Un decodificatore che insiste sul riempimento rifiuterà segmenti JWT perfettamente validi.

Consiglio pratico: il tuo decoder dovrebbe accettare entrambi gli alfabeti e tollerare la mancanza di riempimento, perché raramente controlli ciò che ti viene consegnato. Il tuo codificatore dovrebbe essere esplicito su ciò che emette, perché probabilmente al destinatario importa. L'utilità base64 qui fa esattamente questo: accetta qualsiasi cosa ragionevole e ti consente di scegliere esattamente cosa produce.

encodeURI e encodeURIComponent: la differenza in una frase

Entrambi codificano in percentuale utilizzando UTF-8. Differiscono solo per i caratteri che lasciano inalterati, e questa differenza è l'intera storia: encodeURIComponent sfugge ai delimitatori riservati, encodeURI no.

I delimitatori riservati sono i caratteri che danno a URL la sua struttura: : / ? #[]@! $ & ' ( ) * + , ; =. encodeURI presuppone che tu gli abbia consegnato un URL che è già strutturato correttamente e deve rimanere tale, quindi li preserva: non trasformerà https:// in https%3A%2F%2F. encodeURIComponent presuppone che tu gli abbia consegnato un pezzo che verrà rilasciato in uno slot, quindi sfugge a loro, assicurandosi che il pezzo non possa uscire dal suo slot.

Il bug che questo produce è completamente meccanico. Prendi un valore di ricerca di a&b=c. Codificalo con encodeURI e aggiungilo come ?q=a&b=c, e avrai creato silenziosamente due parametri: q ora è solo "a" ed è apparso un b=c vagante. Codificalo con encodeURIComponent e otterrai ?q=a%26b%3Dc, un parametro, valore corretto. La stessa classe di bug consente a un valore creato di inserire parametri in un URL creato dal tuo codice, motivo per cui "usa il modulo del componente per i valori" è una regola di sicurezza, non solo di correttezza.

La codifica dei moduli è una terza regola che assomiglia alla seconda. application/x-www-form-urlencoded scrive uno spazio come + anziché %20. Se decodifichi il corpo di un modulo con il semplice decodeURIComponent, ogni segno più nei dati diventa uno spazio. Ogni casella di ricerca che abbia mai trasformato "C++" in "C" è un bug.

HTML entità e perché decodificarle con innerHTML è una cattiva abitudine

L'escape per HTML è ristretto e ben compreso: & diventa &amp;, < diventa <, > diventa > e anche i valori degli attributi interni " e ' devono essere sottoposti a escape. Cinque caratteri. L'escape di più caratteri, ovvero trasformare ogni lettera accentata in un'entità con nome, era una soluzione alternativa ai tempi delle codifiche dei caratteri incerte, e ora è piuttosto uno stile opzionale che sicurezza.

La decodifica è il luogo in cui vive la cattiva abitudine. Il trucco di una riga che appare in ogni risposta è assegnare la stringa all'innerHTML di un elemento separato e rileggere il suo textContent. Funziona ed è una pessima idea. Hai consegnato input non attendibili al parser HTML, che crea nodi DOM reali da esso. Un <img src=x onerror=...> in quella stringa diventa un elemento immagine effettivo con un gestore di errori effettivo allegato; se quella sottostruttura viene mai inserita nel documento, viene eseguita. Inoltre distrugge silenziosamente i tuoi dati: i tag nell'input svaniscono invece di andare avanti e indietro, perché il parser li interpreta come markup anziché come testo.

La decodifica corretta delle entità non richiede alcun parser: abbina il riferimento, cerca il nome in una tabella o esegui l'aritmetica per un riferimento numerico. Si tratta di poche dozzine di righe, non può eseguire nulla e va avanti e indietro fedelmente. Questo toolkit funziona in questo modo, motivo per cui incollare un tag di script nel decodificatore di entità mostra un tag di script.

La scelta di un hash e le tre domande che lo decidono

Un hash crittografico trasforma qualsiasi input in un digest di lunghezza fissa, in modo tale che trovare due input con lo stesso digest dovrebbe essere impossibile. Questa proprietà è ciò che consente a un digest di sostituire i dati: in una firma, in un controllo di integrità o in un indirizzo di contenuto.

Prima domanda: stai proteggendo dagli incidenti o da un avversario? Un checksum che protegge da un download danneggiato deve solo rilevare lanci casuali; CRC32 va bene. Un digest da cui un utente malintenzionato potrebbe trarre vantaggio dalla collisione necessita di un hash ancora in piedi. Questa distinzione è il motivo per cui SHA-1 non è semplicemente "vecchio".

SHA-1 è rotto, concretamente. In 2017 il lavoro SHAttered ha prodotto due diversi file PDF con lo stesso digest SHA-1. In 2020, "SHA-1 is a Shambles" ha dimostrato una collisione con prefisso scelto: la variante più forte e molto più pericolosa, perché consente a un utente malintenzionato di scontrare due documenti significativamente diversi anziché due blob attentamente costruiti. Se la sicurezza di un sistema si basa sulla SHA-1 resistenza alle collisioni, quella sicurezza scompare. SHA-1 rimane in questo toolkit perché gli ID oggetto git e una lunga coda di firme legacy API lo utilizzano ancora e devi essere in grado di riprodurre tali valori. Riprodurre un valore non è la stessa cosa che affidarsi ad esso.

Seconda domanda: l'input è una password? Se è così, nessuna di queste è la risposta. SHA-256 è progettato per essere veloce, e veloce è proprio sbagliato per le password: significa che un utente malintenzionato con il tuo database può provare miliardi di tentativi al secondo. Le password necessitano di una funzione deliberatamente lenta e che richiede molta memoria con un salt per utente: Argon2id, scrypt o bcrypt. Questa non è una sfumatura; l'utilizzo di SHA-256 per le password è l'errore di hashing grave più comune.

Terza domanda: hai bisogno di un digest con chiave? Se stai autenticando un messaggio anziché rilevandone l'impronta digitale, vuoi HMAC, non un semplice hash. Concatenare un segreto e sottoporlo ad hashing è un classico autogol contro gli attacchi di estensione della lunghezza; HMAC esiste perché quella costruzione è più difficile da realizzare correttamente di quanto sembri.

Per tutto il resto (impronta digitale di un file, indirizzo di contenuto, attributo di integrità) SHA-256 è l'impostazione predefinita sensata e SHA-512 è spesso più veloce su hardware 64-bit fornendo un digest più ampio.

Perché gli hash qui provengono dal browser

I digest in questo toolkit sono calcolati da SubtleCrypto, l'implementazione Web Crypto del browser, non da JavaScript spedito da questo sito. Si tratta di una scelta deliberata: l’implementazione del browser viene controllata, mantenuta e solitamente eseguita come codice nativo ottimizzato. Un SHA-256 scritto a mano in un bundle di pagine è un ulteriore codice di cui fidarsi senza alcun vantaggio.

Ha una conseguenza visibile. Web Crypto viene esposto solo in un contesto sicuro, ovvero https:// o localhost. Apri questa pagina su HTTP su un indirizzo LAN e crypto.subtle non sarà definito, quindi l'utilità hash te lo dirà chiaramente anziché fallire silenziosamente o sostituire qualcosa di più debole.

Lo stesso ragionamento guida il generatore UUID. crypto.randomUUID() è anche solo contesto sicuro, quindi quando non è disponibile il toolkit ricorre a crypto.getRandomValues() — che è sempre la stessa fonte crittograficamente sicura — e imposta personalmente la versione e i bit della variante. Ciò che non farà mai è ricorrere a Math.random(). Si tratta di un veloce PRNG non crittografico il cui stato interno può essere recuperato da una breve esecuzione dei suoi output e gli identificatori hanno la sfortunata abitudine di essere promossi in chiavi di sessione e collegamenti di reimpostazione della password. Se non esiste una fonte sicura, questo strumento non genera nulla e spiega il motivo.

Cosa succede a ciò che incolli

  • Ogni conversione, hash, decodifica e differenza viene eseguita nella scheda del tuo browser. Nessun input viene caricato, registrato o archiviato su un server, perché non è coinvolto alcun server una volta caricata la pagina.
  • Gli hash provengono dall'implementazione Web Crypto del browser e gli UUID dal suo generatore casuale crittograficamente sicuro. Nessuno dei due prevede una chiamata di rete.
  • Niente di ciò che digiti viene scritto nella memoria locale o in un cookie. Ricaricando la pagina la si elimina; chiudendo la scheda la si elimina.
  • L'analisi a livello di sito viene eseguita solo sull'host di produzione canonico configurato e viene divulgata nell'Informativa sulla privacy; gli host locali e di anteprima lo rifiutano. Valori, token, URL e contenuti dei file incollati sono esclusi dagli eventi di analisi di ToolAcre. La pubblicità è disabilitata nella configurazione attuale.
  • Detto questo: una chiave JWT o API è una credenziale attiva. L'abitudine più sicura è non incollarne mai uno in una pagina web che non hai scritto tu, per quanto attendibili siano le sue affermazioni, inclusa questa.

Domande

Base64 è un modo per nascondere i dati?

No. Si tratta di una rappresentazione testuale reversibile senza chiave, decodificabile da chiunque in una frazione di secondo. Fa sì che i dati sopravvivano ai canali di solo testo; non lo rende segreto. Tutto ciò che è veramente sensibile necessita di crittografia e il risultato crittografato è spesso codificato in base64 per il trasporto, il che è la fonte della confusione.

Perché il mio base64 è più lungo dell'input?

Poiché quattro caratteri di output trasportano tre byte di input, l'output ha all'incirca 4/3 la dimensione, più un massimo di due caratteri di riempimento. Questo è inerente al formato. Se le dimensioni contano, comprimi prima della codifica, mai dopo, perché l'output base64 si comprime scarsamente.

Quale funzione di codifica URL dovrei usare?

Utilizza encodeURIComponent per ogni singolo pezzo che stai inserendo in URL: un valore di query, un segmento di percorso, un frammento. Utilizza encodeURI solo quando hai un file URL intero e già strutturato che contiene semplicemente spazi o nonASCII. Se stai creando una stringa di query, preferisci URLSearchParams, che applica la regola corretta per te e gestisce la differenza di spazio come più.

Perché il mio decoder restituisce "URI malformato"?

Perché una % nell'input non è seguita da due cifre esadecimali. Di solito il testo contiene un segno di percentuale letterale — "50% off" — che non è mai stato codificato. Una percentuale letterale deve essere scritta come %25. L'utilità URL qui riporta la posizione esatta della fuga incriminata invece di limitarsi a rifiutare.

Posso utilizzare SHA-256 per memorizzare le password?

No. SHA-256 è veloce per definizione, il che significa che un utente malintenzionato che ruba il tuo database può testare miliardi di password candidate al secondo su hardware comune. Le password necessitano di una funzione lenta, con molta memoria e salata: Argon2id, scrypt o bcrypt. Questo è l’errore grave più comune in questo settore.

Perché SHA-1 è ancora qui se è rotto?

Perché devi ancora riprodurre i valori SHA-1 che già esistono: ID oggetto git, vecchie impronte digitali del certificato TLS, firme di richiesta legacy API. Essere in grado di calcolare un valore per l'interoperabilità è diverso dal fare affidamento su di esso per la sicurezza. Ogni luogo che SHA-1 appare in questo toolkit è etichettato di conseguenza.

Perché due strumenti forniscono hash diversi per lo stesso testo?

Quasi sempre una differenza nei byte, non nell'algoritmo. I soliti colpevoli sono un fine riga finale (un file termina con uno; una casella di testo potrebbe non esserlo), una diversa codifica del testo o CRLF rispetto alle terminazioni di riga LF. Questo strumento esegue l'hashing dei UTF-8 byte esattamente di ciò che hai digitato e ti mostra il conteggio dei byte, il che di solito rende evidente la discrepanza.

Limitazioni

  • La tabella di entità denominata HTML copre il sottoinsieme pratico: caratteri critici per il markup, tipografia, valuta, frecce, matematica, greco e latino-1 - non tutti i riferimenti denominati 2,231 HTML5. I nomi non riconosciuti vengono segnalati e lasciati esattamente come scritti anziché indovinati.
  • La decodifica dell'entità richiede il punto e virgola finale. HTML5 tollera una manciata di riferimenti legacy senza uno, ma decodificarli correttamente dipende dal contesto di markup circostante, che uno strumento di testo autonomo non ha.
  • L'hashing e la generazione di UUID necessitano di un contesto sicuro (https:// o localhost) perché Web Crypto non è esposto in altro modo. Lo strumento segnala questo invece di sostituire un'implementazione più debole.
  • Sono disponibili solo SHA-1, SHA-256, SHA-384 e SHA-512, perché questi sono ciò che SubtleCrypto implementa. MD5 è assente per scelta oltre che per necessità.
  • Non c'è HMAC, nessuna derivazione di chiave e nessuna crittografia qui. Questi necessitano della gestione delle chiavi, che non è qualcosa che una pagina trovata su Internet dovrebbe gestire.
  • Tutto è limitato dalla memoria del tuo dispositivo, poiché tutto viene eseguito in un'unica scheda del browser. Gli input sono limitati – pochi megabyte per utilità – e lo strumento rifiuta lavori di grandi dimensioni anziché bloccarsi.