Implementación de codificación Base 64 en C++

Abdul Mateen 12 octubre 2023
  1. Esquema de codificación Base_64
  2. Implementación de codificación Base_64 en C++
Implementación de codificación Base 64 en C++

Este tutorial discutirá la codificación en base_64 en C++.

Primero, discutiremos la codificación base_64 y por qué y dónde se requiere. Más adelante, discutiremos la codificación/decodificación de base_64 en C++.

Esquema de codificación Base_64

Base_64 es una adición a los esquemas de codificación. Es similar a la codificación de binario a texto en que representa datos binarios en una cadena ASCII.

La diferencia es que la codificación base_64 utiliza la traducción a un radix-64. El nombre de codificación Base_64 proviene de la definición matemática de bases.

La base representa los dígitos básicos del sistema numérico. Como base_2 tiene solo 2 dígitos básicos, 0 y 1.

Base_8, un sistema numérico octal, tiene 8 dígitos básicos del 0 al 7.

De manera similar, base_16 tiene 16 dígitos básicos del 0 al 15, donde usamos A a F para representar 10 a 15. En base_64, hay 64 dígitos básicos que consisten en:

  1. Alfabeto de 26 mayúsculas
  2. 26 pequeños alfabetos
  3. 10 dígitos, 0 a 9
  4. 2 signos + y /

La codificación Base_64 se usa comúnmente para transferir datos a través de medios, diseñada para manejar ASCII. El base_64 intenta mantener la integridad de los datos transmitidos a través de los medios.

La aplicación principal es el correo electrónico a través de MIME y el almacenamiento de datos complejos en XML. Base_64 también se denomina correo electrónico con privacidad mejorada (PEM).

Pasos de codificación Base_64

Para la codificación, tenemos datos como cadenas binarias, donde tenemos que operar en cada carácter de la cadena. Tenemos que realizar los siguientes pasos para codificar en base_64.

  • Tome el valor ASCII de cada carácter.
  • Encuentre el binario de 8 bits de los valores ASCII.
  • Convierta los 8 bits (obtenidos en el paso 2) en 6 bits reorganizando los dígitos (requirió cierta manipulación, incluidas algunas operaciones de bits (que se discutirán más adelante))
  • Convierta los binarios de 6 bits a sus valores decimales correspondientes
  • Usando base_64 (dígitos básicos en base_64 ya discutidos), asigne el carácter base_64 respectivo contra cada valor decimal.

Aquí discutiremos los detalles del paso 3, que es la conversión de grupos de 8 bits a grupos de 6 bits.

Procedimiento para convertir grupos de 8 bits en grupos de 6 bits

En la codificación Base_64, como se discutió al principio, tenemos 64 caracteres/dígitos primarios, mientras que normalmente leemos/escribimos datos en bytes. 1 byte tiene 8 bits, donde podemos almacenar 0 a 255, lo que significa que podemos representar 256 únicos en un byte.

6 bits pueden representar valores únicos 64, donde tenemos que mantener los últimos 2 bits 0 para que cada byte almacene solo 1 dígito/carácter del esquema de codificación Base_64.

Cada carácter/valor ASCII ocupa 8 bits. Por lo tanto, ajustar 2 bits de cada byte requiere más almacenamiento que los datos originales.

Para la codificación Base_64, debemos convertirlos a 6 bits sin pérdida de datos.

Si tomamos el LCM de 8 y 6, obtenemos 24. 3 bytes tienen 24 bits, pero si usamos 6 de 8 bits (los últimos 2 bits no se usan), necesitamos 4 bytes para 24 bits. Por lo tanto, sin pérdida de datos, podemos convertir cada uno de los 3 grupos de 8 bits en 4 grupos de 6 bits.

El primer paso es agrupar los datos en conjuntos de 3 bytes. Si el último grupo tiene menos bytes, el grupo se completa agregando bytes con valor 0.

A continuación, cada conjunto de 3 bytes se agrupa en 4 bytes utilizando las siguientes operaciones. Consideremos un conjunto de 3 bytes como t1, t2 & t3 y 4 como f1, f2, f3 & f4.

f1 = ( t1 & 0xfc ) >> 2

Considere la máscara 0xfc (equivalente al binario 11111100), aplique bit a bit y operación entre el primer byte del conjunto y la máscara. A continuación, utilice el desplazamiento a la derecha dos veces para obtener el resultado de operación y bit a bit.

La operación de desplazamiento transferirá los 6 bits de la izquierda a la derecha y los últimos 2 bits se convertirán en 0.

La máscara 0xfc tiene los 2 primeros bits 0; donde una operación hace que los primeros 2 bits del primer byte del conjunto sean 0 (lo que significa que se consideran los últimos 6 bits del primer byte), los primeros 2 bits (ignorados en esta operación) se considerarán en el siguiente proceso.

