Perché la codifica base64 richiede riempimento se la lunghezza dell'input non è divisibile per 3?


100

Qual è lo scopo del riempimento nella codifica base64. Quello che segue è l'estratto da wikipedia:

"Viene assegnato un carattere di riempimento aggiuntivo che può essere utilizzato per forzare l'output codificato in un multiplo intero di 4 caratteri (o equivalentemente quando il testo binario non codificato non è un multiplo di 3 byte); questi caratteri di riempimento devono quindi essere scartati durante la decodifica ma consentire comunque il calcolo della lunghezza effettiva del testo non codificato, quando la sua lunghezza binaria di input non sarebbe un multiplo di 3 byte (l'ultimo carattere non pad è normalmente codificato in modo che l'ultimo blocco di 6 bit che rappresenta sarà zero -imbottito sui suoi bit meno significativi, al massimo due caratteri pad possono essere presenti alla fine del flusso codificato). "

Ho scritto un programma che potrebbe codificare in base64 qualsiasi stringa e decodificare qualsiasi stringa codificata in base64. Quale problema risolve l'imbottitura?

Risposte:


208

La tua conclusione che l'imbottitura non è necessaria è giusta. È sempre possibile determinare la lunghezza dell'input in modo univoco dalla lunghezza della sequenza codificata.

Tuttavia, il padding è utile nelle situazioni in cui le stringhe codificate in base64 sono concatenate in modo tale da perdere le lunghezze delle singole sequenze, come potrebbe accadere, ad esempio, in un protocollo di rete molto semplice.

Se vengono concatenate stringhe non riempite , è impossibile recuperare i dati originali perché le informazioni sul numero di byte dispari alla fine di ogni singola sequenza vengono perse. Tuttavia, se vengono utilizzate sequenze riempite, non c'è ambiguità e la sequenza nel suo insieme può essere decodificata correttamente.

Modifica: un'illustrazione

Supponiamo di avere un programma che codifica le parole in base64, le concatena e le invia su una rete. Codifica "I", "AM" e "TJM", raggruppa i risultati senza riempimento e li trasmette.

  • Icodifica in SQ( SQ==con imbottitura)
  • AMcodifica in QU0( QU0=con imbottitura)
  • TJMcodifica in VEpN( VEpNcon imbottitura)

Quindi i dati trasmessi sono SQQU0VEpN. Il ricevitore base64 decodifica questo come I\x04\x14\xd1Q)invece del previsto IAMTJM. Il risultato è un'assurdità perché il mittente ha distrutto le informazioni su dove finisce ogni parola nella sequenza codificata. Se invece il mittente avesse inviato SQ==QU0=VEpN, il destinatario avrebbe potuto decodificarlo come tre sequenze base64 separate che si sarebbero concatenate per dare IAMTJM.

Perché preoccuparsi dell'imbottitura?

Perché non progettare semplicemente il protocollo per anteporre a ciascuna parola una lunghezza intera? Quindi il ricevitore potrebbe decodificare correttamente il flusso e non ci sarebbe bisogno di riempimento.

È un'ottima idea, purché conosciamo la lunghezza dei dati che stiamo codificando prima di iniziare a codificarli. Ma cosa succederebbe se, invece delle parole, codificassimo porzioni di video da una telecamera live? Potremmo non conoscere la lunghezza di ogni blocco in anticipo.

Se il protocollo usasse il riempimento, non sarebbe affatto necessario trasmettere una lunghezza. I dati potrebbero essere codificati non appena provenivano dalla telecamera, ogni blocco sarebbe terminato con un riempimento e il ricevitore sarebbe in grado di decodificare correttamente il flusso.

Ovviamente questo è un esempio molto artificioso, ma forse illustra perché l'imbottitura potrebbe essere utile in alcune situazioni.


22
+1 L'unica risposta che fornisce effettivamente una risposta ragionevole oltre a "perché ci piacciono la verbosità e la ridondanza per qualche motivo inspiegabile".
valido il

1
Funziona bene per i blocchi codificati in modo distinto, ma ci si aspetta che vengano concatenati in modo indivisibile dopo la decodifica. Se invii U0FNSQ == QU0 =, puoi ricostruire la frase, ma perdi le parole che la compongono. Meglio di niente, immagino. In particolare, il programma GNU base64 gestisce automaticamente le codifiche concatenate.
Marcelo Cantos

2
E se la lunghezza delle parole fosse un multiplo di 3? Questo modo stupido di concatenazione distrugge le informazioni (finali di parole), non la rimozione del riempimento.
GreenScape

