Base-64-Codierungsimplementierung in C++

Abdul Mateen 12 Oktober 2023
  1. Kodierungsschema Base_64
  2. Base_64-Kodierungsimplementierung in C++
Base-64-Codierungsimplementierung in C++

Dieses Tutorial behandelt die Kodierung in base_64 in C++.

Zuerst werden wir die Codierung base_64 besprechen und warum und wo sie erforderlich ist. Später werden wir die Codierung/Decodierung von base_64 in C++ besprechen.

Kodierungsschema Base_64

Base_64 ist eine Ergänzung zu Kodierungsschemata. Es ähnelt der Binär-zu-Text-Codierung, da es binäre Daten in einer ASCII-Zeichenfolge darstellt.

Der Unterschied besteht darin, dass die Codierung base_64 die Übersetzung in Radix-64 verwendet. Der Codierungsname Base_64 stammt von der mathematischen Definition von Basen.

Die Basis repräsentiert die Basisziffern des Zahlensystems. Wie Basis_2 hat nur 2 Grundziffern, 0 und 1.

Base_8, ein Oktalzahlensystem, hat 8 Grundziffern von 0 bis 7.

Ebenso hat base_16 16 Grundziffern von 0 bis 15, wobei wir A bis F verwenden, um 10 bis 15 darzustellen. In base_64 gibt es 64 Basisziffern, bestehend aus:

  1. 26 Großbuchstaben
  2. 26 kleine Alphabete
  3. 10 Ziffern, 0 bis 9
  4. 2 Zeichen + und /

Die Base_64-Codierung wird häufig verwendet, um Daten über Medien zu übertragen, die für den Umgang mit ASCII ausgelegt sind. Die base_64 versucht, die Integrität der über die Medien übertragenen Daten aufrechtzuerhalten.

Die Hauptanwendung ist E-Mail über MIME und das Speichern komplexer Daten in XML. Base_64 wird auch Privacy Enhanced Electronic Mail (PEM) genannt.

Kodierung Base_64 Schritte

Für die Codierung haben wir Daten als binäre Zeichenfolge, in der wir mit jedem Zeichen in der Zeichenfolge arbeiten müssen. Wir müssen die folgenden Schritte ausführen, um in base_64 zu codieren.

  • Nehmen Sie den ASCII-Wert jedes Zeichens.
  • Suchen Sie die 8-Bit-Binärdatei der ASCII-Werte.
  • Konvertieren Sie die 8 Bits (in Schritt 2 erhalten) in 6 Bits, indem Sie die Ziffern neu anordnen (erfordert einige Manipulationen, einschließlich einiger Bitoperationen (wird später besprochen))
  • Konvertieren Sie die 6-Bit-Binärdateien in ihre entsprechenden Dezimalwerte
  • Weisen Sie mit base_64 (Basisziffern in base_64 bereits besprochen) jedem Dezimalwert das entsprechende base_64-Zeichen zu.

Hier werden wir die Details von Schritt 3 besprechen, bei dem es sich um die Umwandlung von 8-Bit-Gruppen in 6-Bit-Gruppen handelt.

Verfahren zum Konvertieren von 8-Bit-Gruppen in 6-Bit-Gruppen

Bei der eingangs erwähnten Base_64-Kodierung haben wir 64 primäre Zeichen/Ziffern, während wir normalerweise Daten in Bytes lesen/schreiben. 1 Byte hat 8 Bits, wobei wir 0 bis 255 speichern können, was bedeutet, dass wir 256 eindeutig in einem Byte darstellen können.

6 Bits können 64 eindeutige Werte darstellen, wobei wir die letzten 2 Bits auf 0 halten müssen, damit jedes Byte nur 1 Ziffer/Zeichen des Base_64-Codierungsschemas speichert.

Jedes Zeichen/ASCII-Wert benötigt 8 Bit. Daher erfordert das Anpassen von 2 Bits jedes Bytes mehr Speicherplatz als die ursprünglichen Daten.

