Codificación base64

Logo Base64

Base 64 es un sistema de numeración, como el decimal, el binario o el hexadecimal, pero que usa 64 símbolos.

En la práctica no se usa tanto para codificar números, sino sobre todo para codificar datos binarios. El motivo es que usa 64 símbolos imprimibles: 62 de los símbolos consisten en el alfabeto en mayúsculas y en minúsculas y los dígitos 0 a 9. Para los otros dos se usan distintos caracteres, dependiendo de la versión particular del código. Los más habituales son el '+' y el '/'.

Esto hace que cualquier valor codificado en base 64 sea imprimible, lo que a su vez permite que se puedan visualizar con un editor de textos, o eludir los problemas que suelen aparecer con caracteres especiales en transmisiones de datos o manejo de ficheros.

Se suele usar base 64 para codificar los ficheros binarios que se envían adjuntos con mensajes de correo, por ejemplo. Pero se puede usar en otras muchas cosas.

En mi caso, lo he usado para transferir datos binarios usando una línea serie, ya que si lo hacía en binario surgían algunos problemas con algunos valores que tienen un significado especial para el sistema operativo.

Esto en cuanto a ventajas. Entre los inconvenientes cabe mencionar que se necesitan más bytes en la versión codificada en base 64 que en la binaria. La relación es de 4/3, es decir, por cada tres bytes binarios se necesitan cuatro en base 64. Esto es así porque sólo se usan 64 valores de cada byte, es decir, 6 bits de los 8 disponibles, 8/6 equivale a 4/3.

Empaquetado de bits

La idea es tomar los 24 bits correspondientes a tres bytes a codificar y repartirlos en cuatro bytes, de modo que sólo se usen seis de los ocho bits de cada uno. Eso nos da cuatro valores entre 0 y 63 que usaremos como índice en un array de caracteres que contiene los siguientes valores:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

Así, al valor 0 le corresponde el carácter 'A', al 1 el carácter 'B', etc.

Conversión base 64
Conversión base 64

La forma de repartir esos bits es simple:

  • Se toman los seis bits de mayor peso del primer byte de origen y se copian a los seis bits de menor peso del primer byte de destino.
  • Se toman los dos bits de menor peso del primer byte de origen y los cuatro de mayor peso del segundo y se copian al segundo byte de destino.
  • Se toman los cuatro bits de menor peso del segundo byte de origen y los dos de mayor peso del tercero y se copian al tercer byte de destino.
  • Se toman los seis bits del tercer byte de origen y se copian al cuarto byte de destino.

Ahora los cuatro bytes de destino contienen valores entre 0 y 63. Esos valores se toman como índice para obtener el carácter correspondiente en la codificación base 64.

A la hora de codificar en base 64 hay que tener en cuenta un detalle extra. La longitud de los datos de entrada no será siempre un múltiplo exacto de tres. Cuando no lo sea, en la última iteración de conversión pueden quedar uno o dos bytes. Si es uno, la conversión requiere sólo dos bytes, y si es dos requerirá tres.

Sin embargo, la longitud de los datos de salida siempre será un múltiplo de cuatro, de modo que si en la última iteración sólo queda un byte por convertir se calculan los dos bytes de salida correspondientes y se añaden dos caracteres especiales, que no forman parte de los caracteres permitidos en base 64. El carácter que se usa es el '='. Si en la última iteración quedan dos bytes, se calculan los tres bytes de salida correspondientes y se añade un carácter de relleno.

Esto asegura que será posible no sólo hacer la conversión inversa, sino que además la longitud será la correcta.

Texto E s t
ASCII 69 115 116
Binario 0 1 0 0 0 1 0 1 0 1 1 1 0 0 1 1 0 1 1 1 0 1 0 0
Índice 17 23 13 52
Base 64 R X N 0

En caso de que sobren dos caracteres, la conversión queda así:

Texto E s
ASCII 69 115
Binario 0 1 0 0 0 1 0 1 0 1 1 1 0 0 1 1 0 0 0 0 0 0 0 0
Índice 17 23 12
Base 64 R X M =

Y en caso de que sobre uno:

Texto E
ASCII 69
Binario 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Índice 17 16
Base 64 R Q = =

Codificación en C++

Este es un ejemplo de funciones en C++ para convertir a y desde Base 64:

int CodificarB64(unsigned char* entrada, char*salida , int l) {
    char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    int i=0, j=0;
    unsigned char A, B, C;

    // Bloques de tres bytes:
    entrada[l]=0;
    if(l%3==1) entrada[l+1]=0;
    if(l%3==2) entrada[l+2]=0;
    for(i = 0; i < l; i+=3){
        A = entrada[i];
        B = entrada[i+1];
        C = entrada[i+2];
        salida[j++] = b64[A>>2];
        salida[j++] = b64[((A<<4)+(B>>4)) & 0x3f];
        if(i+1 < l) salida[j++] = b64[((B<<2)+(C>>6)) & 0x3f]; else salida[j++] = '=';
        if(i+2 < l) salida[j++] = b64[C & 0x3f]; else salida[j++] = '=';
    }
    salida[j] = 0;
    return j;
}

char Indice(const char c) {
    if(isdigit(c)) return c-'0'+52;
    if(isupper(c)) return c-'A';
    if(islower(c)) return c-'a'+26;
    if(c=='+') return 62;
    if(c=='/') return 63;
    return 0;
}

int DecodificarB64(char* entrada, unsigned char* salida) {
    int i=0, j=0;
    unsigned char A, B, C, D;

    // Bloques de tres bytes:
    while(entrada[i]) {
        A = Indice(entrada[i++]);
        B = Indice(entrada[i++]);
        C = Indice(entrada[i++]);
        D = Indice(entrada[i++]);

        salida[j++] = (unsigned char)((A << 2) + (B >> 4));
        salida[j++] = (unsigned char)((B << 4) + (C >> 2));
        salida[j++] = (unsigned char)((C << 6) + D);
    }
    return j;
}