Come faccio a codificare (decodificare) base64 in C?


131

Ho dati binari in una variabile char senza segno. Devo convertirli in PEM base64 in c. Ho cercato nella libreria openssl ma non sono riuscito a trovare alcuna funzione. Qualcuno ha qualche idea?

c  base64 

11
Ho un repository github con funzioni testate base64 e unbase64. L'unica intestazione di cui hai bisogno è base64.h
bobobobo

Sfortunatamente la maggior parte delle risposte qui sono completamente fuori tema. C ++ non è C.
Joe Coder

Risposte:


106

Ecco quello che sto usando:

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

Tieni presente che questo non esegue alcun controllo degli errori durante la decodifica: i dati codificati non basati su 64 verranno elaborati.


7
Non ha senso usarlo se c'è una libreria.
Diego Woitasen,

11
Puoi saltare la "dipendenza" di libm e math.h nonché la necessità di operazioni in virgola mobile (che sono lente su alcuni componenti hardware), usando *output_length = ((input_length - 1) / 3) * 4 + 4;all'inizio di base64_encode.
Fabian Henze,

9
Mi rendo conto che non si tratta di "nessun controllo degli errori", ma in particolare noto che sebbene la tabella di decodifica nel decodificatore sia un array di 256, poiché il carattere è firmato sulla maggior parte delle architetture, stai indicizzando da -128 a 127. Qualsiasi carattere con il il bit impostato ti farà leggere fuori dalla memoria allocata. Forzare la ricerca di dati come un carattere senza segno lo cancella. Ottieni ancora immondizia per immondizia, ma non ti segfa.
Bitmusher,

1
Si è verificato un problema fuori campo dell'array build_decoding_table. encoding_table[64]per encoding_table[255]non esistono.
bobobobo,

3
La decodifica inoltre non gestisce la situazione in cui manca il padding "=". Insieme a tutti gli altri errori, un'implementazione piuttosto scadente.
Lothar,

56

So che questa domanda è piuttosto vecchia, ma mi stavo confondendo per la quantità di soluzioni fornite, ognuna delle quali sosteneva di essere più veloce e migliore. Ho messo insieme un progetto su github per confrontare gli encoder e i decodificatori base64: https://github.com/gaspardpetit/base64/

A questo punto, non mi sono limitato agli algoritmi C: se un'implementazione funziona bene in C ++, può essere facilmente trasferita in C. Anche i test sono stati condotti utilizzando Visual Studio 2015. Se qualcuno vuole aggiornare questa risposta con i risultati di clang / gcc, sii mio ospite.

ENCODER PIÙ VELOCI : le due implementazioni di encoder più veloci che ho trovato sono state quelle di Jouni Malinen su http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c e l'Apache su https://opensource.apple .com / source / QuickTimeStreamingServer / QuickTimeStreamingServer-452 / CommonUtilitiesLib / base64.c .

Ecco il tempo (in microsecondi) per codificare 32K di dati usando i diversi algoritmi che ho testato finora:

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

(La soluzione di René Nyffenegger, accreditata in un'altra risposta a questa domanda, è elencata qui come adp_gmbh).

Ecco quello di Jouni Malinen che ho leggermente modificato per restituire uno std :: string:

/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
*
* 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;
}

DECODIFICATORI PIÙ VELOCI: ecco i risultati della decodifica e devo ammettere che sono un po 'sorpreso:

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

Lo snippet di Polfosol dallo snippet di decodifica base64 in c ++ è il più veloce di un fattore quasi 2x.

Ecco il codice per completezza:

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

18
Non credo davvero che std :: string e il resto delle funzioni che hai usato siano parti di ANSI C. La domanda che richiede il codice C e taggata C ottiene la risposta più votata in C ++.
SF.

4
Citando "Non mi sono limitato agli algoritmi C: se un'implementazione funziona bene in C ++, può essere facilmente trasferita in C". Aggiungi un altro char* outStrparametro e scrivi su quel buffer invece di restituire un std::stringse vuoi, è banale da fare. Prima di pubblicare questo, c'erano già due risposte C ++ con voti positivi qui.
GaspardP

Se si desidera una soluzione che funzioni bene sia per la decodifica che per la codifica senza dover prendere il codice da due punti, sceglierei la versione apache per C e la soluzione polfosol per C ++
DaedalusAlpha

@GaspardP La decodifica di Polfosol può essere utilizzata sulla codifica di Jouni?
Sam Thomas,

33

Ma puoi anche farlo in openssl ( openssl enccomando lo fa ....), guarda la BIO_f_base64()funzione


Sembra che l'OP stia già usando OpenSSL per qualche altro motivo, quindi questo è probabilmente il modo migliore per farlo.
joshk0

18

Ecco la mia soluzione usando 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.
}

2
Nella riga "Aggiunge un terminatore null" ottengo un errore AddressSanitizer che la scrittura trabocca l'heap di 1 byte.
bparker,