Für die Base_64-Kodierung müssen wir sie ohne Datenverlust in 6 Bit umwandeln.

Wenn wir das LCM von 8 und 6 nehmen, erhalten wir 24. 3 Bytes haben 24 Bits, aber wenn wir 6 von 8 Bits verwenden (die letzten 2 Bits werden nicht verwendet), benötigen wir 4 Bytes für 24 Bits. Daher können wir ohne Datenverlust jede der 3 8-Bit-Gruppen in 4 6-Bit-Gruppen umwandeln.

Der erste Schritt besteht darin, Daten in Sätze von 3 Bytes zu gruppieren. Wenn die letzte Gruppe kleinere Bytes hat, wird die Gruppe vervollständigt, indem Bytes mit einem 0-Wert hinzugefügt werden.

Als nächstes wird jeder Satz von 3 Bytes unter Verwendung der folgenden Operationen in 4 Bytes gruppiert. Betrachten wir einen Satz von 3 Bytes als t1, t2 & t3 und 4 als f1, f2, f3 & f4.

f1 = ( t1 & 0xfc ) >> 2

Betrachten Sie die Maske 0xfc (entspricht binär 11111100), wenden Sie bitweise an und operieren Sie zwischen dem ersten Byte des Satzes und der Maske. Als nächstes verwenden Sie die rechte Verschiebung zweimal zum Ergebnis von bitweise und Operation.

Die Schiebeoperation überträgt die linken 6 Bits nach rechts, und die letzten 2 Bits werden 0.

Die Maske 0xfc hat die ersten 2 Bits 0; Wenn eine Operation die ersten 2 Bits des ersten Bytes des Satzes auf 0 setzt (was bedeutet, dass die letzten 6 Bits des ersten Bytes berücksichtigt werden), werden die ersten 2 Bits (bei dieser Operation ignoriert) im folgenden Prozess berücksichtigt.

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

Hier wird die Maske 0x03 00000011 auf das erste Byte einer Operation angewendet (d.h. es werden nur die ersten 2 Bits berücksichtigt, die letzten 6 Bits werden bereits bei der vorherigen Operation berücksichtigt). Die Verschiebungsoperation überträgt die resultierenden 2 Bits des ersten Bytes nach links und macht sie zum fünften und sechsten Bit im Ausdruck.

Auf das zweite Byte einer Operation wird die Maske 0xf0 11110000 angewendet (also nur die letzten 4 Bits berücksichtigt). Die Verschiebungsoperation überträgt die resultierenden 4 Bits nach rechts, um sie zu den ersten 4 Bits des Ausdrucks zu machen.

Der erste Teil des Ausdrucks hat die fünften und sechsten Bits eingeschaltet, der zweite Teil hat die ersten 4 Bits eingeschaltet, und insgesamt haben wir die ersten 6 Bits eingeschaltet und die letzten Bits ausgeschaltet.

Schließlich kombinieren wir sie, um ein Byte mit den letzten 2 Bits zu erhalten. In diesem Schritt haben wir ein weiteres Byte von 6 Bits erhalten, wobei das erste Byte abgeschlossen ist und die ersten 4 Bits des zweiten Bytes berücksichtigt werden.

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

Für den Betrieb wird auf das zweite Byte die Maske 0x0f 00001111 angewendet (also nur die ersten 4 Bits berücksichtigt). Die Verschiebungsoperation überträgt die resultierenden 4 Bits nach links, um sie zum dritten, vierten, fünften und sechsten Bit des Ausdrucks zu machen und einen Platz für die ersten 2 Bits zu schaffen.

Als nächstes wird die Maske 0xc0 11000000 auf das dritte Byte für eine Operation angewendet (d. h. es werden nur die ersten 2 Bits berücksichtigt). Die Verschiebungsoperation überträgt die resultierenden 2 Bits nach rechts, um sie zum ersten und zweiten Bit des Ausdrucks zu machen.

