2 puntos por GN⁺ 2023-10-24 | 1 comentarios | Compartir por WhatsApp
  • La codificación Base64 convierte datos binarios en texto ASCII, reduciendo la posibilidad de que los datos se interpreten mal durante el almacenamiento o la transmisión
  • Como no es cifrado sino un cambio en la forma de representación, los datos codificados pueden volver fácilmente al texto o archivo original
  • Como 64 caracteres pueden representarse con 6 bits, cada carácter de Base64 contiene 6 bits de datos, y 3 bytes (24 bits) se convierten en cuatro caracteres Base64
  • Es útil en entornos como las Data URLs de HTML, la transmisión binaria en correos electrónicos y redes o URLs centradas en texto donde el binario crudo puede causar problemas
  • Varios lenguajes y herramientas, como Ruby, C#, PHP, JavaScript y el comando base64 en la terminal, ofrecen funciones de codificación y decodificación

Qué cambia Base64

  • La codificación Base64 transforma datos binarios en texto, más específicamente en texto ASCII
  • El resultado usa solo los 64 caracteres siguientes
    • A-Z
    • a-z
    • 0-9
    • +
    • /
  • Este conjunto se usa como un conjunto de caracteres seguro para evitar situaciones donde caracteres como <, >, \n puedan interpretarse mal en computadoras o programas antiguos
  • Si codificas "Ruby on Rails" en Base64, el resultado es UnVieSBvbiBSYWlscw==
  • Base64 no es cifrado
    • Los datos codificados pueden revertirse fácilmente al texto original
    • No oculta los datos; solo cambia su representación

Cuándo se usa Base64

  • Las Data URLs permiten insertar directamente datos de archivos como imágenes dentro de HTML, y para eso usan texto codificado en Base64
  • El formato de ejemplo es data:[<mime type>][;charset=<charset>][;base64],<encoded data>
  • En el correo electrónico, Base64 se ha usado para transportar datos binarios de forma segura incluso en entornos donde el servidor puede modificar los saltos de línea
  • Al insertar datos de imagen directamente en el código HTML, se necesita codificación para evitar que caracteres como < y > se interpreten como etiquetas
  • También puede usarse al almacenar o transmitir datos binarios a través de redes diseñadas para procesar texto o datos US-ASCII
  • Base64 también puede usarse para transmitir datos que contienen caracteres difíciles de incluir en una URL
  • Las codificaciones de la familia Base permiten manipular objetos con editores de texto, por lo que se aprovechan en muchas aplicaciones

Algoritmo de codificación

  • La codificación Base64 sigue este proceso
    • Convierte el texto a una representación binaria
    • Divide los bits en grupos de 6 bits
    • Convierte cada grupo de 6 bits en un número decimal de 0 a 63
    • Cambia ese número por el carácter correspondiente del alfabeto Base64
  • Si faltan bits en el último grupo, se puede agregar = o == como relleno
  • Para representar 64 caracteres se necesitan 6 bits
    • 2^6 = 64
    • Un valor Base64 representa 6 bits de datos
  • Un byte tiene 8 bits, y el múltiplo común más cercano entre 8 y 6 es 24
    • 24 bits equivalen a 3 bytes
    • 24 bits se representan como cuatro valores Base64 de 6 bits

Ejemplo de codificación de “Akshay”

  • Si conviertes "Akshay" a números ASCII y luego a binario, obtienes lo siguiente
    • 01000001 01101011 01110011 01101000 01100001 01111001
  • Si lo divides en grupos de 6 bits, queda así
    • 010000 010110 101101 110011 011010 000110 000101 111001
  • Al convertir cada grupo a decimal, se obtienen estos valores
    • 16 22 45 51 26 6 5 57
  • Al convertirlos al alfabeto Base64, resultan estos caracteres
    • Q W t z a G F 5
  • Por lo tanto, la representación Base64 de "Akshay" es QWtzaGF5
  • Del mismo modo, archivos como imágenes, PDF, texto o video pueden convertirse a binario y luego codificarse en Base64 para almacenarse o transmitirse como texto ASCII

Uso en lenguajes y herramientas

  • Ruby maneja la codificación y decodificación con el módulo Base64
    • Base64.encode64("Ruby on Rails")
    • Base64.decode64(encoded)
  • En C#, una cadena se convierte en un arreglo de bytes y luego se codifica con Convert.ToBase64String; para decodificar se usa System.Convert.FromBase64String
  • PHP ofrece las funciones globales base64_encode y base64_decode
  • JavaScript codifica con btoa() y decodifica con atob()
  • En la terminal también se puede codificar y decodificar con el comando base64
    • echo "akshay" | base64 imprime YWtzaGF5Cg==
    • echo "YWtzaGF5Cg==" | base64 -d imprime akshay