f2 = ( ( t1 & 0x03 ) << 4 ) + ( ( t2 & 0xf0 ) >> 4 )

Aquí se aplica la máscara 0x03 00000011 en el primer byte de una operación (lo que significa que solo se consideran los primeros 2 bits, los últimos 6 bits ya se consideraron en la operación anterior). La operación de desplazamiento transferirá los 2 bits resultantes del primer byte a la izquierda, convirtiéndolos en el quinto y sexto bits de la expresión.

La máscara 0xf0 11110000 se aplica en el segundo byte de una operación (lo que significa que solo se consideran los últimos 4 bits). La operación de desplazamiento transferirá los 4 bits resultantes a la derecha para convertirlos en los primeros 4 bits de la expresión.

La primera parte de la expresión tiene activados los bits quinto y sexto, la segunda parte tiene activados los primeros 4 bits y, colectivamente, tenemos activados los primeros 6 bits y desactivados los últimos.

Finalmente, los combinamos para obtener un byte con los últimos 2 bits desactivados. En este paso hemos obtenido otro byte de 6 bits, donde se completa el primer byte, y se consideran los primeros 4 bits del segundo byte.

f3 = ( ( t2 & 0x0f ) << 2 ) + ( ( t3 & 0xc0 ) >> 6 )

La máscara 0x0f 00001111 se aplica en el segundo byte para la operación (lo que significa que solo se consideran los primeros 4 bits). La operación de desplazamiento transferirá los 4 bits resultantes a la izquierda para convertirlos en los bits tercero, cuarto, quinto y sexto de la expresión y creará un espacio para los primeros 2 bits.

A continuación, se aplica la máscara 0xc0 11000000 en el tercer byte de una operación (lo que significa que solo se consideran los 2 primeros bits). La operación de cambio transferirá los 2 bits resultantes a la derecha para convertirlos en el primer y segundo bit de la expresión.

Finalmente, ambos resultados se combinan para obtener el tercer byte del grupo de 6 bits. Nuevamente, en el conjunto, hemos completado el segundo byte del conjunto y 2 bits del tercer byte.

f4 = t3 & 0x3f

Finalmente, el tercer byte solo tiene operación, donde la máscara 0x3f 00111111 tiene los 6 primeros bits activados y los 2 últimos desactivados. La operación con el tercer byte considerará los 6 bits restantes del tercer byte.

Ya hemos discutido los 64 dígitos básicos usados en base_64. En el siguiente paso, cada byte del conjunto de 4 bytes (obtenido mediante operaciones con bits) se convierte en base_64 y se concatena en una cadena.

Codifiquemos la palabra PLAY. En el primer paso, haremos conjuntos de 3 personajes cada uno. En el primer conjunto, tenemos PLA.

En la siguiente etapa, tenemos Y\0\0. Aquí, \0 es un carácter nulo agregado para completar el conjunto.

El ASCII de cada uno de estos caracteres es 80 76 65 89 0 0. El valor binario correspondiente es 01010000 01001000 01000001 01011001.

Ahora hagamos operaciones con bits.

  1. f1 = 01010000 & 11111100 = 01010000 >> 2 = 00010100 = 20
  2. 01010000 & 00000011 = 0000000 << 4 = 00000000 primera parte de la expresión
  3. 01001000 & 11110000 = 01010000 >> 4 = 00000101 segunda parte de la expresión
  4. f2 = 00000000 + 00000101 = 00000101 = 5, sumando el resultado de la primera y segunda parte
  5. 01001000 & 00001111 = 00001000 << 2 = 00100000 primera parte de la expresión
  6. 01000001 & 11000000 = 01000000 >> 4 = 00000100 segunda parte de la expresión
  7. f3 = 00100000 + 00000100 = 00100100 = 36, sumando el resultado de la primera y segunda parte
  8. f4 = 01000001 & 00000011 = 00000001 = 1

Ahora repita la operación en el siguiente conjunto, donde el segundo y tercer valor es 0; por lo tanto, los resultados serán:

f1 = 00010110 = 21

f2 = 00010000 = 16

f3 = 0

f4 = 0

A continuación, tenemos que convertir estos valores en base_64. Además, tenemos que colocar algunos caracteres centinela/especiales en los últimos 2 bytes para que el proceso de decodificación pueda reconocerlos y decodificarlos en consecuencia.

En el primer conjunto tenemos f1= 20, f2 = 5, f3 = 36 & f4 = 1. Los valores base_64 correspondientes serán UFkB.

El siguiente conjunto, tenemos f1 = 21, f2 = 16, f3 = 0 y f4 = 0. Nuevamente, los valores correspondientes de base_64 serán VQ^^, donde los signos de intercalación se usan como caracteres especiales, por lo que la cadena colectiva es UFkBV^^.