2
La concatenazione Base64 consente agli encoder di elaborare grandi blocchi in parallelo senza l'onere di allineare le dimensioni dei blocchi a un multiplo di tre. Allo stesso modo, come dettaglio di implementazione, potrebbe esserci un codificatore là fuori che deve svuotare un buffer di dati interno di una dimensione che non è un multiplo di tre.
Andre D

1
Questa risposta potrebbe farti pensare che puoi decodificare qualcosa come "SQ == QU0 = VEpN" semplicemente dandolo a un decoder. In realtà sembra che tu non possa, ad esempio le implementazioni in javascript e php non lo supportano. A partire da una stringa concatenata, devi decodificare 4 byte alla volta o dividere la stringa dopo i caratteri di riempimento. Sembra che quelle implementazioni ignorino semplicemente i caratteri di riempimento, anche quando si trovano nel mezzo di una stringa.
Roman

38

In una nota correlata, ecco un convertitore di base per la conversione di base arbitraria che ho creato per te. Godere! https://convert.zamicol.com/

Cosa sono i caratteri di riempimento?

I caratteri di riempimento aiutano a soddisfare i requisiti di lunghezza e non hanno alcun significato.

Esempio decimale di riempimento: dato il requisito arbitrario che tutte le stringhe abbiano una lunghezza di 8 caratteri, il numero 640 può soddisfare questo requisito utilizzando gli 0 precedenti come caratteri di riempimento in quanto non hanno alcun significato, "00000640".

Codifica binaria

Il paradigma dei byte: il byte è l'unità di misura standard de facto e qualsiasi schema di codifica deve essere correlato ai byte.

Base256 si inserisce esattamente in questo paradigma. Un byte è uguale a un carattere in base256.

Base16 , esadecimale o esadecimale, utilizza 4 bit per ogni carattere. Un byte può rappresentare due caratteri base16.

Base64 non si adatta in modo uniforme al paradigma byte (né base32), a differenza di base256 e base16. Tutti i caratteri base64 possono essere rappresentati in 6 bit, 2 bit meno di un byte intero.

Possiamo rappresentare la codifica base64 rispetto al paradigma byte come una frazione: 6 bit per carattere su 8 bit per byte . Ridotta questa frazione è di 3 byte su 4 caratteri.

Questo rapporto, 3 byte per ogni 4 caratteri base64, è la regola che vogliamo seguire quando si codifica base64. La codifica Base64 può promettere solo misurazioni con bundle di 3 byte, a differenza di base16 e base256 dove ogni byte può stare da solo.

Allora perché il riempimento è incoraggiato anche se la codifica potrebbe funzionare bene senza i caratteri di riempimento?

Se la lunghezza di un flusso è sconosciuta o se può essere utile sapere esattamente quando finisce un flusso di dati, usa il riempimento. I caratteri di riempimento comunicano esplicitamente che quei punti extra dovrebbero essere vuoti ed escludono qualsiasi ambiguità. Anche se la lunghezza è sconosciuta con il riempimento, saprai dove finisce il tuo flusso di dati.

Come contro esempio, alcuni standard come JOSE non consentono caratteri di riempimento. In questo caso, se manca qualcosa, una firma crittografica non funzionerà o mancheranno altri caratteri non base64 (come il "."). Sebbene non siano fatte supposizioni sulla lunghezza, l'imbottitura non è necessaria perché se c'è qualcosa di sbagliato semplicemente non funzionerà.

E questo è esattamente ciò che dice la RFC base64 ,

In alcune circostanze, l'uso del riempimento ("=") nei dati con codifica di base non è richiesto o utilizzato. Nel caso generale, quando non è possibile fare ipotesi sulla dimensione dei dati trasportati, il riempimento è necessario per fornire dati decodificati corretti.

[...]

La fase di riempimento in base 64 [...] se implementata in modo improprio, porta ad alterazioni non significative dei dati codificati. Ad esempio, se l'ingresso è solo un ottetto per una codifica base 64, vengono utilizzati tutti e sei i bit del primo simbolo, ma vengono utilizzati solo i primi due bit del simbolo successivo. Questi bit di pad DEVONO essere impostati a zero da codificatori conformi, come descritto nelle descrizioni sul riempimento di seguito. Se questa proprietà non è valida, non vi è alcuna rappresentazione canonica dei dati codificati in base e più stringhe codificate in base possono essere decodificate negli stessi dati binari. Se questa proprietà (e altre discusse in questo documento) è valida, è garantita una codifica canonica.