1 comentarios

 
GN⁺ 2023-10-24
Opiniones de Hacker News
  • Gracias por enfatizar que aquí no se está cifrando texto. Muchos desarrolladores junior se llevan golpes por aprender demasiado tarde la diferencia entre cifrado, que requiere un valor secreto para poder revertirse; hashing, que no puede revertirse; y codificación, que siempre puede revertirse fácilmente.
    También vale la pena saber que, aunque la salida parezca aleatoria, la entropía es la misma que la de la entrada. Es decir, no hay que codificar una contraseña en Base64 para hacerla más fuerte.

    • Esto es casi hilar demasiado fino y no tiene mucha relación con el punto central, pero codificar una contraseña en Base64 sí podría hacerla más fuerte. La fortaleza de una contraseña no es solo un asunto de entropía, aunque una alta entropía sea el método más efectivo.
      Si la contraseña se generó de forma completamente aleatoria, codificarla en Base64 no tiene ningún efecto. Pero si la contraseña se creó con un esquema de baja entropía, como palabras de diccionario o reglas fáciles de recordar, entonces el atacante tendría que configurar un cracker de contraseñas inteligente para que también considere la regla de codificación en Base64, lo que agrega más o menos una operación adicional por intento.
      Claro, no deberías usar ese tipo de esquema de contraseñas. Creo que una contraseña del estilo “correct horse battery staple” es suficiente.
    • Creo que cualquiera que haya estudiado ciencias de la computación conoce estas diferencias. También son conceptos que alguien interesado en programar puede aprender en una tarde.
    • Algo relacionado que siempre vale la pena enfatizar es que un hash no necesariamente es criptográficamente seguro.
      El hashing tiene muchos objetivos además de la seguridad, por eso también hay muchas bibliotecas de hash. Si vas a usar un hash para seguridad o criptografía, debes usar uno diseñado para ese propósito. Los hashes CRC son rápidos, pero no son una buena opción para contraseñas de usuarios.
  • Algo interesante de Base64 es que, si partes de cualquier cadena y repites la codificación, el inicio del resultado converge cada vez más hacia un punto fijo. También se puede comprobar con Bash.
    Lo descubrí por casualidad hace más de 10 años y lo tuiteé como si fuera un cifrado [1]; alguien escribió una entrada de blog sobre el tema y también la publicó aquí, pero no hubo mucha discusión [2]. Cuando otra persona la publicó en Reddit /r/compsci, allí sí hubo una discusión productiva que corrigió la entrada del blog [3]. El blog ya no está en línea, pero queda una copia en Internet Archive [4].
    [1] https://twitter.com/p4bl0/status/298900842076045312
    [2] https://news.ycombinator.com/item?id=5181256
    [3] https://www.reddit.com/r/compsci/comments/18234a/the_base64_...
    [4] https://web.archive.org/web/20130315082932/http://fmota.eu/b...

  • Al codificar en Bash hay que usar la opción -n: $ echo -n "abcde" |base64
    Sin -n, echo agrega un carácter de salto de línea al final de la cadena, y ese carácter también se codifica.

  • También existe base64URL, que codifica usando otros caracteres ASCII seguros para URL. Algunos desarrolladores llaman simplemente base64 a BASE64URL, lo que puede causar problemas a quienes no lo saben.
    https://datatracker.ietf.org/doc/html/rfc4648#section-5

    • El problema de base64url es que ~ y . no son letras, así que al hacer doble clic sobre un valor codificado no se selecciona completo. Eso genera fricción innecesaria en muchos casos de copiar y pegar.
      La codificación Base62 (0-9A-Za-z) es casi tan eficiente como base64url y mantiene la seguridad para URL, pero facilita más copiar y pegar. Si quieres reducir la ambigüedad para lectura humana, puedes bajar a Base58, aunque normalmente, si estás usando una codificación BaseXX, la longitud ya es suficiente como para que copiar y pegar sea lo habitual, así que no es un gran problema.
      https://en.wikipedia.org/wiki/Base62
    • Base64url normalmente también omite el padding.
      Una cadena Base64 con padding siempre tiene una longitud múltiplo de 4, así que si recibes una cadena cuya longitud no es múltiplo de 4, puedes saber cuánto padding debería haber tenido originalmente y también determinar cómo decodificar los últimos 3 bytes.
      Por eso me confunde un poco por qué Base64 necesita el padding == desde el principio.
  • Cada vez que sale el tema de la conversión de bases, promociono sin vergüenza mi conversor de bases arbitrarias: https://convert.zamicol.com
    El base64 bajo “useful alphabets” es la base “natural”, que hace divisiones repetidas por la base; el método de conversión por “buckets” del RFC está bajo extras.

  • Si codificaste algo y una persona tiene que escribirlo manualmente, recomiendo https://en.wikipedia.org/wiki/Base32
    No hay nada tan molesto como confundirse entre l y 1, o entre o, O y 0, por culpa de una mala fuente.

  • Para ser un poco más rigurosos, sería más correcto decir que Base64 codifica datos binarios en un subconjunto de ASCII, no en todo el conjunto de caracteres ASCII.
    ASCII tiene 128 puntos de código; de ellos, 95 son caracteres imprimibles y 33 son caracteres de control, pero Base64 usa solo 64 de ellos, o 65 si se incluye el padding.

  • El artículo no trata en detalle el propósito del padding = / ==, ni muestra con ejemplos cómo se manejan los datos que no se dividen exactamente en grupos de 6 bits
    Creo que lo entendí más o menos, pero quiero saberlo con certeza. Me gustaría una respuesta breve y completa sobre cuándo se usa = y cuándo ==, si siempre se agregan o si hay casos en los que no, cómo se procesan exactamente los bits sobrantes de una cadena como "5byte", y qué hay que considerar al decodificar

    • Las dos preguntas están relacionadas
      Un carácter Base64 representa 6 bits, así que un bloque de datos de 3 bytes corresponde a un bloque de 4 caracteres codificados en Base64. Por eso es cómodo procesar los datos Base64 de a 4 caracteres
      = es padding que se agrega según sea necesario en cantidades de 0, 1 o 2 para que la longitud de la cadena codificada sea múltiplo de 4. Por ejemplo, "543210" queda como "543210==", "6543210" como "6543210=", y "76543210" no necesita padding. Nunca hacen falta 3 caracteres = de padding, porque incluso 1 byte de datos requiere como mínimo 2 caracteres Base64
      Los bits sobrantes se pueden rellenar con 0, y el decodificador puede descartarlos al ver que no hay bits suficientes para formar 1 byte completo. En la mayoría de los casos modernos, el padding no es estrictamente necesario, sino más bien una convención. El artículo de Wikipedia es bastante detallado: https://en.wikipedia.org/wiki/Base64
    • El padding solo es necesario al concatenar datos codificados o al hacer streaming, es decir, cuando aparecen caracteres de padding en medio del flujo codificado
      Los caracteres de padding al final de un stream, archivo o cadena se pueden inferir a partir de la longitud ya procesada, así que en sentido estricto no son indispensables
      Dicho eso, el manejo del padding es bastante sutil, y esas diferencias dieron lugar a variantes de implementación interesantes: https://eprint.iacr.org/2022/361.pdf
    • Según el artículo, un dígito Base64 representa 6 bits de datos. Un byte son 8 bits, y el múltiplo común más cercano de 8 y 6 es 24, así que 24 bits, es decir 3 bytes, pueden representarse con 4 dígitos Base64 de 6 bits
      En definitiva, se codifica en unidades de 24 bits. Cuando terminan los datos, la parte restante de esos 24 bits se rellena con =, no con A, porque A como dato significa 000000. Yo también tuve que leer todo dos veces para entenderlo
  • Mi shader codificador Base64 está aquí: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
    Lo reduje a unas 13 líneas de GLSL: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
    Lo uso en el Cursed Mode de un proyecto paralelo, donde renderiza un framebuffer WebGL como un BMP de color indexado de 640x480 píxeles codificado en Base64 unas 15 veces por segundo: https://rezmason.github.io/excel_97_egg/?cursed=1

  • Cuando uno empieza a profundizar, aparecen más detalles interesantes, y también una cantidad sorprendentemente grande de variantes de esos detalles
    Si la longitud de los datos de entrada no es exactamente múltiplo de 3 bytes, se usan 2 o 3 caracteres Base64 para codificar el último byte o los últimos 2 bytes. Como un carácter Base64 tiene 6 bits, para representar 8 o 16 bits se terminan usando 12 o 18 bits, lo que deja 4 o 2 bits extra que no codifican nada
    El RFC exige que el codificador ponga esos bits en 0, pero solo dice que el decodificador puede rechazar entradas donde esos bits no sean 0. En la práctica, casi ninguna implementación los rechaza por defecto y, hasta donde sé, solo Ruby, Rust y Go pueden configurarse para fallar con ese tipo de entrada. Python tiene una opción validate, pero no valida esos bits
    Otra gran diferencia es el manejo de espacios en blanco y caracteres que no son Base64. Sorprendentemente, muchas implementaciones, incluida Python, ignoran silenciosamente caracteres arbitrarios en la entrada. Si se elige mal el alfabeto, eso puede ser un problema: por ejemplo, en Python, base64.standard_b64decode(base64.urlsafe_b64encode(b'\xFF\xFE\xFD\xFC')) no produce un error y devuelve silenciosamente una salida incorrecta
    También es interesante que el codificador Base64 de Ruby inserte saltos de línea cada 60 caracteres. Fuera de PEM, no hay ninguna codificación estándar que requiera líneas tan cortas, y PEM exige líneas de exactamente 64 caracteres, así que es una elección bastante peculiar
    Escribí un texto que resume las diferencias entre lenguajes de programación y algunas bibliotecas de JavaScript [1], y también estoy trabajando en agregar una Base64 mejor a JS [2]
    [1] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...
    [2] https://github.com/tc39/proposal-arraybuffer-base64