Grazie, ho corretto l'errore, oltre a fare test approfonditi con stringhe di byte casuali di dimensioni casuali per assicurarmi che il codice funzioni come pubblicizzato. :)
schulwitz,

1
SIMPATICO! L'ho compilato con cc -o base base.c -lssl -lcrypto . Nessun errore Ha prodotto questo risultato: Original character string is: Base64 encode this string! Base-64 encoded string is: QmFzZTY0IGVuY29kZSB0aGlzIHN0cmluZyE= Base-64 decoded string is: Base64 encode this string!
chiarimento il

@schulwitz Ho un file che è codificato come stringa usando Python, ma quando decodifico la stringa usando la tua funzione e provo a scrivere il risultato decodificato in un file (in C) non riesco a recuperare lo stesso file. La stringa codificata è corretta. `` `const unsigned char * jarFile =" <file codificato> "; int main () {print_version (); FILE * fp; char * out = base64decode (jarFile, strlen (jarFile)); fp = fopen ("file.jar", "wb"); if (fp == NULL) {printf ("Apertura file non riuscita"); ritorno 1; } fwrite (out, sizeof (out), 1, fp); fclose (fp); libera (out); ritorna 0; } ``
Sam Thomas,

1
@SamThomas Nel mio esempio l'uso di strlen funziona perché ho creato una stringa in cui esiste un solo terminatore null (ed è alla fine della stringa). Vedi: tutorialspoint.com/cprogramming/c_strings.htm La lettura in jarFile con strlen fallirà, perché probabilmente esiste un terminatore nullo nel mezzo del file binario, incasinando il valore bytes_to_decode. Vedere: stackoverflow.com/questions/24596189/... Trovare la dimensione del file di un modo diverso: stackoverflow.com/questions/238603/...
schulwitz


16

libb64 ha entrambe le API C e C ++. È leggero e forse l'implementazione pubblica più veloce disponibile. È anche una libreria di codifica base64 dedicata stand-alone, che può essere utile se non hai bisogno di tutte le altre cose che derivano dall'uso di una libreria più grande come OpenSSL o glib.


5
Nota su libb64: BUFFERSIZE è definito in un file make, quindi se non usi make / cmake, dovrai definirlo manualmente nei file header per poterlo compilare. Funziona / brevemente testato VS2012
Tom,

3
Come ha detto Tom: #define BUFFERSIZE 16777216puoi sostituire 65536 se hai bisogno di un buffer più piccolo.
jyz,

1
Attenzione! Dopo un'ora di debug ho capito che libb64 presuppone che charsia firmato sul sistema di destinazione ... Questo è un problema poiché base64_decode_valuepotrebbe restituire un numero negativo che viene poi lanciato su char.
Noir,

Si noti che l'implementazione di sourceforge aggiunge nuove righe che non sono universalmente supportate. Un fork di BuLogics su github li rimuove e ho generato una richiesta pull basata sulla tua scoperta estremamente utile, @Noir.
alcalinità

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
Tutti i lavoratori sono essenziali

14

I coreutils di GNU lo hanno in lib / base64. È un po 'gonfio ma si occupa di cose come EBCDIC. Puoi anche giocare da solo, ad es.

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

Conoscete tutte le persone con questi regali che non dovreste confondere "giocando da soli" con "implementare uno standard". Yeesh.


3
Inoltre, '+'è 62 ed '/'è 63 in PEM base64 come richiesto dall'OP. Ecco un elenco di varianti di codifica base64 . Non vedo una variante di codifica base64 con l'ordinamento dei caratteri che usi. Ma la matematica dietro l'algoritmo è corretta.
Patrick,

2
Come già detto: attenzione che questo algoritmo non è compatibile con la base64 comune
Cerber,

E la codifica?
Geremia,

14

Avevo bisogno dell'implementazione C ++ che funzionasse su std :: string . Nessuna delle risposte soddisfaceva le mie esigenze, avevo bisogno di una semplice soluzione a due funzioni per codificare e decodificare, ma ero troppo pigro per scrivere il mio codice, quindi ho trovato questo:

http://www.adp-gmbh.ch/cpp/common/base64.html

I crediti per il codice vanno a René Nyffenegger.

Inserendo il codice seguente nel caso in cui il sito non funzioni:

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 rene.nyffenegger@adp-gmbh.ch

*/

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

uso

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

9

Ecco il decoder che uso da anni ...

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

qual è il * dest = 0; all'inizio per?
Tim

1
È solo un'operazione molto semplice che assicura che il buffer dest sia impostato su NULL nel caso in cui il chiamante non lo abbia fatto prima della chiamata, e se forse la decodifica non fosse riuscita, il buffer restituito sarebbe di lunghezza zero. Non ho detto di aver eseguito il debug, tracciato e profilato questa routine, è solo uno che uso da anni. :) Quando lo guardo adesso, non c'è davvero bisogno di essere lì, quindi, perché non lo chiamiamo un "esercizio per il lettore?" hehe .. Forse lo modificherò. Grazie per segnalarlo!
LarryF,

3
la tua UnBase64funzione potrebbe compromettere la memoria dopo il buffer dest, se quel buffer ha la dimensione esatta richiesta per decodificare la stringa codificata base 64. Prendiamo ad esempio il semplice caso in cui si tenta di decodificare la seguente stringa codificata in base 64 "BQ ==", in un singolo BYTE, ovvero unsigned char Result = 0; UnBase64(&Result, "BQ==", 4); danneggerà lo stack!
Mike Dinescu,

3
Sì, ha causato un brutto bug nella nostra app. Non lo consiglio.
Harald Maassen,

Ciao Larry, grazie per aver condiviso il tuo codice. È molto utile!
Federico,

4

Nel caso in cui le persone abbiano bisogno di una soluzione c ++, metto insieme questa soluzione OpenSSL (sia per la codifica che per la decodifica). Dovrai collegarti con la libreria "crypto" (che è OpenSSL). Questo è stato verificato per perdite con valgrind (anche se potresti aggiungere qualche codice di controllo degli errori aggiuntivo per renderlo un po 'meglio - so almeno che la funzione di scrittura dovrebbe verificare il valore restituito).

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

}

BIO_free_all deve specificare la testa, non la coda, della tua catena bio (cioè il filtro base64). L'implementazione corrente presenta una perdita di memoria.
schulwitz,

@schulwitz Quale linea ha la perdita? Bio_free_all libera l'intera catena.
Homer6

4

Ne ho scritto uno da utilizzare con C ++, è molto veloce, funziona con streaming, gratuito e open source:

https://tmplusplus.svn.sourceforge.net/svnroot/tmplusplus/trunk/src/

Sentiti libero di usarlo se si adatta al tuo scopo.

Modifica: codice aggiunto in linea su richiesta.

L'aumento delle prestazioni viene ottenuto utilizzando una tabella di ricerca per la codifica e la decodifica. _UINT8è uno unsigned charsulla maggior parte dei sistemi operativi.

/** 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];
    }
}

1
Il blog collegato non sembra più esistere in quell'URL.
HulkHolden,


@cpburnz Ho aggiunto l'esempio in linea ora e un commento sul perché è veloce, grazie.

2

Un piccolo miglioramento del codice da parte di ryyst (che ha ottenuto il maggior numero di voti) è di non utilizzare una tabella di decodifica allocata dinamicamente, ma piuttosto una tabella precomputata const statica. Questo elimina l'uso del puntatore e l'inizializzazione della tabella ed evita anche la perdita di memoria se si dimentica di ripulire la tabella di decodifica con base64_cleanup () (a proposito, in base64_cleanup (), dopo aver chiamato free (decoding_table), si dovrebbe avere decoding_table = NULL, altrimenti la chiamata accidentale base64_decode dopo base64_cleanup () andrà in crash o causerà un comportamento indeterminato). Un'altra soluzione potrebbe essere quella di utilizzare std :: unique_ptr ... ma sono soddisfatto di avere solo const char [256] nello stack ed evitare di usare i puntatori del tutto - il codice sembra più pulito e più breve in questo modo.

La tabella di decodifica viene calcolata come segue:

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();

e il codice modificato che sto usando è:

        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;

        };

1

Questo è un decodificatore che è stato scritto appositamente per evitare la necessità di un buffer, scrivendo direttamente a una funzione putchar. Questo si basa sull'implementazione di wikibook https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C

Questo non è facile da usare come altre opzioni sopra. Tuttavia, può essere utile nei sistemi incorporati, in cui si desidera scaricare un file di grandi dimensioni senza allocare un altro buffer di grandi dimensioni per memorizzare la stringa datauri base64 risultante. (È un peccato che datauri non ti permetta di specificare il nome del file).

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

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

Uscita prevista: data:text/plain;charset=utf-8;base64,dGVzdA==


1

Le funzioni EVP_EncodeBlockeEVP_DecodeBlock lo rendono molto semplice:

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

0

Questa soluzione si basa sulla risposta schulwitz (codifica / decodifica usando OpenSSL), ma è per C ++ (beh, la domanda originale era su C, ma ci sono già altre risposte C ++ qui) e utilizza il controllo degli errori (quindi è più sicuro da usare) :

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

Si noti che base64_decode restituisce una stringa vuota, se l'input è una sequenza base64 errata (openssl funziona in questo modo).


hm ... l'uso della libreria openssl per la decodifica / codifica base64 richiede più righe di codice rispetto all'implementazione diretta (migliore risposta a questa domanda) ...
anton_rh

-2

Ecco una versione ottimizzata dell'encoder per la risposta accettata, che supporta anche l'interruzione di linea per MIME e altri protocolli (l'ottimizzazione simlar può essere applicata al decoder):

 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)'=';
    }
 }

il tuo frammento non può nemmeno essere compilato: nessuna variabile di dimensione e spero che non sia una variabile globale nel tuo codice.
Max Lapshin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.