Schließlich werden beide Ergebnisse kombiniert, um das dritte Byte der 6-Bit-Gruppe zu erhalten. Auch hier haben wir in der Menge das zweite Byte der Menge und 2 Bits des dritten Bytes vervollständigt.

f4 = t3 & 0x3f

Schließlich hat das dritte Byte nur eine Operation, wobei die Maske 0x3f 00111111 die ersten 6 Bits an und die letzten 2 aus hat. Die Operation mit dem dritten Byte berücksichtigt die verbleibenden 6 Bits des dritten Bytes.

Wir haben bereits die 64 Basisziffern besprochen, die in base_64 verwendet werden. Im nächsten Schritt wird jedes Byte aus der Menge der 4 Bytes (erhalten durch Bitoperationen) in base_64 umgewandelt und zu einem String verkettet.

Lassen Sie uns das Wort PLAY verschlüsseln. Im ersten Schritt erstellen wir Sets mit jeweils 3 Charakteren. Im ersten Satz haben wir PLA.

Im nächsten Schritt haben wir Y\0\0. Hier ist \0 ein Nullzeichen, das hinzugefügt wird, um den Satz zu vervollständigen.

Der ASCII-Code jedes dieser Zeichen ist 80 76 65 89 0 0. Der entsprechende Binärwert ist 01010000 01001000 01000001 01011001.

Lassen Sie uns nun Bitoperationen durchführen.

  1. f1 = 01010000 & 11111100 = 01010000 >> 2 = 00010100 = 20
  2. 01010000 & 00000011 = 0000000 << 4 = 00000000 erster Teil des Ausdrucks
  3. 01001000 & 11110000 = 01010000 >> 4 = 00000101 zweiter Teil des Ausdrucks
  4. f2 = 00000000 + 00000101 = 00000101 = 5, das Ergebnis des ersten und zweiten Teils addieren
  5. 01001000 & 00001111 = 00001000 << 2 = 00100000 erster Teil des Ausdrucks
  6. 01000001 & 11000000 = 01000000 >> 4 = 00000100 zweiter Teil des Ausdrucks
  7. f3 = 00100000 + 00000100 = 00100100 = 36, das Ergebnis des ersten und zweiten Teils addieren
  8. f4 = 01000001 & 00000011 = 00000001 = 1

Wiederholen Sie nun die Operation beim nächsten Satz, bei dem der zweite und dritte Wert 0 ist; daher werden die Ergebnisse sein:

f1 = 00010110 = 21

f2 = 00010000 = 16

f3 = 0

f4 = 0

Als nächstes müssen wir diese Werte in base_64 umwandeln. Außerdem müssen wir einige Sentinel-/Sonderzeichen in die letzten 2 Bytes einfügen, damit der Decodierungsprozess sie erkennen und entsprechend decodieren kann.

Im ersten Satz haben wir f1= 20, f2 = 5, f3 = 36 & f4 = 1. Die entsprechenden base_64-Werte sind UFkB.

Im nächsten Satz haben wir f1 = 21, f2 = 16, f3 = 0 & f4 = 0. Auch hier sind die entsprechenden base_64-Werte VQ^^, wobei Caret-Zeichen als Sonderzeichen verwendet werden, also ist der String insgesamt UFkBV^^.

Der Dekodierungsprozess ist einfach der umgekehrte Prozess; Sie können beide Methoden schnell aus dem C++-Code unten abrufen.

Base_64-Kodierungsimplementierung in C++

Es ist ein unkomplizierter Prozess, in C++ zu kodieren. Wir können (die besprochenen Schritte) schnell in C++ implementieren.

Wir werden es in Phasen besprechen und schließlich werden wir den vollständigen Code mit 2 Beispielen geben.

Zuerst definieren wir für die Konvertierung base_64 eine konstante Zeichenfolge mit grundlegenden Ziffern/Zeichen von base_64.

const string base64_chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

