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:
- Alfabeto de 26 mayúsculas
- 26 pequeños alfabetos
- 10 dígitos, 0 a 9
- 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 enbase_64
ya discutidos), asigne el carácterbase_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.
f1 = 01010000 & 11111100 = 01010000 >> 2 = 00010100 = 20
01010000 & 00000011 = 0000000 << 4 = 00000000
primera parte de la expresión01001000 & 11110000 = 01010000 >> 4 = 00000101
segunda parte de la expresiónf2 = 00000000 + 00000101 = 00000101 = 5
, sumando el resultado de la primera y segunda parte01001000 & 00001111 = 00001000 << 2 = 00100000
primera parte de la expresión01000001 & 11000000 = 01000000 >> 4 = 00000100
segunda parte de la expresiónf3 = 00100000 + 00000100 = 00100100 = 36
, sumando el resultado de la primera y segunda partef4 = 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.
- Obtenga el índice de cada carácter obtenido del conjunto de caracteres
base_64
y haga un conjunto de 4 bytes. - Nuevamente, agregue 0 contra los caracteres especiales que hemos almacenado en el proceso de codificación.
- 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).
- 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.