Il riempimento ci consente di decodificare la codifica base64 con la promessa di non perdere bit. Senza riempimento non c'è più il riconoscimento esplicito della misurazione in fasci di tre byte. Senza riempimento potresti non essere in grado di garantire la riproduzione esatta della codifica originale senza informazioni aggiuntive di solito da qualche altra parte nello stack, come TCP, checksum o altri metodi.

Esempi

Ecco il modulo di esempio RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

Ogni carattere all'interno della funzione "BASE64" utilizza un byte (base256). Lo traduciamo quindi in base64.

BASE64("")       = ""           (No bytes used. 0%3=0.)
BASE64("f")      = "Zg=="       (One byte used. 1%3=1.)
BASE64("fo")     = "Zm8="       (Two bytes. 2%3=2.)
BASE64("foo")    = "Zm9v"       (Three bytes. 3%3=0.)
BASE64("foob")   = "Zm9vYg=="   (Four bytes. 4%3=1.)
BASE64("fooba")  = "Zm9vYmE="   (Five bytes. 5%3=2.)
BASE64("foobar") = "Zm9vYmFy"   (Six bytes. 6%3=0.)

Ecco un codificatore con cui puoi giocare: http://www.motobit.com/util/base64-decoder-encoder.asp


16
-1 È un post carino e completo su come funzionano i sistemi numerici, ma non spiega perché il riempimento viene utilizzato quando la codifica funzionerebbe perfettamente senza.
Matti Virkkunen

2
Hai letto la domanda? Non è necessario il riempimento per decodificare correttamente.
Navin

3
Penso che questa risposta abbia effettivamente spiegato il motivo come affermato qui: "non possiamo più garantire la riproduzione esatta della codifica originale senza informazioni aggiuntive". È davvero semplice, l'imbottitura ci fa sapere che abbiamo ricevuto la codifica completa. Ogni volta che hai 3 byte, puoi tranquillamente presumere che sia giusto andare avanti e decodificarlo, non ti preoccupare, canticchia ... forse un altro byte sta per arrivare forse cambiando la codifica.
Didier A.

@DidierA. Come fai a sapere che non ci sono altri 3 byte in una sottostringa base64? Per decodificare a char*, è necessaria la dimensione della stringa o un terminatore nullo. L'imbottitura è ridondante. Quindi, la domanda di OP.
Navin

4
@Navin Se stai decodificando il flusso in base64 byte, non conosci la lunghezza, con il riempimento di 3 byte, sai che ogni volta che hai 3 byte puoi elaborare i 4 caratteri, fino a raggiungere la fine del flusso. Senza di esso, potresti dover tornare indietro, perché il byte successivo potrebbe far cambiare il carattere precedente, quindi puoi essere sicuro di averlo decodificato correttamente solo una volta raggiunta la fine del flusso. Quindi, non è molto utile, ma ha alcuni casi limite in cui potresti volerlo.
Didier A.

1

Non ci sono molti vantaggi al giorno d'oggi. Quindi consideriamo questo come una questione di quale potrebbe essere stato lo scopo storico originale .

La codifica Base64 fa la sua prima comparsa nella RFC 1421 del 1993. Questa RFC è in realtà focalizzata sulla crittografia della posta elettronica, e base64 è descritta in una piccola sezione 4.3.2.4 .

Questo RFC non spiega lo scopo del riempimento. Il più vicino che abbiamo a una menzione dello scopo originale è questa frase:

Un quanto di codifica completo viene sempre completato alla fine di un messaggio.

Non suggerisce la concatenazione (risposta principale qui), né la facilità di implementazione come scopo esplicito per il riempimento. Tuttavia, considerando l'intera descrizione, non è irragionevole presumere che ciò possa essere stato inteso per aiutare il decodificatore a leggere l'ingresso in unità a 32 bit ( "quanti" ). Questo non è di alcun beneficio oggi, tuttavia nel 1993 un codice C non sicuro avrebbe molto probabilmente sfruttato questa proprietà.


1
In assenza di riempimento, un tentativo di concatenare due stringhe quando la lunghezza della prima stringa non è un multiplo di tre produrrebbe spesso una stringa apparentemente valida, ma il contenuto della seconda stringa verrebbe decodificato in modo errato. L'aggiunta dell'imbottitura garantisce che non si verifichi.
supercat

1
@supercat Se questo fosse l'obiettivo, non sarebbe più facile terminare ogni stringa base64 con un solo "="? La lunghezza media sarebbe più breve e impedirebbe comunque concatenazioni errate.
Roman Starkov

2
La durata media di b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v' è la stessa di quella di b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.