¿Por qué la codificación base64 requiere relleno si la longitud de entrada no es divisible por 3?

101

¿Cuál es el propósito del relleno en la codificación base64? El siguiente es el extracto de wikipedia:

"Se asigna un carácter de relleno adicional que se puede utilizar para forzar la salida codificada en un múltiplo entero de 4 caracteres (o de manera equivalente cuando el texto binario no codificado no es un múltiplo de 3 bytes); estos caracteres de relleno deben descartarse al decodificar pero todavía permiten el cálculo de la longitud efectiva del texto no codificado, cuando su longitud binaria de entrada no sería un múltiplo de 3 bytes (el último carácter no pad se codifica normalmente de modo que el último bloque de 6 bits que representa sea cero -agregado en sus bits menos significativos, como máximo pueden aparecer dos caracteres de relleno al final de la secuencia codificada).

Escribí un programa que podía codificar en base64 cualquier cadena y decodificar cualquier cadena codificada en base64. ¿Qué problema resuelve el acolchado?

Anand Patel
fuente

Respuestas:

210

Su conclusión de que el relleno es innecesario es correcta. Siempre es posible determinar la longitud de la entrada sin ambigüedades a partir de la longitud de la secuencia codificada.

Sin embargo, el relleno es útil en situaciones donde las cadenas codificadas en base64 se concatenan de tal manera que se pierden las longitudes de las secuencias individuales, como podría suceder, por ejemplo, en un protocolo de red muy simple.

Si se concatenan cadenas sin relleno, es imposible recuperar los datos originales porque se pierde información sobre el número de bytes impares al final de cada secuencia individual. Sin embargo, si se utilizan secuencias rellenas, no hay ambigüedad y la secuencia en su conjunto se puede decodificar correctamente.

Editar: una ilustración

Supongamos que tenemos un programa que codifica palabras en base64, las concatena y las envía a través de una red. Codifica "I", "AM" y "TJM", empareja los resultados sin rellenar y los transmite.

  • Icodifica a SQ( SQ==con relleno)
  • AMcodifica a QU0( QU0=con relleno)
  • TJMcodifica a VEpN( VEpNcon relleno)

Entonces los datos transmitidos son SQQU0VEpN. El receptor base64-decodifica esto como en I\x04\x14\xd1Q)lugar de lo previsto IAMTJM. El resultado es una tontería porque el remitente ha destruido información sobre dónde termina cada palabra en la secuencia codificada. Si el remitente hubiera enviado en su SQ==QU0=VEpNlugar, el receptor podría haber decodificado esto como tres secuencias base64 separadas que se concatenarían para dar IAMTJM.

¿Por qué molestarse con el acolchado?

¿Por qué no diseñar el protocolo para prefijar cada palabra con una longitud entera? Entonces, el receptor podría decodificar la secuencia correctamente y no habría necesidad de relleno.

Es una gran idea, siempre que sepamos la longitud de los datos que estamos codificando antes de empezar a codificarlos. Pero, ¿y si, en lugar de palabras, codificamos fragmentos de video de una cámara en vivo? Es posible que no sepamos la longitud de cada fragmento de antemano.

Si el protocolo usara relleno, no habría necesidad de transmitir una longitud en absoluto. Los datos podrían codificarse a medida que ingresaban desde la cámara, cada fragmento terminaba con relleno y el receptor podría decodificar la transmisión correctamente.

Obviamente, ese es un ejemplo muy elaborado, pero tal vez ilustra por qué el relleno podría ser útil en algunas situaciones.