Bevor wir auf Codierungs- und Decodierungsfunktionen eingehen, haben wir einige Definitionen am Anfang. In erster Linie gibt es einige Masken, die beim Codieren und Decodieren verwendet werden.

6 dieser Masken wurden bereits besprochen, während die Umwandlung von 8-Bit-Gruppen in 6-Bit-Gruppen erläutert wurde.

Einige dieser Masken werden im Dekodierungsprozess verwendet, um 6-Bit-Gruppen in 8-Bit-Gruppen umzuwandeln, und zusätzlich werden 2 weitere Masken benötigt. Insgesamt haben wir 8 Masken.

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

Betrachten Sie als Nächstes die Codierungsfunktion. Wir werden Sätze von 3 Zeichen machen.

Als nächstes werden wir sie in Gruppen von 4 Zeichen mit Bitoperationen umwandeln, die bereits ausführlich besprochen wurden. Schließlich konvertieren wir jedes Byte unserer Gruppe von 4 Zeichen und verketten sie, um eine codierte Zeichenfolge zu erstellen.

Hier ist der Code:

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;
}

Die Funktion benötigt 2 Parameter, der erste sind die Rohdaten (zur Codierung gesendet) und der zweite die Länge der Nachricht. Wir haben 2 Arrays der Größen 3 und 4 deklariert. Innerhalb der Schleife speichern wir Daten im ersten Array der Größe 3.

Als nächstes fügen wir im Falle von kleineren Bytes im letzten Satz Nullzeichen hinzu, um den letzten Satz zu vervollständigen. Als nächstes haben wir 4 Anweisungen, die 8-Bit-Daten in 6-Bit-für-Bit-Operationen konvertieren.

Zuletzt wandeln wir in der vorletzten Schleife eine Gruppe von 4 6-Bit-Zeichen in base_64 um.

Die letzte Schleife speichert zusätzliche Zeichen, um einen Satz von 4 Bytes zu vervollständigen. Als nächstes haben wir die Dekodierungsfunktion.

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;
}

Diese Funktion nimmt die codierte Nachricht und führt die umgekehrte Operation aus, die die folgenden Schritte umfasst.

  1. Holen Sie sich den Index jedes Zeichens aus dem Zeichensatz base_64 und erstellen Sie einen Satz von 4 Bytes.
  2. Fügen Sie wieder 0 zu den Sonderzeichen hinzu, die wir im Codierungsprozess gespeichert haben.
  3. Konvertiere als nächstes einen Satz von 4 Bytes in einen Satz von 3 Bytes durch umgekehrte Bitoperationen (hier gehen wir nicht auf Einzelheiten dieser Operationen ein).
  4. Kombinieren Sie schließlich einen Satz von 3 Bytes, um die kombinierte decodierte Nachricht zu erhalten.

Schließlich haben wir hier einen vollständigen Code mit 2 Beispielen für Codierung und Codierung.

#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;
}

Im Wesentlichen haben wir 2 Datensätze. Im ersten Satz haben wir numerische Zeichen; im nächsten Satz haben wir numerische Werte; Daher geben wir in der letzten Schleife die decodierte Nachricht mit Typumwandlung in eine ganze Zahl aus.

Ausgang:

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

Der erste Satz hat 4 Zeichen (4 Bytes), und die codierte Nachricht Njc4OQ^^ hat 6 Zeichen (die letzten 2 Zeichen sind extra). Im zweiten Satz gibt es 3 Bytes und die verschlüsselte Nachricht BBBA hat 4 Bytes.

In der base_64-Codierung hat jedes Zeichen maximal 6 Bits auf 1 gesetzt, wobei wir entsprechende 64 base_64-Primärzeichen haben. Ebenso erfordert die verschlüsselte Nachricht 33 % mehr Speicherplatz als ASCII.

Trotz zusätzlichem Speicherplatz besteht der Vorteil darin, Sonderzeichen zu verwalten. Daher überträgt dieses Codierungsschema Daten und hält die Integrität intakt.