El proceso de decodificación es simplemente el proceso inverso; puede obtener rápidamente ambos métodos del código C++ a continuación.

Implementación de codificación Base_64 en C++

Es un proceso sencillo para codificar en C++. Podemos implementar rápidamente (los pasos que hemos discutido) en C++.

Lo discutiremos en fases y, finalmente, daremos el código completo con 2 ejemplos.

Primero, para la conversión base_64, definiremos una cadena constante con dígitos/caracteres básicos de base_64.

const string base64_chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

Antes de discutir las funciones de codificación y decodificación, tenemos algunas definiciones al principio. Principalmente, hay algunas máscaras que se utilizan para codificar y decodificar.

6 de estas máscaras ya se analizan al explicar la conversión de grupos de 8 bits a grupos de 6 bits.

Algunas de estas máscaras se utilizarán en el proceso de decodificación para convertir grupos de 6 bits a grupos de 8 bits y, además, se requerirán 2 máscaras más. En total, tenemos 8 máscaras.

typedef unsigned char UC;
typedef unsigned int UI;
#define EXTRA '^'
#define MASK1 0xfc
#define MASK2 0x03
#define MASK3 0xf0
#define MASK4 0x0f
#define MASK5 0xc0
#define MASK6 0x3f
#define MASK7 0x30
#define MASK8 0x3c

A continuación, considere la función de codificación. Haremos conjuntos de 3 personajes.

A continuación, los convertiremos en grupos de 4 caracteres con operaciones de bits, ya comentadas en detalle. Finalmente, convertiremos cada byte de nuestro grupo de 4 caracteres y los concatenaremos para crear una cadena codificada.

Aquí está el código:

string encode_base64(UC const* buf, UI bufLen) {
  string encoded = "";
  UI i = 0, j = 0, k = 0;
  UC temp_a_3[3], temp_4[4];
  for (i = 0; i < bufLen; i += 3) {
    for (j = i, k = 0; j < bufLen && j < i + 3; j++) temp_a_3[k++] = *(buf++);
    for (; k < 3; k++) temp_a_3[k] = '\0';
    temp_4[0] = (temp_a_3[0] & MASK1) >> 2;
    temp_4[1] = ((temp_a_3[0] & MASK2) << 4) + ((temp_a_3[1] & MASK3) >> 4);
    temp_4[2] = ((temp_a_3[1] & MASK4) << 2) + ((temp_a_3[2] & MASK5) >> 6);
    temp_4[3] = temp_a_3[2] & MASK6;
    for (j = i, k = 0; j < bufLen + 1 && j < i + 4; j++, k++)
      encoded += base64_chars[temp_4[k]];
    for (; k < 4; k++) encoded += EXTRA;  // sentinal value
  }
  return encoded;
}

La función toma 2 parámetros, el primero son los datos sin procesar (enviados para la codificación) y el segundo es la longitud del mensaje. Hemos declarado 2 matrices de tamaños 3 y 4. Dentro del bucle, almacenamos datos en la primera matriz de tamaño 3.

A continuación, en caso de que haya menos bytes en el último conjunto, agregamos caracteres nulos para completar el último conjunto. A continuación, tenemos 4 declaraciones que convierten datos de 8 bits en operaciones de 6 bits por bit.

Por último, en el penúltimo bucle, convertimos un grupo de 4 caracteres de 6 bits en base_64.

El último bucle almacena caracteres adicionales para completar un conjunto de 4 bytes. A continuación, tenemos la función de decodificación.

vector<UC> decode_base64(string const& encoded) {
  UI i = 0, j = 0, k = 0, in_len = encoded.size();
  UC temp_a_3[3], temp_4[4];
  vector<UC> decoded;
  for (i = 0; i < in_len; i += 4) {
    for (j = i, k = 0; j < i + 4 && encoded[j] != EXTRA; j++)
      temp_4[k++] = base64_chars.find(encoded[j]);
    for (; k < 4; k++) temp_4[k++] = '\0';
    temp_a_3[0] = (temp_4[0] << 2) + ((temp_4[1] & MASK7) >> 4);
    temp_a_3[1] = ((temp_4[1] & MASK4) << 4) + ((temp_4[2] & MASK8) >> 2);
    temp_a_3[2] = ((temp_4[2] & MASK2) << 6) + temp_4[3];
    for (j = i, k = 0; k < 3 && encoded[j + 1] != EXTRA; j++, k++)
      decoded.push_back(temp_a_3[k]);
  }
  return decoded;
}

Esta función toma el mensaje codificado y realiza la operación inversa, que incluye los siguientes pasos.

  1. Obtenga el índice de cada carácter obtenido del conjunto de caracteres base_64 y haga un conjunto de 4 bytes.
  2. Nuevamente, agregue 0 contra los caracteres especiales que hemos almacenado en el proceso de codificación.
  3. A continuación, convierta un conjunto de 4 bytes en un conjunto de 3 bytes mediante operaciones de bits inversas (aquí no entraremos en detalles de estas operaciones).
  4. Finalmente, combine un conjunto de 3 bytes para obtener el mensaje decodificado combinado.