TJM
fuente
22
+1 La única respuesta que realmente proporciona una respuesta razonable además de "porque nos gusta la verbosidad y la redundancia por alguna razón inexplicable".
válido
1
Esto funciona bien para fragmentos que están codificados de forma distinta, pero se espera que se concatenen indivisiblemente después de la decodificación. Si envía U0FNSQ == QU0 =, puede reconstruir la oración, pero pierde las palabras que la componen. Mejor que nada, supongo. En particular, el programa GNU base64 maneja automáticamente codificaciones concatenadas.
Marcelo Cantos
2
¿Y si la longitud de las palabras fuera múltiplo de 3? Esta forma tonta de concatenación destruye información (terminaciones de palabras), no la eliminación del relleno.
GreenScape
2
La concatenación de Base64 permite a los codificadores procesar grandes fragmentos en paralelo sin la carga de alinear los tamaños de los fragmentos a un múltiplo de tres. De manera similar, como detalle de implementación, puede haber un codificador que necesite vaciar un búfer de datos interno de un tamaño que no sea múltiplo de tres.
Andre D
1
Esta respuesta podría hacerle pensar que puede decodificar algo como "SQ == QU0 = VEpN" con solo dárselo a un decodificador. En realidad, parece que no puede, por ejemplo, las implementaciones en javascript y php no admiten esto. Comenzando con una cadena concatenada, debe decodificar 4 bytes a la vez o dividir la cadena después de rellenar caracteres. Parece que esas implementaciones simplemente ignoran los caracteres de relleno, incluso cuando están en medio de una cadena.
Romano
38

En una nota relacionada, aquí hay un convertidor de base para la conversión de base arbitraria que creé para usted. ¡Disfrutar! https://convert.zamicol.com/

¿Qué son los caracteres de relleno?

Los caracteres de relleno ayudan a satisfacer los requisitos de longitud y no tienen significado.

Ejemplo decimal de relleno: dado el requisito arbitrario de que todas las cadenas tengan 8 caracteres de longitud, el número 640 puede cumplir con este requisito utilizando ceros anteriores como caracteres de relleno, ya que no tienen significado, "00000640".

Codificación binaria

El paradigma del byte: el byte es la unidad de medida estándar de facto y cualquier esquema de codificación debe relacionarse con los bytes.

Base256 encaja exactamente en este paradigma. Un byte es igual a un carácter en base256.

Base16 , hexadecimal o hexadecimal, utiliza 4 bits para cada carácter. Un byte puede representar dos caracteres base16.

Base64 no encaja uniformemente en el paradigma de bytes (ni base32), a diferencia de base256 y base16. Todos los caracteres base64 se pueden representar en 6 bits, 2 bits menos que un byte completo.

Podemos representar la codificación base64 frente al paradigma de bytes como una fracción: 6 bits por carácter sobre 8 bits por byte . Reducida esta fracción es de 3 bytes sobre 4 caracteres.

Esta proporción, 3 bytes por cada 4 caracteres base64, es la regla que queremos seguir al codificar base64. La codificación Base64 solo puede prometer una medición uniforme con paquetes de 3 bytes, a diferencia de base16 y base256, donde cada byte puede ser independiente.

Entonces, ¿ por qué se recomienda el relleno a pesar de que la codificación podría funcionar bien sin los caracteres de relleno?

Si se desconoce la longitud de una secuencia o si podría ser útil saber exactamente cuándo termina una secuencia de datos, utilice el relleno. Los caracteres de relleno comunican explícitamente que esos espacios adicionales deben estar vacíos y descarta cualquier ambigüedad. Incluso si se desconoce la longitud con el relleno, sabrá dónde termina su flujo de datos.

Como contraejemplo, algunos estándares como JOSE no permiten caracteres de relleno. En este caso, si falta algo, una firma criptográfica no funcionará o faltarán otros caracteres que no sean base64 (como el "."). Aunque no se hacen suposiciones sobre la longitud, el relleno no es necesario porque si hay algo mal, simplemente no funcionará.

Y esto es exactamente lo que dice el RFC base64 ,

En algunas circunstancias, no se requiere ni se utiliza el uso de relleno ("=") en datos codificados en base. En el caso general, cuando no se pueden hacer suposiciones sobre el tamaño de los datos transportados, se requiere el relleno para producir datos decodificados correctos.

[...]

El paso de relleno en base 64 [...] si se implementa incorrectamente, conduce a alteraciones no significativas de los datos codificados. Por ejemplo, si la entrada es solo un octeto para una codificación base 64, entonces se usan los seis bits del primer símbolo, pero solo se usan los primeros dos bits del siguiente símbolo. Estos bits de relleno DEBEN ponerse a cero mediante codificadores conformes, que se describen en las descripciones de relleno a continuación. Si esta propiedad no se cumple, no existe una representación canónica de datos codificados en base y se pueden decodificar múltiples cadenas codificadas en base a los mismos datos binarios. Si esta propiedad (y otras discutidas en este documento) se mantiene, se garantiza una codificación canónica.

