Mam dane binarne w zmiennej bez znaku. Muszę przekonwertować je na PEM base64 w c. Szukałem w bibliotece openssl, ale nie mogłem znaleźć żadnej funkcji. Czy ktoś ma jakiś pomysł?
Należy pamiętać, że nie powoduje to żadnego sprawdzania błędów podczas dekodowania - przetworzone zostaną dane zakodowane w formacie innym niż base 64.
Nie ma sensu używać tego, jeśli istnieje biblioteka.
Diego Woitasen
11
Możesz pominąć "zależność" libm i math.h, a także potrzebę wykonywania operacji zmiennoprzecinkowych (które są powolne na niektórych urządzeniach), używając *output_length = ((input_length - 1) / 3) * 4 + 4;na początku base64_encode.
Fabian Henze
9
Zdaję sobie sprawę, że jest to „bez sprawdzania błędów”, ale szczególnie zauważ, że chociaż tablica dekodowania w dekoderze jest tablicą 256, ponieważ znak char jest podpisany na większości architektur, tak naprawdę indeksujesz od -128 do 127. Dowolny znak z wysokim ustawiony bit spowoduje, że będziesz czytać poza przydzieloną pamięcią. Wymuszenie wyszukiwania danych jako znaku bez znaku usuwa to. Nadal wyrzucasz śmieci na śmieci, ale nie przejdziesz do segffortu.
bitmusher,
1
Masz problem z tablicą poza zakresem build_decoding_table. encoding_table[64]aby encoding_table[255]nie istnieją.
bobobobo
4
Dekodowanie nie obsługuje również sytuacji, w której brakuje dopełnienia „=”. Wraz ze wszystkimi innymi błędami całkiem zła implementacja.
Lothar
62
Wiem, że to pytanie jest dość stare, ale byłem zdezorientowany ilością dostarczonych rozwiązań - każde z nich twierdziło, że jest szybsze i lepsze. Złożyłem projekt na githubie, aby porównać kodery i dekodery base64: https://github.com/gaspardpetit/base64/
W tym miejscu nie ograniczyłem się do algorytmów C - jeśli jedna implementacja działa dobrze w C ++, to można ją łatwo przenieść do C. Testy zostały również przeprowadzone w Visual Studio 2015. Jeśli ktoś chce zaktualizować tę odpowiedź wynikami z clang / gcc, bądź moim gościem.
Naprawdę nie sądzę, że std :: string i pozostałe używane funkcje są częściami ANSI C. Pytanie z pytaniem o kod w C i otagowane C, otrzymuje najbardziej pozytywną odpowiedź w C ++.
SF.
6
Cytując siebie „Nie ograniczałem się do algorytmów C - jeśli jedna implementacja działa dobrze w C ++, można ją łatwo przenieść z powrotem do C”. Dodaj kolejny char* outStrparametr i zapisz do tego bufora zamiast zwracać a, std::stringjeśli chcesz, jest to trywialne. Zanim to opublikowałem, były już tutaj dwie odpowiedzi C ++ z upvotes.
GaspardP
Jeśli ktoś potrzebuje rozwiązania, które działa dobrze zarówno do dekodowania, jak i kodowania bez konieczności pobierania kodu z dwóch miejsc, wybrałbym wersję Apache dla C i rozwiązanie polfosol dla C ++
DaedalusAlpha
@GaspardP Czy dekodowanie Polfosol może być użyte w kodowaniu Jouniego?
Sam Thomas
33
Ale możesz to również zrobić w openssl ( openssl encpolecenie robi to ...), spójrz na BIO_f_base64()funkcję
Wygląda na to, że OP już używa OpenSSL z innego powodu, więc jest to prawdopodobnie najlepszy sposób, aby to zrobić.
joshk0
18
Oto moje rozwiązanie wykorzystujące OpenSSL.
/* A BASE-64 ENCODER AND DECODER USING OPENSSL */#include<openssl/pem.h>#include<string.h> //Only needed for strlen().char *base64encode(constvoid *b64_encode_this, int encode_this_many_bytes){
BIO *b64_bio, *mem_bio; //Declares two OpenSSL BIOs: a base64 filter and a memory BIO.
BUF_MEM *mem_bio_mem_ptr; //Pointer to a "memory BIO" structure holding our base64 data.
b64_bio = BIO_new(BIO_f_base64()); //Initialize our base64 filter BIO.
mem_bio = BIO_new(BIO_s_mem()); //Initialize our memory sink BIO.
BIO_push(b64_bio, mem_bio); //Link the BIOs by creating a filter-sink BIO chain.
BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); //No newlines every 64 characters or less.
BIO_write(b64_bio, b64_encode_this, encode_this_many_bytes); //Records base64 encoded data.
BIO_flush(b64_bio); //Flush data. Necessary for b64 encoding, because of pad characters.
BIO_get_mem_ptr(mem_bio, &mem_bio_mem_ptr); //Store address of mem_bio's memory structure.
BIO_set_close(mem_bio, BIO_NOCLOSE); //Permit access to mem_ptr after BIOs are destroyed.
BIO_free_all(b64_bio); //Destroys all BIOs in chain, starting with b64 (i.e. the 1st one).
BUF_MEM_grow(mem_bio_mem_ptr, (*mem_bio_mem_ptr).length + 1); //Makes space for end null.
(*mem_bio_mem_ptr).data[(*mem_bio_mem_ptr).length] = '\0'; //Adds null-terminator to tail.return (*mem_bio_mem_ptr).data; //Returns base-64 encoded data. (See: "buf_mem_st" struct).
}
char *base64decode(constvoid *b64_decode_this, int decode_this_many_bytes){
BIO *b64_bio, *mem_bio; //Declares two OpenSSL BIOs: a base64 filter and a memory BIO.char *base64_decoded = calloc( (decode_this_many_bytes*3)/4+1, sizeof(char) ); //+1 = null.
b64_bio = BIO_new(BIO_f_base64()); //Initialize our base64 filter BIO.
mem_bio = BIO_new(BIO_s_mem()); //Initialize our memory source BIO.
BIO_write(mem_bio, b64_decode_this, decode_this_many_bytes); //Base64 data saved in source.
BIO_push(b64_bio, mem_bio); //Link the BIOs by creating a filter-source BIO chain.
BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); //Don't require trailing newlines.int decoded_byte_index = 0; //Index where the next base64_decoded byte should be written.while ( 0 < BIO_read(b64_bio, base64_decoded+decoded_byte_index, 1) ){ //Read byte-by-byte.
decoded_byte_index++; //Increment the index until read of BIO decoded data is complete.
} //Once we're done reading decoded data, BIO_read returns -1 even though there's no error.
BIO_free_all(b64_bio); //Destroys all BIOs in chain, starting with b64 (i.e. the 1st one).return base64_decoded; //Returns base-64 decoded data with trailing null terminator.
}
/*Here's one way to base64 encode/decode using the base64encode() and base64decode functions.*/intmain(void){
char data_to_encode[] = "Base64 encode this string!"; //The string we will base-64 encode.int bytes_to_encode = strlen(data_to_encode); //Number of bytes in string to base64 encode.char *base64_encoded = base64encode(data_to_encode, bytes_to_encode); //Base-64 encoding.int bytes_to_decode = strlen(base64_encoded); //Number of bytes in string to base64 decode.char *base64_decoded = base64decode(base64_encoded, bytes_to_decode); //Base-64 decoding.printf("Original character string is: %s\n", data_to_encode); //Prints our initial string.printf("Base-64 encoded string is: %s\n", base64_encoded); //Prints base64 encoded string.printf("Base-64 decoded string is: %s\n", base64_decoded); //Prints base64 decoded string.free(base64_encoded); //Frees up the memory holding our base64 encoded data.free(base64_decoded); //Frees up the memory holding our base64 decoded data.
}
W wierszu „Adds a null-terminator” pojawia się komunikat o błędzie AddressSanitizer, że zapis przepełnia stertę o 1 bajt.
bparker
Dziękuję, poprawiłem ten błąd, a także przeprowadziłem szeroko zakrojone testy z ciągami losowych bajtów o losowej wielkości, aby upewnić się, że kod działa zgodnie z reklamą. :)
schulwitz
1
ŁADNY! Skompilowałem to zcc -o base base.c -lssl -lcrypto . Bez błędów. Wytworzył ten wynik:Original character string is: Base64 encode this string! Base-64 encoded string is: QmFzZTY0IGVuY29kZSB0aGlzIHN0cmluZyE= Base-64 decoded string is: Base64 encode this string!
jasne światło
@schulwitz Mam plik zakodowany jako ciąg znaków za pomocą Pythona, ale kiedy dekoduję ciąg za pomocą Twojej funkcji i próbuję zapisać zdekodowany wynik do pliku (w C), nie odzyskuję tego samego pliku. Zakodowany ciąg jest poprawny. `` `` const unsigned char * jarFile = "<zakodowany plik>"; int main () {print_version (); PLIK * fp; char * out = base64decode (jarFile, strlen (jarFile)); fp = fopen ("plik.jar", "wb"); if (fp == NULL) {printf ("Otwarcie pliku nie powiodło się"); powrót 1; } fwrite (out, sizeof (out), 1, fp); fclose (fp); wolny (out); return 0; } ``
Sam Thomas
1
@SamThomas Używanie strlen działa w moim przykładzie, ponieważ utworzyłem ciąg, w którym istnieje tylko jeden terminator o wartości null (i znajduje się na końcu ciągu). Zobacz: tutorialspoint.com/cprogramming/c_strings.htm Czytanie w jarFile za pomocą strlen nie powiedzie się, ponieważ w środku pliku binarnego prawdopodobnie znajduje się terminator o wartości zerowej, zakłócając wartość bytes_to_decode. Zobacz: stackoverflow.com/questions/24596189/… Znajdź rozmiar pliku w inny sposób: stackoverflow.com/questions/238603/ ...
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
Wszyscy pracownicy są niezbędni
17
libb64 ma interfejsy API C i C ++. Jest to lekka i prawdopodobnie najszybsza publicznie dostępna implementacja. Jest to również dedykowana samodzielna biblioteka kodowania base64, co może być miłe, jeśli nie potrzebujesz wszystkich innych rzeczy, które pochodzą z korzystania z większej biblioteki, takiej jak OpenSSL lub glib.
Uwaga na temat libb64: BUFFERSIZE jest zdefiniowane w pliku make, więc jeśli nie używasz make / cmake, musisz ręcznie zdefiniować go w plikach nagłówkowych, aby mógł się skompilować. Prace / krótko testowane VS2012
Tom,
3
Jak powiedział Tom: #define BUFFERSIZE 16777216możesz zastąpić 65536, jeśli potrzebujesz mniejszego bufora.
jyz
2
Strzec się! Po godzinie debugowania zorientowałem się, że libb64 zakłada, że charjest podpisany w systemie docelowym ... Jest to problem, ponieważ base64_decode_valuemoże zwrócić liczbę ujemną, która jest następnie rzutowana na znak.
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
Wszyscy pracownicy są niezbędni
15
GNU coreutils ma to w lib / base64. Jest trochę nadęty, ale dotyczy rzeczy takich jak EBCDIC. Możesz też bawić się samodzielnie, np.
charbase64_digit(n)unsigned n; {
if (n < 10) return n - '0';
elseif (n < 10 + 26) return n - 'a';
elseif (n < 10 + 26 + 26) return n - 'A';
else assert(0);
return0;
}
unsignedcharbase64_decode_digit(char c){
switch (c) {
case'=' : return62;
case'.' : return63;
default :
if (isdigit(c)) return c - '0';
elseif (islower(c)) return c - 'a' + 10;
elseif (isupper(c)) return c - 'A' + 10 + 26;
else assert(0);
}
return0xff;
}
unsignedbase64_decode(char *s){
char *p;
unsigned n = 0;
for (p = s; *p; p++)
n = 64 * n + base64_decode_digit(*p);
return n;
}
Poznajcie wszystkie osoby po tych prezentach, aby nie mylić „zabawy na własną rękę” z „wdrażaniem standardu”. Yeesh.
Ponadto '+'ma 62 i '/'63 w PEM base64, zgodnie z prośbą OP. Oto lista wariantów kodowania base64 . Nie widzę wariantu kodowania base64 z kolejnością używanych znaków. Ale matematyka stojąca za algorytmem jest poprawna.
Patrick
2
Jak już powiedziano: uważaj, ten algorytm nie jest kompatybilny ze wspólnym base64
Cerber
A co z kodowaniem?
Geremia
15
Potrzebowałem implementacji C ++ działającej na std :: string . Żadna z odpowiedzi nie zaspokajała moich potrzeb, potrzebowałem prostego dwufunkcyjnego rozwiązania do kodowania i dekodowania, ale byłem zbyt leniwy, aby napisać własny kod, więc znalazłem to:
Umieszczenie poniższego kodu na wypadek awarii witryny:
base64.cpp
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger [email protected]
*/#include"base64.h"#include<iostream>staticconststd::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ""abcdefghijklmnopqrstuvwxyz""0123456789+/";
staticinlineboolis_base64(unsignedchar c){
return (isalnum(c) || (c == '+') || (c == '/'));
}
std::stringbase64_encode(unsignedcharconst* bytes_to_encode, unsignedint in_len){
std::string ret;
int i = 0;
int j = 0;
unsignedchar char_array_3[3];
unsignedchar char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while((i++ < 3))
ret += '=';
}
return ret;
}
std::stringbase64_decode(std::stringconst& encoded_string){
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsignedchar char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;
for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
Jest to po prostu bardzo prosta operacja, która zapewnia, że bufor docelowy jest ustawiony na NULL w przypadku, gdy wywołujący nie zrobił tego przed wywołaniem, a jeśli być może dekodowanie się nie powiedzie, zwrócony bufor będzie miał zerową długość. Nie powiedziałem, że debugowałem, śledziłem i profilowałem tę procedurę, to tylko jedna, której używam od lat. :) Kiedy teraz patrzę na to, naprawdę nie musi tam być, więc dlaczego nie nazwiemy tego „ćwiczeniem dla czytelnika”? hehe .. Może po prostu to wyedytuję. Dzięki za zwrócenie uwagi!
LarryF
3
Twoja UnBase64funkcja może naruszyć pamięć po buforze dest, jeśli ten bufor ma dokładny rozmiar wymagany do zdekodowania ciągu zakodowanego metodą base 64. Weźmy na przykład prosty przypadek, w którym próbujesz zdekodować następujący łańcuch zakodowany w oparciu o 64 algorytmy „BQ ==” na pojedynczy bajt BYTE, tj. unsigned char Result = 0; UnBase64(&Result, "BQ==", 4); Spowoduje to uszkodzenie stosu!
Mike Dinescu
3
Tak, spowodował paskudny błąd w naszej aplikacji. Nie polecam.
Harald Maassen
Cześć Larry, dziękuję za udostępnienie kodu. To bardzo przydatne!
Federico
4
W przypadku, gdy ludzie potrzebują rozwiązania C ++, umieszczam to rozwiązanie OpenSSL razem (zarówno do kodowania, jak i dekodowania). Musisz połączyć się z biblioteką „crypto” (którą jest OpenSSL). Zostało to sprawdzone pod kątem wycieków za pomocą valgrind (chociaż możesz dodać dodatkowy kod sprawdzający błędy, aby był nieco lepszy - wiem, że przynajmniej funkcja zapisu powinna sprawdzać zwracaną wartość).
#include<openssl/bio.h>#include<openssl/evp.h>#include<stdlib.h>stringbase64_encode( conststring &str ){
BIO *base64_filter = BIO_new( BIO_f_base64() );
BIO_set_flags( base64_filter, BIO_FLAGS_BASE64_NO_NL );
BIO *bio = BIO_new( BIO_s_mem() );
BIO_set_flags( bio, BIO_FLAGS_BASE64_NO_NL );
bio = BIO_push( base64_filter, bio );
BIO_write( bio, str.c_str(), str.length() );
BIO_flush( bio );
char *new_data;
long bytes_written = BIO_get_mem_data( bio, &new_data );
stringresult( new_data, bytes_written );
BIO_free_all( bio );
return result;
}
stringbase64_decode( conststring &str ){
BIO *bio, *base64_filter, *bio_out;
char inbuf[512];
int inlen;
base64_filter = BIO_new( BIO_f_base64() );
BIO_set_flags( base64_filter, BIO_FLAGS_BASE64_NO_NL );
bio = BIO_new_mem_buf( (void*)str.c_str(), str.length() );
bio = BIO_push( base64_filter, bio );
bio_out = BIO_new( BIO_s_mem() );
while( (inlen = BIO_read(bio, inbuf, 512)) > 0 ){
BIO_write( bio_out, inbuf, inlen );
}
BIO_flush( bio_out );
char *new_data;
long bytes_written = BIO_get_mem_data( bio_out, &new_data );
stringresult( new_data, bytes_written );
BIO_free_all( bio );
BIO_free_all( bio_out );
return result;
}
Nie krępuj się go używać, jeśli pasuje do twojego celu.
Edycja: dodano kod wbudowany na żądanie.
Zwiększenie wydajności uzyskuje się za pomocą tabeli wyszukiwania do kodowania i dekodowania. _UINT8jest unsigned charw większości systemów operacyjnych.
/** Static Base64 character encoding lookup table */constchar CBase64::encodeCharacterTable[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/** Static Base64 character decoding lookup table */constchar CBase64::decodeCharacterTable[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21
,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1};
/*!
\brief Encodes binary data to base 64 character data
\param in The data to encode
\param out The encoded data as characters
*/voidCBase64::Encode(std::istream &in, std::ostringstream &out){
char buff1[3];
char buff2[4];
_UINT8 i=0, j;
while(in.readsome(&buff1[i++], 1))
if (i==3)
{
out << encodeCharacterTable[(buff1[0] & 0xfc) >> 2];
out << encodeCharacterTable[((buff1[0] & 0x03) << 4) + ((buff1[1] & 0xf0) >> 4)];
out << encodeCharacterTable[((buff1[1] & 0x0f) << 2) + ((buff1[2] & 0xc0) >> 6)];
out << encodeCharacterTable[buff1[2] & 0x3f];
i=0;
}
if (--i)
{
for(j=i;j<3;j++) buff1[j] = '\0';
buff2[0] = (buff1[0] & 0xfc) >> 2;
buff2[1] = ((buff1[0] & 0x03) << 4) + ((buff1[1] & 0xf0) >> 4);
buff2[2] = ((buff1[1] & 0x0f) << 2) + ((buff1[2] & 0xc0) >> 6);
buff2[3] = buff1[2] & 0x3f;
for (j=0;j<(i+1);j++) out << encodeCharacterTable[buff2[j]];
while(i++<3) out << '=';
}
}
/*!
\brief Decodes base 64 character data to binary data
\param in The character data to decode
\param out The decoded data
*/voidCBase64::Decode(std::istringstream &in, std::ostream &out){
char buff1[4];
char buff2[4];
_UINT8 i=0, j;
while(in.readsome(&buff2[i], 1) && buff2[i] != '=')
{
if (++i==4)
{
for (i=0;i!=4;i++)
buff2[i] = decodeCharacterTable[buff2[i]];
out << (char)((buff2[0] << 2) + ((buff2[1] & 0x30) >> 4));
out << (char)(((buff2[1] & 0xf) << 4) + ((buff2[2] & 0x3c) >> 2));
out << (char)(((buff2[2] & 0x3) << 6) + buff2[3]);
i=0;
}
}
if (i)
{
for (j=i;j<4;j++) buff2[j] = '\0';
for (j=0;j<4;j++) buff2[j] = decodeCharacterTable[buff2[j]];
buff1[0] = (buff2[0] << 2) + ((buff2[1] & 0x30) >> 4);
buff1[1] = ((buff2[1] & 0xf) << 4) + ((buff2[2] & 0x3c) >> 2);
buff1[2] = ((buff2[2] & 0x3) << 6) + buff2[3];
for (j=0;j<(i-1); j++) out << (char)buff1[j];
}
}
@cpburnz Dodałem teraz przykład w wierszu i komentarz, dlaczego jest szybki, dzięki.
2
Małe ulepszenie kodu od ryyst (który zdobył najwięcej głosów) polega na tym, że nie używa dynamicznie przydzielanej tabeli dekodowania, ale raczej statyczną, wstępnie obliczoną tabelę const. Eliminuje to użycie wskaźnika i inicjalizację tabeli, a także unika wycieku pamięci, jeśli zapomni się wyczyścić tabelę dekodowania za pomocą base64_cleanup () (przy okazji, w base64_cleanup (), po wywołaniu free (decoding_table), należy decoding_table = NULL, w przeciwnym razie przypadkowe wywołanie base64_decode po base64_cleanup () spowoduje awarię lub nieokreślone zachowanie). Innym rozwiązaniem może być użycie std :: unique_ptr ... ale jestem zadowolony z posiadania const char [256] na stosie i unikania w ogóle używania wskaźników - w ten sposób kod wygląda na czystszy i krótszy.
Tabela dekodowania jest obliczana w następujący sposób:
Nie jest to tak łatwe w użyciu, jak inne opcje powyżej. Może być jednak przydatny w systemach wbudowanych, w których chcesz zrzucić duży plik bez przydzielania innego dużego buforu do przechowywania wynikowego ciągu danych w formacie base64. (Szkoda, że datauri nie pozwala określić nazwy pliku).
voiddatauriBase64EncodeBufferless(int (*putchar_fcptr)(int), constchar* type_strptr, constvoid* data_buf, constsize_t dataLength){
constchar base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
constuint8_t *data = (constuint8_t *)data_buf;
size_t x = 0;
uint32_t n = 0;
int padCount = dataLength % 3;
uint8_t n0, n1, n2, n3;
size_t outcount = 0;
size_t line = 0;
putchar_fcptr((int)'d');
putchar_fcptr((int)'a');
putchar_fcptr((int)'t');
putchar_fcptr((int)'a');
putchar_fcptr((int)':');
outcount += 5;
while (*type_strptr != '\0')
{
putchar_fcptr((int)*type_strptr);
type_strptr++;
outcount++;
}
putchar_fcptr((int)';');
putchar_fcptr((int)'b');
putchar_fcptr((int)'a');
putchar_fcptr((int)'s');
putchar_fcptr((int)'e');
putchar_fcptr((int)'6');
putchar_fcptr((int)'4');
putchar_fcptr((int)',');
outcount += 8;
/* increment over the length of the string, three characters at a time */for (x = 0; x < dataLength; x += 3)
{
/* these three 8-bit (ASCII) characters become one 24-bit number */
n = ((uint32_t)data[x]) << 16; //parenthesis needed, compiler depending on flags can do the shifting before conversion to uint32_t, resulting to 0if((x+1) < dataLength)
n += ((uint32_t)data[x+1]) << 8;//parenthesis needed, compiler depending on flags can do the shifting before conversion to uint32_t, resulting to 0if((x+2) < dataLength)
n += data[x+2];
/* this 24-bit number gets separated into four 6-bit numbers */
n0 = (uint8_t)(n >> 18) & 63;
n1 = (uint8_t)(n >> 12) & 63;
n2 = (uint8_t)(n >> 6) & 63;
n3 = (uint8_t)n & 63;
/*
* if we have one byte available, then its encoding is spread
* out over two characters
*/
putchar_fcptr((int)base64chars[n0]);
putchar_fcptr((int)base64chars[n1]);
outcount += 2;
/*
* if we have only two bytes available, then their encoding is
* spread out over three chars
*/if((x+1) < dataLength)
{
putchar_fcptr((int)base64chars[n2]);
outcount += 1;
}
/*
* if we have all three bytes available, then their encoding is spread
* out over four characters
*/if((x+2) < dataLength)
{
putchar_fcptr((int)base64chars[n3]);
outcount += 1;
}
/* Breaking up the line so it's easier to copy and paste */int curr_line = (outcount/80);
if( curr_line != line )
{
line = curr_line;
putchar_fcptr((int)'\r');
putchar_fcptr((int)'\n');
}
}
/*
* create and add padding that is required if we did not have a multiple of 3
* number of characters available
*/if (padCount > 0)
{
for (; padCount < 3; padCount++)
{
putchar_fcptr((int)'=');
}
}
putchar_fcptr((int)'\r');
putchar_fcptr((int)'\n');
}
To rozwiązanie jest oparte na odpowiedzi schulwitza (kodowanie / dekodowanie przy użyciu OpenSSL), ale jest przeznaczone dla C ++ (cóż, oryginalne pytanie dotyczyło C, ale są już tutaj inne odpowiedzi C ++) i wykorzystuje sprawdzanie błędów (więc jest bezpieczniejsze w użyciu) :
#include<openssl/bio.h>std::stringbase64_encode(conststd::string &input){
BIO *p_bio_b64 = nullptr;
BIO *p_bio_mem = nullptr;
try
{
// make chain: p_bio_b64 <--> p_bio_mem
p_bio_b64 = BIO_new(BIO_f_base64());
if (!p_bio_b64) { throwstd::runtime_error("BIO_new failed"); }
BIO_set_flags(p_bio_b64, BIO_FLAGS_BASE64_NO_NL); //No newlines every 64 characters or less
p_bio_mem = BIO_new(BIO_s_mem());
if (!p_bio_mem) { throwstd::runtime_error("BIO_new failed"); }
BIO_push(p_bio_b64, p_bio_mem);
// write input to chain// write sequence: input -->> p_bio_b64 -->> p_bio_memif (BIO_write(p_bio_b64, input.c_str(), input.size()) <= 0)
{ throwstd::runtime_error("BIO_write failed"); }
if (BIO_flush(p_bio_b64) <= 0)
{ throwstd::runtime_error("BIO_flush failed"); }
// get resultchar *p_encoded_data = nullptr;
auto encoded_len = BIO_get_mem_data(p_bio_mem, &p_encoded_data);
if (!p_encoded_data) { throwstd::runtime_error("BIO_get_mem_data failed"); }
std::stringresult(p_encoded_data, encoded_len);
// clean
BIO_free_all(p_bio_b64);
return result;
}
catch (...)
{
if (p_bio_b64) { BIO_free_all(p_bio_b64); }
throw;
}
}
std::stringbase64_decode(conststd::string &input){
BIO *p_bio_mem = nullptr;
BIO *p_bio_b64 = nullptr;
try
{
// make chain: p_bio_b64 <--> p_bio_mem
p_bio_b64 = BIO_new(BIO_f_base64());
if (!p_bio_b64) { throwstd::runtime_error("BIO_new failed"); }
BIO_set_flags(p_bio_b64, BIO_FLAGS_BASE64_NO_NL); //Don't require trailing newlines
p_bio_mem = BIO_new_mem_buf((void*)input.c_str(), input.length());
if (!p_bio_mem) { throwstd::runtime_error("BIO_new failed"); }
BIO_push(p_bio_b64, p_bio_mem);
// read result from chain// read sequence (reverse to write): buf <<-- p_bio_b64 <<-- p_bio_memstd::vector<char> buf((input.size()*3/4)+1);
std::string result;
for (;;)
{
auto nread = BIO_read(p_bio_b64, buf.data(), buf.size());
if (nread < 0) { throwstd::runtime_error("BIO_read failed"); }
if (nread == 0) { break; } // eof
result.append(buf.data(), nread);
}
// clean
BIO_free_all(p_bio_b64);
return result;
}
catch (...)
{
if (p_bio_b64) { BIO_free_all(p_bio_b64); }
throw;
}
}
Zauważ, że base64_decode zwraca pusty ciąg, jeśli wejście jest niepoprawną sekwencją base64 (openssl działa w ten sposób).
hm ... użycie biblioteki openssl do dekodowania / kodowania base64 zajmuje więcej linii kodu niż bezpośrednia implementacja (najlepsza odpowiedź na to pytanie) ...
anton_rh
-2
Oto zoptymalizowana wersja kodera dla zaakceptowanej odpowiedzi, która obsługuje również łamanie linii dla MIME i innych protokołów (podobną optymalizację można zastosować do dekodera):
Odpowiedzi:
Oto ten, którego używam:
#include <stdint.h> #include <stdlib.h> static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; static char *decoding_table = NULL; static int mod_table[] = {0, 2, 1}; char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length) { *output_length = 4 * ((input_length + 2) / 3); char *encoded_data = malloc(*output_length); if (encoded_data == NULL) return NULL; for (int i = 0, j = 0; i < input_length;) { uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; } for (int i = 0; i < mod_table[input_length % 3]; i++) encoded_data[*output_length - 1 - i] = '='; return encoded_data; } unsigned char *base64_decode(const char *data, size_t input_length, size_t *output_length) { if (decoding_table == NULL) build_decoding_table(); if (input_length % 4 != 0) return NULL; *output_length = input_length / 4 * 3; if (data[input_length - 1] == '=') (*output_length)--; if (data[input_length - 2] == '=') (*output_length)--; unsigned char *decoded_data = malloc(*output_length); if (decoded_data == NULL) return NULL; for (int i = 0, j = 0; i < input_length;) { uint32_t sextet_a = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t sextet_b = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t sextet_c = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t sextet_d = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); if (j < *output_length) decoded_data[j++] = (triple >> 2 * 8) & 0xFF; if (j < *output_length) decoded_data[j++] = (triple >> 1 * 8) & 0xFF; if (j < *output_length) decoded_data[j++] = (triple >> 0 * 8) & 0xFF; } return decoded_data; } void build_decoding_table() { decoding_table = malloc(256); for (int i = 0; i < 64; i++) decoding_table[(unsigned char) encoding_table[i]] = i; } void base64_cleanup() { free(decoding_table); }
Należy pamiętać, że nie powoduje to żadnego sprawdzania błędów podczas dekodowania - przetworzone zostaną dane zakodowane w formacie innym niż base 64.
źródło
*output_length = ((input_length - 1) / 3) * 4 + 4;
na początku base64_encode.build_decoding_table
.encoding_table[64]
abyencoding_table[255]
nie istnieją.Wiem, że to pytanie jest dość stare, ale byłem zdezorientowany ilością dostarczonych rozwiązań - każde z nich twierdziło, że jest szybsze i lepsze. Złożyłem projekt na githubie, aby porównać kodery i dekodery base64: https://github.com/gaspardpetit/base64/
W tym miejscu nie ograniczyłem się do algorytmów C - jeśli jedna implementacja działa dobrze w C ++, to można ją łatwo przenieść do C. Testy zostały również przeprowadzone w Visual Studio 2015. Jeśli ktoś chce zaktualizować tę odpowiedź wynikami z clang / gcc, bądź moim gościem.
NAJSZYBSZE KODERY: Dwie najszybsze implementacje kodera, które znalazłem, to Jouni Malinen na http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c i Apache na https://opensource.apple .com / source / QuickTimeStreamingServer / QuickTimeStreamingServer-452 / CommonUtilitiesLib / base64.c .
Oto czas (w mikrosekundach) na zakodowanie 32K danych przy użyciu różnych algorytmów, które testowałem do tej pory:
jounimalinen 25.1544 apache 25.5309 NibbleAndAHalf 38.4165 internetsoftwareconsortium 48.2879 polfosol 48.7955 wikibooks_org_c 51.9659 gnome 74.8188 elegantdice 118.899 libb64 120.601 manuelmartinez 120.801 arduino 126.262 daedalusalpha 126.473 CppCodec 151.866 wikibooks_org_cpp 343.2 adp_gmbh 381.523 LihO 406.693 libcurl 3246.39 user152949 4828.21
(Rozwiązanie René Nyffeneggera, wymienione w innej odpowiedzi na to pytanie, jest tutaj wymienione jako adp_gmbh).
Oto ten od Jouniego Malinena, który nieznacznie zmodyfikowałem, aby zwracał std :: string:
/* * Base64 encoding/decoding (RFC1341) * Copyright (c) 2005-2011, Jouni Malinen <[email protected]> * * This software may be distributed under the terms of the BSD license. * See README for more details. */ // 2016-12-12 - Gaspard Petit : Slightly modified to return a std::string // instead of a buffer allocated with malloc. #include <string> static const unsigned char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /** * base64_encode - Base64 encode * @src: Data to be encoded * @len: Length of the data to be encoded * @out_len: Pointer to output length variable, or %NULL if not used * Returns: Allocated buffer of out_len bytes of encoded data, * or empty string on failure */ std::string base64_encode(const unsigned char *src, size_t len) { unsigned char *out, *pos; const unsigned char *end, *in; size_t olen; olen = 4*((len + 2) / 3); /* 3-byte blocks to 4-byte */ if (olen < len) return std::string(); /* integer overflow */ std::string outStr; outStr.resize(olen); out = (unsigned char*)&outStr[0]; end = src + len; in = src; pos = out; while (end - in >= 3) { *pos++ = base64_table[in[0] >> 2]; *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; *pos++ = base64_table[in[2] & 0x3f]; in += 3; } if (end - in) { *pos++ = base64_table[in[0] >> 2]; if (end - in == 1) { *pos++ = base64_table[(in[0] & 0x03) << 4]; *pos++ = '='; } else { *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = base64_table[(in[1] & 0x0f) << 2]; } *pos++ = '='; } return outStr; }
NAJSZYBSZE DEKODERY: Oto wyniki dekodowania i muszę przyznać, że jestem nieco zaskoczony:
polfosol 45.2335 wikibooks_org_c 74.7347 apache 77.1438 libb64 100.332 gnome 114.511 manuelmartinez 126.579 elegantdice 138.514 daedalusalpha 151.561 jounimalinen 206.163 arduino 335.95 wikibooks_org_cpp 350.437 CppCodec 526.187 internetsoftwareconsortium 862.833 libcurl 1280.27 LihO 1852.4 adp_gmbh 1934.43 user152949 5332.87
Fragment kodu Polfosol z fragmentu dekodowania base64 w języku c ++ jest najszybszy prawie 2x.
Oto kod ze względu na kompletność:
static const int B64index[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; std::string b64decode(const void* data, const size_t len) { unsigned char* p = (unsigned char*)data; int pad = len > 0 && (len % 4 || p[len - 1] == '='); const size_t L = ((len + 3) / 4 - pad) * 4; std::string str(L / 4 * 3 + pad, '\0'); for (size_t i = 0, j = 0; i < L; i += 4) { int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]]; str[j++] = n >> 16; str[j++] = n >> 8 & 0xFF; str[j++] = n & 0xFF; } if (pad) { int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; str[str.size() - 1] = n >> 16; if (len > L + 2 && p[L + 2] != '=') { n |= B64index[p[L + 2]] << 6; str.push_back(n >> 8 & 0xFF); } } return str; }
źródło
char* outStr
parametr i zapisz do tego bufora zamiast zwracać a,std::string
jeśli chcesz, jest to trywialne. Zanim to opublikowałem, były już tutaj dwie odpowiedzi C ++ z upvotes.Ale możesz to również zrobić w openssl (
openssl enc
polecenie robi to ...), spójrz naBIO_f_base64()
funkcjęźródło
Oto moje rozwiązanie wykorzystujące OpenSSL.
/* A BASE-64 ENCODER AND DECODER USING OPENSSL */ #include <openssl/pem.h> #include <string.h> //Only needed for strlen(). char *base64encode (const void *b64_encode_this, int encode_this_many_bytes){ BIO *b64_bio, *mem_bio; //Declares two OpenSSL BIOs: a base64 filter and a memory BIO. BUF_MEM *mem_bio_mem_ptr; //Pointer to a "memory BIO" structure holding our base64 data. b64_bio = BIO_new(BIO_f_base64()); //Initialize our base64 filter BIO. mem_bio = BIO_new(BIO_s_mem()); //Initialize our memory sink BIO. BIO_push(b64_bio, mem_bio); //Link the BIOs by creating a filter-sink BIO chain. BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); //No newlines every 64 characters or less. BIO_write(b64_bio, b64_encode_this, encode_this_many_bytes); //Records base64 encoded data. BIO_flush(b64_bio); //Flush data. Necessary for b64 encoding, because of pad characters. BIO_get_mem_ptr(mem_bio, &mem_bio_mem_ptr); //Store address of mem_bio's memory structure. BIO_set_close(mem_bio, BIO_NOCLOSE); //Permit access to mem_ptr after BIOs are destroyed. BIO_free_all(b64_bio); //Destroys all BIOs in chain, starting with b64 (i.e. the 1st one). BUF_MEM_grow(mem_bio_mem_ptr, (*mem_bio_mem_ptr).length + 1); //Makes space for end null. (*mem_bio_mem_ptr).data[(*mem_bio_mem_ptr).length] = '\0'; //Adds null-terminator to tail. return (*mem_bio_mem_ptr).data; //Returns base-64 encoded data. (See: "buf_mem_st" struct). } char *base64decode (const void *b64_decode_this, int decode_this_many_bytes){ BIO *b64_bio, *mem_bio; //Declares two OpenSSL BIOs: a base64 filter and a memory BIO. char *base64_decoded = calloc( (decode_this_many_bytes*3)/4+1, sizeof(char) ); //+1 = null. b64_bio = BIO_new(BIO_f_base64()); //Initialize our base64 filter BIO. mem_bio = BIO_new(BIO_s_mem()); //Initialize our memory source BIO. BIO_write(mem_bio, b64_decode_this, decode_this_many_bytes); //Base64 data saved in source. BIO_push(b64_bio, mem_bio); //Link the BIOs by creating a filter-source BIO chain. BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); //Don't require trailing newlines. int decoded_byte_index = 0; //Index where the next base64_decoded byte should be written. while ( 0 < BIO_read(b64_bio, base64_decoded+decoded_byte_index, 1) ){ //Read byte-by-byte. decoded_byte_index++; //Increment the index until read of BIO decoded data is complete. } //Once we're done reading decoded data, BIO_read returns -1 even though there's no error. BIO_free_all(b64_bio); //Destroys all BIOs in chain, starting with b64 (i.e. the 1st one). return base64_decoded; //Returns base-64 decoded data with trailing null terminator. } /*Here's one way to base64 encode/decode using the base64encode() and base64decode functions.*/ int main(void){ char data_to_encode[] = "Base64 encode this string!"; //The string we will base-64 encode. int bytes_to_encode = strlen(data_to_encode); //Number of bytes in string to base64 encode. char *base64_encoded = base64encode(data_to_encode, bytes_to_encode); //Base-64 encoding. int bytes_to_decode = strlen(base64_encoded); //Number of bytes in string to base64 decode. char *base64_decoded = base64decode(base64_encoded, bytes_to_decode); //Base-64 decoding. printf("Original character string is: %s\n", data_to_encode); //Prints our initial string. printf("Base-64 encoded string is: %s\n", base64_encoded); //Prints base64 encoded string. printf("Base-64 decoded string is: %s\n", base64_decoded); //Prints base64 decoded string. free(base64_encoded); //Frees up the memory holding our base64 encoded data. free(base64_decoded); //Frees up the memory holding our base64 decoded data. }
źródło
cc -o base base.c -lssl -lcrypto
. Bez błędów. Wytworzył ten wynik:Original character string is: Base64 encode this string! Base-64 encoded string is: QmFzZTY0IGVuY29kZSB0aGlzIHN0cmluZyE= Base-64 decoded string is: Base64 encode this string!
glib ma funkcje do kodowania base64: https://developer.gnome.org/glib/stable/glib-Base64-Encoding.html
źródło
libb64 ma interfejsy API C i C ++. Jest to lekka i prawdopodobnie najszybsza publicznie dostępna implementacja. Jest to również dedykowana samodzielna biblioteka kodowania base64, co może być miłe, jeśli nie potrzebujesz wszystkich innych rzeczy, które pochodzą z korzystania z większej biblioteki, takiej jak OpenSSL lub glib.
źródło
#define BUFFERSIZE 16777216
możesz zastąpić 65536, jeśli potrzebujesz mniejszego bufora.char
jest podpisany w systemie docelowym ... Jest to problem, ponieważbase64_decode_value
może zwrócić liczbę ujemną, która jest następnie rzutowana na znak.GNU coreutils ma to w lib / base64. Jest trochę nadęty, ale dotyczy rzeczy takich jak EBCDIC. Możesz też bawić się samodzielnie, np.
char base64_digit (n) unsigned n; { if (n < 10) return n - '0'; else if (n < 10 + 26) return n - 'a'; else if (n < 10 + 26 + 26) return n - 'A'; else assert(0); return 0; } unsigned char base64_decode_digit(char c) { switch (c) { case '=' : return 62; case '.' : return 63; default : if (isdigit(c)) return c - '0'; else if (islower(c)) return c - 'a' + 10; else if (isupper(c)) return c - 'A' + 10 + 26; else assert(0); } return 0xff; } unsigned base64_decode(char *s) { char *p; unsigned n = 0; for (p = s; *p; p++) n = 64 * n + base64_decode_digit(*p); return n; }
Poznajcie wszystkie osoby po tych prezentach, aby nie mylić „zabawy na własną rękę” z „wdrażaniem standardu”. Yeesh.
źródło
'+'
ma 62 i'/'
63 w PEM base64, zgodnie z prośbą OP. Oto lista wariantów kodowania base64 . Nie widzę wariantu kodowania base64 z kolejnością używanych znaków. Ale matematyka stojąca za algorytmem jest poprawna.Potrzebowałem implementacji C ++ działającej na std :: string . Żadna z odpowiedzi nie zaspokajała moich potrzeb, potrzebowałem prostego dwufunkcyjnego rozwiązania do kodowania i dekodowania, ale byłem zbyt leniwy, aby napisać własny kod, więc znalazłem to:
http://www.adp-gmbh.ch/cpp/common/base64.html
Kredyty na kod idą do René Nyffeneggera.
Umieszczenie poniższego kodu na wypadek awarii witryny:
base64.cpp
/* base64.cpp and base64.h Copyright (C) 2004-2008 René Nyffenegger This source code is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this source code must not be misrepresented; you must not claim that you wrote the original source code. If you use this source code in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original source code. 3. This notice may not be removed or altered from any source distribution. René Nyffenegger [email protected] */ #include "base64.h" #include <iostream> static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { std::string ret; int i = 0; int j = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; while (in_len--) { char_array_3[i++] = *(bytes_to_encode++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for(i = 0; (i <4) ; i++) ret += base64_chars[char_array_4[i]]; i = 0; } } if (i) { for(j = i; j < 3; j++) char_array_3[j] = '\0'; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; while((i++ < 3)) ret += '='; } return ret; } std::string base64_decode(std::string const& encoded_string) { int in_len = encoded_string.size(); int i = 0; int j = 0; int in_ = 0; unsigned char char_array_4[4], char_array_3[3]; std::string ret; while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_]; in_++; if (i ==4) { for (i = 0; i <4; i++) char_array_4[i] = base64_chars.find(char_array_4[i]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; for (i = 0; (i < 3); i++) ret += char_array_3[i]; i = 0; } } if (i) { for (j = i; j <4; j++) char_array_4[j] = 0; for (j = 0; j <4; j++) char_array_4[j] = base64_chars.find(char_array_4[j]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; } return ret; }
base64.h
#include <string> std::string base64_encode(unsigned char const* , unsigned int len); std::string base64_decode(std::string const& s);
Stosowanie
const std::string s = "test"; std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>(s.c_str()), s.length()); std::string decoded = base64_decode(encoded);
źródło
Oto dekoder, którego używam od lat ...
static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const int BASE64_INPUT_SIZE = 57; BOOL isbase64(char c) { return c && strchr(table, c) != NULL; } inline char value(char c) { const char *p = strchr(table, c); if(p) { return p-table; } else { return 0; } } int UnBase64(unsigned char *dest, const unsigned char *src, int srclen) { *dest = 0; if(*src == 0) { return 0; } unsigned char *p = dest; do { char a = value(src[0]); char b = value(src[1]); char c = value(src[2]); char d = value(src[3]); *p++ = (a << 2) | (b >> 4); *p++ = (b << 4) | (c >> 2); *p++ = (c << 6) | d; if(!isbase64(src[1])) { p -= 2; break; } else if(!isbase64(src[2])) { p -= 2; break; } else if(!isbase64(src[3])) { p--; break; } src += 4; while(*src && (*src == 13 || *src == 10)) src++; } while(srclen-= 4); *p = 0; return p-dest; }
źródło
UnBase64
funkcja może naruszyć pamięć po buforze dest, jeśli ten bufor ma dokładny rozmiar wymagany do zdekodowania ciągu zakodowanego metodą base 64. Weźmy na przykład prosty przypadek, w którym próbujesz zdekodować następujący łańcuch zakodowany w oparciu o 64 algorytmy „BQ ==” na pojedynczy bajt BYTE, tj.unsigned char Result = 0; UnBase64(&Result, "BQ==", 4);
Spowoduje to uszkodzenie stosu!W przypadku, gdy ludzie potrzebują rozwiązania C ++, umieszczam to rozwiązanie OpenSSL razem (zarówno do kodowania, jak i dekodowania). Musisz połączyć się z biblioteką „crypto” (którą jest OpenSSL). Zostało to sprawdzone pod kątem wycieków za pomocą valgrind (chociaż możesz dodać dodatkowy kod sprawdzający błędy, aby był nieco lepszy - wiem, że przynajmniej funkcja zapisu powinna sprawdzać zwracaną wartość).
#include <openssl/bio.h> #include <openssl/evp.h> #include <stdlib.h> string base64_encode( const string &str ){ BIO *base64_filter = BIO_new( BIO_f_base64() ); BIO_set_flags( base64_filter, BIO_FLAGS_BASE64_NO_NL ); BIO *bio = BIO_new( BIO_s_mem() ); BIO_set_flags( bio, BIO_FLAGS_BASE64_NO_NL ); bio = BIO_push( base64_filter, bio ); BIO_write( bio, str.c_str(), str.length() ); BIO_flush( bio ); char *new_data; long bytes_written = BIO_get_mem_data( bio, &new_data ); string result( new_data, bytes_written ); BIO_free_all( bio ); return result; } string base64_decode( const string &str ){ BIO *bio, *base64_filter, *bio_out; char inbuf[512]; int inlen; base64_filter = BIO_new( BIO_f_base64() ); BIO_set_flags( base64_filter, BIO_FLAGS_BASE64_NO_NL ); bio = BIO_new_mem_buf( (void*)str.c_str(), str.length() ); bio = BIO_push( base64_filter, bio ); bio_out = BIO_new( BIO_s_mem() ); while( (inlen = BIO_read(bio, inbuf, 512)) > 0 ){ BIO_write( bio_out, inbuf, inlen ); } BIO_flush( bio_out ); char *new_data; long bytes_written = BIO_get_mem_data( bio_out, &new_data ); string result( new_data, bytes_written ); BIO_free_all( bio ); BIO_free_all( bio_out ); return result; }
źródło
Napisałem jeden do użytku z C ++, jest bardzo szybki, działa ze strumieniami, darmowy i open source:
https://tmplusplus.svn.sourceforge.net/svnroot/tmplusplus/trunk/src/
Nie krępuj się go używać, jeśli pasuje do twojego celu.
Edycja: dodano kod wbudowany na żądanie.
Zwiększenie wydajności uzyskuje się za pomocą tabeli wyszukiwania do kodowania i dekodowania.
_UINT8
jestunsigned char
w większości systemów operacyjnych./** Static Base64 character encoding lookup table */ const char CBase64::encodeCharacterTable[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /** Static Base64 character decoding lookup table */ const char CBase64::decodeCharacterTable[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21 ,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1}; /*! \brief Encodes binary data to base 64 character data \param in The data to encode \param out The encoded data as characters */ void CBase64::Encode(std::istream &in, std::ostringstream &out) { char buff1[3]; char buff2[4]; _UINT8 i=0, j; while(in.readsome(&buff1[i++], 1)) if (i==3) { out << encodeCharacterTable[(buff1[0] & 0xfc) >> 2]; out << encodeCharacterTable[((buff1[0] & 0x03) << 4) + ((buff1[1] & 0xf0) >> 4)]; out << encodeCharacterTable[((buff1[1] & 0x0f) << 2) + ((buff1[2] & 0xc0) >> 6)]; out << encodeCharacterTable[buff1[2] & 0x3f]; i=0; } if (--i) { for(j=i;j<3;j++) buff1[j] = '\0'; buff2[0] = (buff1[0] & 0xfc) >> 2; buff2[1] = ((buff1[0] & 0x03) << 4) + ((buff1[1] & 0xf0) >> 4); buff2[2] = ((buff1[1] & 0x0f) << 2) + ((buff1[2] & 0xc0) >> 6); buff2[3] = buff1[2] & 0x3f; for (j=0;j<(i+1);j++) out << encodeCharacterTable[buff2[j]]; while(i++<3) out << '='; } } /*! \brief Decodes base 64 character data to binary data \param in The character data to decode \param out The decoded data */ void CBase64::Decode(std::istringstream &in, std::ostream &out) { char buff1[4]; char buff2[4]; _UINT8 i=0, j; while(in.readsome(&buff2[i], 1) && buff2[i] != '=') { if (++i==4) { for (i=0;i!=4;i++) buff2[i] = decodeCharacterTable[buff2[i]]; out << (char)((buff2[0] << 2) + ((buff2[1] & 0x30) >> 4)); out << (char)(((buff2[1] & 0xf) << 4) + ((buff2[2] & 0x3c) >> 2)); out << (char)(((buff2[2] & 0x3) << 6) + buff2[3]); i=0; } } if (i) { for (j=i;j<4;j++) buff2[j] = '\0'; for (j=0;j<4;j++) buff2[j] = decodeCharacterTable[buff2[j]]; buff1[0] = (buff2[0] << 2) + ((buff2[1] & 0x30) >> 4); buff1[1] = ((buff2[1] & 0xf) << 4) + ((buff2[2] & 0x3c) >> 2); buff1[2] = ((buff2[2] & 0x3) << 6) + buff2[3]; for (j=0;j<(i-1); j++) out << (char)buff1[j]; } }
źródło
Małe ulepszenie kodu od ryyst (który zdobył najwięcej głosów) polega na tym, że nie używa dynamicznie przydzielanej tabeli dekodowania, ale raczej statyczną, wstępnie obliczoną tabelę const. Eliminuje to użycie wskaźnika i inicjalizację tabeli, a także unika wycieku pamięci, jeśli zapomni się wyczyścić tabelę dekodowania za pomocą base64_cleanup () (przy okazji, w base64_cleanup (), po wywołaniu free (decoding_table), należy decoding_table = NULL, w przeciwnym razie przypadkowe wywołanie base64_decode po base64_cleanup () spowoduje awarię lub nieokreślone zachowanie). Innym rozwiązaniem może być użycie std :: unique_ptr ... ale jestem zadowolony z posiadania const char [256] na stosie i unikania w ogóle używania wskaźników - w ten sposób kod wygląda na czystszy i krótszy.
Tabela dekodowania jest obliczana w następujący sposób:
const char encoding_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; unsigned char decoding_table[256]; for (int i = 0; i < 256; i++) decoding_table[i] = '\0'; for (int i = 0; i < 64; i++) decoding_table[(unsigned char)encoding_table[i]] = i; for (int i = 0; i < 256; i++) cout << "0x" << (int(decoding_table[i]) < 16 ? "0" : "") << hex << int(decoding_table[i]) << (i != 255 ? "," : "") << ((i+1) % 16 == 0 ? '\n' : '\0'); cin.ignore();
a zmodyfikowany kod, którego używam, to:
static const char encoding_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; static const unsigned char decoding_table[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; char* base64_encode(const unsigned char *data, size_t input_length, size_t &output_length) { const int mod_table[] = { 0, 2, 1 }; output_length = 4 * ((input_length + 2) / 3); char *encoded_data = (char*)malloc(output_length); if (encoded_data == nullptr) return nullptr; for (int i = 0, j = 0; i < input_length;) { uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; } for (int i = 0; i < mod_table[input_length % 3]; i++) encoded_data[output_length - 1 - i] = '='; return encoded_data; }; unsigned char* base64_decode(const char *data, size_t input_length, size_t &output_length) { if (input_length % 4 != 0) return nullptr; output_length = input_length / 4 * 3; if (data[input_length - 1] == '=') (output_length)--; if (data[input_length - 2] == '=') (output_length)--; unsigned char* decoded_data = (unsigned char*)malloc(output_length); if (decoded_data == nullptr) return nullptr; for (int i = 0, j = 0; i < input_length;) { uint32_t sextet_a = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t sextet_b = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t sextet_c = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t sextet_d = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]]; uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); if (j < output_length) decoded_data[j++] = (triple >> 2 * 8) & 0xFF; if (j < output_length) decoded_data[j++] = (triple >> 1 * 8) & 0xFF; if (j < output_length) decoded_data[j++] = (triple >> 0 * 8) & 0xFF; } return decoded_data; };
źródło
EVP_EncodeBlock
IEVP_DecodeBlock
funkcje sprawiają, że bardzo proste:#include <stdio.h> #include <stdlib.h> #include <openssl/evp.h> char *base64(const unsigned char *input, int length) { const int pl = 4*((length+2)/3); char *output = calloc(pl+1, 1); //+1 for the terminating null that EVP_EncodeBlock adds on const int ol = EVP_EncodeBlock(output, input, length); if (ol != pl) { fprintf(stderr, "Whoops, encode predicted %d but we got %d\n", pl, ol); } return output; } unsigned char *decode64(const char *input, int length) { const int pl = 3*length/4; unsigned char *output = calloc(pl+1, 1); const int ol = EVP_DecodeBlock(output, input, length); if (pl != ol) { fprintf(stderr, "Whoops, decode predicted %d but we got %d\n", pl, ol); } return output; }
źródło
Jest to dekoder, który został specjalnie napisany, aby uniknąć potrzeby stosowania bufora, zapisując bezpośrednio do funkcji putchar. Opiera się to na implementacji wikibooka https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C
Nie jest to tak łatwe w użyciu, jak inne opcje powyżej. Może być jednak przydatny w systemach wbudowanych, w których chcesz zrzucić duży plik bez przydzielania innego dużego buforu do przechowywania wynikowego ciągu danych w formacie base64. (Szkoda, że datauri nie pozwala określić nazwy pliku).
void datauriBase64EncodeBufferless(int (*putchar_fcptr)(int), const char* type_strptr, const void* data_buf, const size_t dataLength) { const char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const uint8_t *data = (const uint8_t *)data_buf; size_t x = 0; uint32_t n = 0; int padCount = dataLength % 3; uint8_t n0, n1, n2, n3; size_t outcount = 0; size_t line = 0; putchar_fcptr((int)'d'); putchar_fcptr((int)'a'); putchar_fcptr((int)'t'); putchar_fcptr((int)'a'); putchar_fcptr((int)':'); outcount += 5; while (*type_strptr != '\0') { putchar_fcptr((int)*type_strptr); type_strptr++; outcount++; } putchar_fcptr((int)';'); putchar_fcptr((int)'b'); putchar_fcptr((int)'a'); putchar_fcptr((int)'s'); putchar_fcptr((int)'e'); putchar_fcptr((int)'6'); putchar_fcptr((int)'4'); putchar_fcptr((int)','); outcount += 8; /* increment over the length of the string, three characters at a time */ for (x = 0; x < dataLength; x += 3) { /* these three 8-bit (ASCII) characters become one 24-bit number */ n = ((uint32_t)data[x]) << 16; //parenthesis needed, compiler depending on flags can do the shifting before conversion to uint32_t, resulting to 0 if((x+1) < dataLength) n += ((uint32_t)data[x+1]) << 8;//parenthesis needed, compiler depending on flags can do the shifting before conversion to uint32_t, resulting to 0 if((x+2) < dataLength) n += data[x+2]; /* this 24-bit number gets separated into four 6-bit numbers */ n0 = (uint8_t)(n >> 18) & 63; n1 = (uint8_t)(n >> 12) & 63; n2 = (uint8_t)(n >> 6) & 63; n3 = (uint8_t)n & 63; /* * if we have one byte available, then its encoding is spread * out over two characters */ putchar_fcptr((int)base64chars[n0]); putchar_fcptr((int)base64chars[n1]); outcount += 2; /* * if we have only two bytes available, then their encoding is * spread out over three chars */ if((x+1) < dataLength) { putchar_fcptr((int)base64chars[n2]); outcount += 1; } /* * if we have all three bytes available, then their encoding is spread * out over four characters */ if((x+2) < dataLength) { putchar_fcptr((int)base64chars[n3]); outcount += 1; } /* Breaking up the line so it's easier to copy and paste */ int curr_line = (outcount/80); if( curr_line != line ) { line = curr_line; putchar_fcptr((int)'\r'); putchar_fcptr((int)'\n'); } } /* * create and add padding that is required if we did not have a multiple of 3 * number of characters available */ if (padCount > 0) { for (; padCount < 3; padCount++) { putchar_fcptr((int)'='); } } putchar_fcptr((int)'\r'); putchar_fcptr((int)'\n'); }
Oto test
#include <stdio.h> #include <stdint.h> #include <string.h> int main(void) { char str[] = "test"; datauriBase64EncodeBufferless(putchar, "text/plain;charset=utf-8", str, strlen(str)); return 0; }
Oczekiwany wynik:
data:text/plain;charset=utf-8;base64,dGVzdA==
źródło
To rozwiązanie jest oparte na odpowiedzi schulwitza (kodowanie / dekodowanie przy użyciu OpenSSL), ale jest przeznaczone dla C ++ (cóż, oryginalne pytanie dotyczyło C, ale są już tutaj inne odpowiedzi C ++) i wykorzystuje sprawdzanie błędów (więc jest bezpieczniejsze w użyciu) :
#include <openssl/bio.h> std::string base64_encode(const std::string &input) { BIO *p_bio_b64 = nullptr; BIO *p_bio_mem = nullptr; try { // make chain: p_bio_b64 <--> p_bio_mem p_bio_b64 = BIO_new(BIO_f_base64()); if (!p_bio_b64) { throw std::runtime_error("BIO_new failed"); } BIO_set_flags(p_bio_b64, BIO_FLAGS_BASE64_NO_NL); //No newlines every 64 characters or less p_bio_mem = BIO_new(BIO_s_mem()); if (!p_bio_mem) { throw std::runtime_error("BIO_new failed"); } BIO_push(p_bio_b64, p_bio_mem); // write input to chain // write sequence: input -->> p_bio_b64 -->> p_bio_mem if (BIO_write(p_bio_b64, input.c_str(), input.size()) <= 0) { throw std::runtime_error("BIO_write failed"); } if (BIO_flush(p_bio_b64) <= 0) { throw std::runtime_error("BIO_flush failed"); } // get result char *p_encoded_data = nullptr; auto encoded_len = BIO_get_mem_data(p_bio_mem, &p_encoded_data); if (!p_encoded_data) { throw std::runtime_error("BIO_get_mem_data failed"); } std::string result(p_encoded_data, encoded_len); // clean BIO_free_all(p_bio_b64); return result; } catch (...) { if (p_bio_b64) { BIO_free_all(p_bio_b64); } throw; } } std::string base64_decode(const std::string &input) { BIO *p_bio_mem = nullptr; BIO *p_bio_b64 = nullptr; try { // make chain: p_bio_b64 <--> p_bio_mem p_bio_b64 = BIO_new(BIO_f_base64()); if (!p_bio_b64) { throw std::runtime_error("BIO_new failed"); } BIO_set_flags(p_bio_b64, BIO_FLAGS_BASE64_NO_NL); //Don't require trailing newlines p_bio_mem = BIO_new_mem_buf((void*)input.c_str(), input.length()); if (!p_bio_mem) { throw std::runtime_error("BIO_new failed"); } BIO_push(p_bio_b64, p_bio_mem); // read result from chain // read sequence (reverse to write): buf <<-- p_bio_b64 <<-- p_bio_mem std::vector<char> buf((input.size()*3/4)+1); std::string result; for (;;) { auto nread = BIO_read(p_bio_b64, buf.data(), buf.size()); if (nread < 0) { throw std::runtime_error("BIO_read failed"); } if (nread == 0) { break; } // eof result.append(buf.data(), nread); } // clean BIO_free_all(p_bio_b64); return result; } catch (...) { if (p_bio_b64) { BIO_free_all(p_bio_b64); } throw; } }
Zauważ, że base64_decode zwraca pusty ciąg, jeśli wejście jest niepoprawną sekwencją base64 (openssl działa w ten sposób).
źródło
Oto zoptymalizowana wersja kodera dla zaakceptowanej odpowiedzi, która obsługuje również łamanie linii dla MIME i innych protokołów (podobną optymalizację można zastosować do dekodera):
char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length, bool addLineBreaks) *output_length = 4 * ((input_length + 2) / 3); if (addLineBreaks) *output_length += *output_length / 38; // CRLF after each 76 chars char *encoded_data = malloc(*output_length); if (encoded_data == NULL) return NULL; UInt32 octet_a; UInt32 octet_b; UInt32 octet_c; UInt32 triple; int lineCount = 0; int sizeMod = size - (size % 3); // check if there is a partial triplet // adding all octet triplets, before partial last triplet for (; offset < sizeMod; ) { octet_a = data[offset++]; octet_b = data[offset++]; octet_c = data[offset++]; triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; encoded_data[mBufferPos++] = encoding_table[(triple >> 3 * 6) & 0x3F]; encoded_data[mBufferPos++] = encoding_table[(triple >> 2 * 6) & 0x3F]; encoded_data[mBufferPos++] = encoding_table[(triple >> 1 * 6) & 0x3F]; encoded_data[mBufferPos++] = encoding_table[(triple >> 0 * 6) & 0x3F]; if (addLineBreaks) { if (++lineCount == 19) { encoded_data[mBufferPos++] = 13; encoded_data[mBufferPos++] = 10; lineCount = 0; } } } // last bytes if (sizeMod < size) { octet_a = data[offset++]; // first octect always added octet_b = offset < size ? data[offset++] : (UInt32)0; // conditional 2nd octet octet_c = (UInt32)0; // last character is definitely padded triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; encoded_data[mBufferPos++] = encoding_table[(triple >> 3 * 6) & 0x3F]; encoded_data[mBufferPos++] = encoding_table[(triple >> 2 * 6) & 0x3F]; encoded_data[mBufferPos++] = encoding_table[(triple >> 1 * 6) & 0x3F]; encoded_data[mBufferPos++] = encoding_table[(triple >> 0 * 6) & 0x3F]; // add padding '=' sizeMod = size % 3; // last character is definitely padded encoded_data[mBufferPos - 1] = (byte)'='; if (sizeMod == 1) encoded_data[mBufferPos - 2] = (byte)'='; } }
źródło