Finalmente, aquí tenemos un código completo con 2 ejemplos de codificación y codificación.

#include <iostream>
#include <string>
#include <vector>

using namespace std;

typedef unsigned char UC;
typedef unsigned int UI;
#define EXTRA '^'
#define MASK1 0xfc
#define MASK2 0x03
#define MASK3 0xf0
#define MASK4 0x0f
#define MASK5 0xc0
#define MASK6 0x3f
#define MASK7 0x30
#define MASK8 0x3c

const string base64_chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

string encode_base64(UC const* buf, UI bufLen) {
  string encoded = "";
  UI i = 0, j = 0, k = 0;
  UC temp_a_3[3], temp_4[4];
  for (i = 0; i < bufLen; i += 3) {
    for (j = i, k = 0; j < bufLen && j < i + 3; j++) temp_a_3[k++] = *(buf++);
    for (; k < 3; k++) temp_a_3[k] = '\0';
    temp_4[0] = (temp_a_3[0] & MASK1) >> 2;
    temp_4[1] = ((temp_a_3[0] & MASK2) << 4) + ((temp_a_3[1] & MASK3) >> 4);
    temp_4[2] = ((temp_a_3[1] & MASK4) << 2) + ((temp_a_3[2] & MASK5) >> 6);
    temp_4[3] = temp_a_3[2] & MASK6;
    for (j = i, k = 0; j < bufLen + 1 && j < i + 4; j++, k++)
      encoded += base64_chars[temp_4[k]];
    for (; k < 4; k++) encoded += EXTRA;  // sentinal value
  }
  return encoded;
}
vector<UC> decode_base64(string const& encoded) {
  UI i = 0, j = 0, k = 0, in_len = encoded.size();
  UC temp_a_3[3], temp_4[4];
  vector<UC> decoded;
  for (i = 0; i < in_len; i += 4) {
    for (j = i, k = 0; j < i + 4 && encoded[j] != EXTRA; j++)
      temp_4[k++] = base64_chars.find(encoded[j]);
    for (; k < 4; k++) temp_4[k++] = '\0';
    temp_a_3[0] = (temp_4[0] << 2) + ((temp_4[1] & MASK7) >> 4);
    temp_a_3[1] = ((temp_4[1] & MASK4) << 4) + ((temp_4[2] & MASK8) >> 2);
    temp_a_3[2] = ((temp_4[2] & MASK2) << 6) + temp_4[3];
    for (j = i, k = 0; k < 3 && encoded[j + 1] != EXTRA; j++, k++)
      decoded.push_back(temp_a_3[k]);
  }
  return decoded;
}
int main() {
  vector<UC> myData = {'6', '7', '8', '9'};
  string encoded = encode_base64(&myData[0], myData.size());
  cout << "Encoded String: " << encoded << '\n';
  vector<UC> decoded = decode_base64(encoded);
  cout << "Decoded Data: ";
  for (int i = 0; i < decoded.size(); i++) cout << (char)decoded[i] << ' ';
  cout << '\n';
  myData = {4, 16, 64};
  encoded = encode_base64(&myData[0], myData.size());
  cout << "Encoded String: " << encoded << '\n';
  decoded = decode_base64(encoded);
  cout << "Decoded Data: ";
  for (int i = 0; i < decoded.size(); i++) cout << (int)decoded[i] << ' ';
  cout << '\n';
  return 0;
}

En general, tenemos 2 conjuntos de datos. En el primer conjunto, tenemos caracteres numéricos; en el siguiente conjunto, tenemos valores numéricos; por lo tanto, en el último ciclo, imprimimos el mensaje decodificado con conversión de tipos en un número entero.

Producción :

Encoded String: Njc4OQ^^
Decoded Data: 6 7 8 9
Encoded String: BBBA
Decoded Data: 4 16 64

El primer conjunto tiene 4 caracteres (4 bytes) y el mensaje codificado Njc4OQ^^ tiene 6 caracteres (los últimos 2 caracteres son extra). En el segundo conjunto, hay 3 bytes, y el mensaje codificado BBBA tiene 4 bytes.

En la codificación base_64, cada carácter tiene un máximo de 6 bits establecidos en 1, donde tenemos 64 caracteres primarios base_64 correspondientes. De manera similar, el mensaje codificado requiere un 33 % más de almacenamiento que ASCII.

A pesar del almacenamiento adicional, la ventaja es administrar caracteres especiales. Por lo tanto, este esquema de codificación transfiere datos y mantiene intacta la integridad.