El relleno nos permite decodificar la codificación base64 con la promesa de no perder bits. Sin relleno, ya no existe el reconocimiento explícito de medir en paquetes de tres bytes. Sin relleno, es posible que no pueda garantizar la reproducción exacta de la codificación original sin información adicional, generalmente de algún otro lugar de su pila, como TCP, sumas de comprobación u otros métodos.

Ejemplos

Aquí está el formulario de ejemplo RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

Cada carácter dentro de la función "BASE64" usa un byte (base256). Luego traducimos eso a 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.)

Aquí hay un codificador con el que puede jugar: http://www.motobit.com/util/base64-decoder-encoder.asp

Zamicol
fuente
16
-1 Es una publicación agradable y completa sobre cómo funcionan los sistemas numéricos, pero no explica por qué se usa el relleno cuando la codificación funcionaría perfectamente sin él.
Matti Virkkunen
2
¿Leíste siquiera la pregunta? No necesita relleno para decodificar correctamente.
Navin
3
Creo que esta respuesta de hecho explicó la razón como se indica aquí: "ya no podemos garantizar la reproducción exacta de la codificación original sin información adicional". Realmente es simple, el relleno nos permite saber que recibimos la codificación completa. Cada vez que tenga 3 bytes, puede asumir con seguridad que está bien seguir adelante y decodificarlo, no se preocupe, zumbido ... tal vez un byte más vendrá posiblemente cambiando la codificación.
Didier A.
@DidierA. ¿Cómo sabe que no hay 3 bytes más en una subcadena base64? Para decodificar un char*, necesita el tamaño de la cadena o un terminador nulo. El relleno es redundante. De ahí la pregunta de OP.
Navin
4
@Navin Si está decodificando un flujo de base64 bytes, no sabe la longitud, con el relleno de 3 bytes, sabe que cada vez que obtiene 3 bytes puede procesar los 4 caracteres, hasta llegar al final del flujo. Sin él, es posible que deba retroceder, porque el siguiente byte podría hacer que el carácter anterior cambie, por lo que solo puede estar seguro de haberlo decodificado correctamente una vez que haya llegado al final de la secuencia. Por lo tanto, no es muy útil, pero tiene algunos casos extremos en los que es posible que lo desee.
Didier A.
1

No tiene mucho beneficio en la actualidad. Así que veamos esto como una cuestión de cuál pudo haber sido el propósito histórico original .

La codificación Base64 hace su primera aparición en RFC 1421 con fecha de 1993. Esta RFC en realidad se centra en el cifrado de correo electrónico, y base64 se describe en una pequeña sección 4.3.2.4 .

Este RFC no explica el propósito del relleno. Lo más cercano que tenemos a una mención del propósito original es esta oración:

Un cuanto de codificación completo siempre se completa al final de un mensaje.

No sugiere la concatenación (respuesta superior aquí), ni la facilidad de implementación como un propósito explícito para el relleno. Sin embargo, considerando la descripción completa, no es descabellado suponer que esto puede haber tenido la intención de ayudar al decodificador a leer la entrada en unidades de 32 bits ( "cuantos" ). Eso no tiene ningún beneficio hoy en día, sin embargo, en 1993, el código C inseguro probablemente se habría aprovechado de esta propiedad.

Roman Starkov
fuente
1
En ausencia de relleno, un intento de concatenar dos cadenas cuando la longitud de la primera cadena no es un múltiplo de tres a menudo produciría una cadena aparentemente válida, pero el contenido de la segunda cadena se decodificaría incorrectamente. Agregar el relleno asegura que no ocurra.
supercat
1
@supercat Si ese fuera el objetivo, ¿no sería más fácil terminar cada cadena base64 con un solo "="? La longitud promedio sería más corta y aún evitaría concatenaciones erróneas.
Roman Starkov
2
La duración promedio de b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v' es la misma que la de b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott