Cómo funciona la codificación Base64?
Publicado el 11 de noviembre de 2024Tabla de contenidos
- Que es Base 64?
- Cómo funciona?
- Algoritmo completo
- No reinventes la rueda
- Calculadora
- Siguientes pasos
Que es Base 64?
Si estas iniciando o ya tienes algún tiempo como desarrollador web, probablemente has escuchado o utilizado la codificación Base64. Si no lo has escuchado, aquí te va una definición: Base64 es un sistema de codificación que se usa para representar los datos binarios en formato de texto. Genial! puedo transformar binario a texto!, pero como funciona 🤔? como puedo transformar una secuencia de bits (10101010) en texto 🤔?. Es posible que en el día a día no necesites conocer como funciona el algoritmos realmente, pero si tienes curiosidad en conocerlo, estas en el lugar correcto. Por cierto, usaré javascript para la implementación.
Cómo funciona?
La codificación Base64 se puede resumir en los siguientes pasos.
- Convertir el texto a bytes (se puede omitir si estamos trabajando directamente con bytes (paso 2))
- Convertir los bytes a binario
- Agrupar los binarios en bloques de 6 bits
- Convertir cada grupo de 6 bits a decimal
- Convertir los decimales a caracteres Base64
- Agregar padding al final
Es un poco abstracto, así que lo haremos paso a paso. Supongamos que tenemos el texto Hola Mundo y deseamos transformarlo a Base64. Entonces seguirmos los siguientes pasos:
const text = 'Hola Mundo';
1. Convertir el texto a bytes
Para convertir una cadena de texto a bytes, podemos usar la siguiente función para iterar en cada caracter y transformarlo a bytes. Si estas trabajando directamente con bytes, este paso es opcional.
const bytes = [];
for (let i = 0; i < text.length; i++) {
bytes.push(text.charCodeAt(i));
}
// bytes = [72, 111, 108, 97, 32, 77, 117, 110, 100, 111]
2. Convertir los bytes a binario
Una vez que tenemos un arreglo de bytes, debemos transformarlo a una larga cadena binaria.
- Para transformar cada byte en su formato binario, podemos usar el método toString() que se encuentra en los números y permite transformar un número a un string con una base específica, base 2 en este caso.
- Además, para asegurar que tendrá 8 caracteres, usamos la función padStart() para añadir tantos 0s como sean necesarios al inicio del string.
- Finalmente usamos el método join() para unir toda la cadena
const binary = bytes.map((byte) => byte.toString(2).padStart(8, '0')).join('');
// binary = 01001000011011110110110001100001001000000100110101110101011011100110010001101111
// binary (para lectura) = 01001000 01101111 01101100 01100001 00100000 01001101 01110101 01101110 01100100 01101111
3. Agrupar los binarios en bloques de 6 bits
Una vez que tenemos la cadena binaria, debemos agruparlos en bloques de 6 bits. Esta es la razón por la que se llama base64, con 6 bits puedes formar 64(2^6) números diferentes [0 - 63].
- Usaremos el método slice() para obtener secciones del arreglo.
- Usaremos el método padEnd() para agregar 0s al final y asegurarnos de que el tamaño siempre sea 6.
const blocks6 = [];
for (let i = 0; i < binary.length; i += 6) {
blocks6.push(binary.slice(i, i + 6).padEnd(6, '0'));
}
// blocks6 = ['010010', '000110', '111101', '101100', '011000', '100010', '000001', '001101', '011101', '010110', '111001', '100100', '011011', '110000']
4. Convertir cada grupo de 6 bits a decimal
Ahora que tenemos los bloques de 6 bits, hay que transformarlos en números decimales.
const decimals = blocks6.map((block) => parseInt(block, 2));
// decimals = [18, 6, 61, 44, 24, 18, 1, 13, 29, 22, 57, 36, 27, 48]
5. Convertir los decimales a caracteres Base64
El último paso es el más interesante, cada uno de los números de la lista se usará como índice de la tabla de caracteres Base64 para formar el texto codificado. Como cada número es de 6 bits, sus valores varían entre 0 y 63, es decir, tenemos 64 posibles valores, que es justo el tamaño de la tabla Base64. Si aún no conoces la tabla, se compone de la siguiente manera.
Tabla de caracteres Base64
La tabla está compuesta de la siguiente manera:
- Las letras en mayúsculas (A-Z) (26 caracteres)
- Las letras en minúscula (a-z) (26 caracteres)
- Los dígitos (0-9) (10 caracteres)
- Caracteres especiales ”+/” (2 caracteres)
- Se usa ”=” para el padding
const base64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const base64 = decimals.map((decimal) => base64Table[decimal]).join('');
// base64 = SG9sYSBNdW5kbw
Si bien es cierto, ya tenemos el texto codificado, nos falta considerar algo muy importante, el padding.
6. Agregar padding al final
Recordemos que la codificación Base64 agrupa los bytes en bloques de 6 bits. Si analizamos los posibles valores de entrada notaremos que no siempre la cantidad de bits es divisible entre 6, a veces nos sobran bits, veamos algunos ejemplos:
Texto | Binario | Bits totales | Bloques de 6 | Bits sobrantes |
---|---|---|---|---|
S | 01010011 | 8 | 1 | 2 |
Sa | 01010011 01100001 | 16 | 2 | 4 |
Sal | 01010011 01100001 01101100 | 24 | 4 | 0 |
Salu | 01010011 01100001 01101100 … | 32 | 5 | 2 |
Salud | 01010011 01100001 01101100 … | 40 | 6 | 4 |
Saludo | 01010011 01100001 01101100 … | 48 | 8 | 0 |
De la tabla podemos concluir que cuando los valores de entrada son múltiplos de 3 bytes (24 bits), no nos sobra ningún bit. Es por ello que la codificación Base64 trabaja con bloques de 3 bytes, al aplicar el algoritmo, estos se convierten en 4 bloques de 6 bits, es decir, 4 caracteres Base64. Cuando el número de bytes no es múltiplo de 3, entonces tenemos que añadir un padding (=) para mantener la alineación. Agreguemos la codificación a la tabla.
Texto | Binario | Bits totales | Bloques de 6 | Bits sobrantes | Base64 |
---|---|---|---|---|---|
S | 01010011 | 8 | 1 | 2 | Uw== |
Sa | 01010011 01100001 | 16 | 2 | 4 | U2E= |
Sal | 01010011 01100001 01101100 | 24 | 4 | 0 | U2Fs |
Salu | 01010011 01100001 01101100 … | 32 | 5 | 2 | U2FsdQ== |
Salud | 01010011 01100001 01101100 … | 40 | 6 | 4 | U2FsdWQ= |
Saludo | 01010011 01100001 01101100 … | 48 | 8 | 0 | U2FsdWRv |
Podemos concluir lo siguiente:
- Los caracteres aumentan de 4 en 4 en base a la longitud de la entrada.
- Si los bytes son múltiplo de 3n + 1 (ejm: 3, 6, 9, etc) no agregamos padding.
- Si los bytes son múltiplo de 3n + 1 (ejm: 1, 4, 7, etc) agregamos (==) de padding.
- Si los bytes son múltiplo de 3n + 2 (ejm: 2, 5, 8, etc) agregamos (=) de padding.
Entonces el algorimo quedaría de la siguiente forma.
const padding = (3 - (text.length % 3)) % 3;
// Para 3n + 1 (1, 4, 7, 10, etc) => 2 => "=="
// Para 3n + 2 (2, 5, 8, 11, etc) => 1 => "="
const base64 = decimals.map((decimal) => base64Table[decimal]).join('') + padding;
// base64 = SG9sYSBNdW5kbw==
Algoritmo completo
Este es el algoritmo completo
const text = 'Hola Mundo';
const bytes = [];
for (let i = 0; i < text.length; i++) {
bytes.push(text.charCodeAt(i));
}
const binary = bytes.map((byte) => byte.toString(2).padStart(8, '0')).join('');
const blocks6 = [];
for (let i = 0; i < binary.length; i += 6) {
blocks6.push(binary.slice(i, i + 6).padEnd(6, '0'));
}
const decimals = blocks6.map((block) => parseInt(block, 2));
const base64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const padding = (3 - (text.length % 3)) % 3;
const base64 = decimals.map((decimal) => base64Table[decimal]).join('') + padding;
No reinventes la rueda
En el día a día no vas a implementar el algoritmo para la codificación Base64, ya existen funciones que nos ayudan con esto, la implementación que hice fue con fines explicativos y evidentemente no está optimizada. En tu día a día usarías la función btoa() de la siguiente manera.
const text = 'Hola Mundo';
const base64 = btoa(text);
// base64 = SG9sYSBNdW5kbw
Si…, eso es todo 😅.
Calculadora
Desarrollé una pequeña calculadora que te permite transformar texto en base64, y te muestra los valores en cada uno de los pasos que seguimos en esta guía.
Text to Base64 Converter
Bytes (10)
72 111 108 97 32 77 117 110 100 111
Bytes in Binary (10)
01001000 01101111 01101100 01100001 00100000 01001101 01110101 01101110 01100100 01101111
Bytes grouped in blocks of 6 (14)
010010 000110 111101 101100 011000 010010 000001 001101 011101 010110 111001 100100 011011 110000
Decimals (14)
18 6 61 44 24 18 1 13 29 22 57 36 27 48
Base64 (14)
SG9sYSBNdW5kbw==
Base64 (usando btoa)
Siguientes pasos
Como ves, el algoritmo no es tan complicado, pero si es un poco trabajoso 😅. Pero ahora que los has entendido, tal vez quieras poner en práctica tus conocimientos. Así que aquí hay algunas ideas de proyectos que puedes realizar
Codificador y Decodificador Base 64
Crea una aplicación que permita transformar un texto a Base64 y que permita convertir de Base64 a texto (spoiler: debes usar el algoritmo a la inversa).
- Tecnologías: las que quieras, puede ser una aplicación web, móvil, terminal, etc.
Crea tu propia codificación
Como ya entendíste la lógica detrás de la codificación Base64, puedes experimentar creando la tuya propia.
- Cambiar la cantidad de bits: Porque limitarse a Base64?, como sería una codificación Base128 o Base32 🤔?
- Cambiar la tabla de caracteres: puedes cambiar el orden de los caracteres de la tabla de caracteres Base64 o crear tu propia tabla.