¿Cómo codifico (decodifico) en base64 en C?

16 minutos de lectura

Tengo datos binarios en una variable char sin firmar. Necesito convertirlos a PEM base64 en c. Busqué en la biblioteca de openssl pero no pude encontrar ninguna función. ¿Alguien tiene alguna idea?

  • tengo un repositorio github con funciones probadas base64 y unbase64. El único encabezado que necesita es base64.h

    – bobobobo

    17 de abril de 2013 a las 22:17

  • Desafortunadamente, la mayoría de las respuestas aquí están completamente fuera de tema. C++ no es C.

    – Joe codificador

    24 de abril de 2020 a las 7:44

avatar de usuario
ryyst

Aquí está el que estoy 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', '+', "https://stackoverflow.com/"};
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);
}

Tenga en cuenta que esto no realiza ninguna verificación de errores durante la decodificación: se procesarán los datos codificados que no sean base 64.

  • No tiene ningún sentido usar esto si hay una biblioteca.

    – Diego Woitasen

    18 de julio de 2012 a las 15:35

  • Puede omitir la “dependencia” de libm y math.h, así como la necesidad de operaciones de coma flotante (que son lentas en algunos hardware), utilizando *output_length = ((input_length - 1) / 3) * 4 + 4; al comienzo de base64_encode.

    – Fabián Henze

    8 sep 2012 a las 15:54

  • Me doy cuenta de que “no hay verificación de errores”, pero observe especialmente que, aunque la tabla de decodificación en el decodificador es una matriz de 256, dado que char está firmado en la mayoría de las arquitecturas, en realidad está indexando de -128 a 127. Cualquier carácter con el alto bit establecido hará que lea fuera de la memoria asignada. Obligar a que la búsqueda de datos sea un carácter sin firmar lo aclara. Todavía obtienes basura por basura, pero no fallarás.

    – bitmusher

    12 de febrero de 2013 a las 14:54

  • Tiene un problema de matriz fuera de los límites en build_decoding_table. encoding_table[64] para encoding_table[255] no existe.

    – bobobobo

    17 abr 2013 a las 19:12


  • La decodificación tampoco maneja la situación en la que falta el relleno “=”. Junto con todos los demás errores, una implementación bastante mala.

    – Lotario

    10 de marzo de 2015 a las 11:45

avatar de usuario
GaspardP

Sé que esta pregunta es bastante antigua, pero me estaba confundiendo la cantidad de soluciones proporcionadas, cada una de las cuales afirmaba ser más rápida y mejor. Preparé un proyecto en github para comparar los codificadores y decodificadores base64: https://github.com/gaspardpetit/base64/

En este punto, no me he limitado a los algoritmos C: si una implementación funciona bien en C ++, se puede transferir fácilmente a C. También se realizaron pruebas con Visual Studio 2015. Si alguien quiere actualizar esta respuesta con resultados de clang/ gcc, sea mi invitado.

CODIFICADORES MÁS RÁPIDOS:
Las dos implementaciones de codificador más rápidas que encontré fueron las de Jouni Malinen en http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c y el Apache en https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c.

Este es el tiempo (en microsegundos) para codificar 32K de datos usando los diferentes algoritmos que he probado hasta ahora:

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 solución de René Nyffenegger, acreditada en otra respuesta a esta pregunta, se incluye aquí como adp_gmbh).

Aquí está el de Jouni Malinen que modifiqué ligeramente para devolver un 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;
}

DECODIFICADORES MÁS RÁPIDOS: Aquí están los resultados de la decodificación y debo admitir que estoy un poco sorprendido:

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

El fragmento de Polfosol del fragmento de decodificación base64 en C++ es el más rápido por un factor de casi 2x.

Aquí está el código en aras de la exhaustividad:

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

  • Realmente no creo que std::string y el resto de las funciones que usó sean partes de ANSI C. La pregunta que solicita el código C, y la etiqueta C, obtiene la respuesta más votada en C ++.

    – SF.

    23 de agosto de 2017 a las 10:24

  • Si uno quiere una solución que funcione bien tanto para decodificar como para codificar sin tener que tomar código de dos lugares, elegiría la versión de apache para C y la solución de polfosol para C++

    – Dédalo Alfa

    7 de febrero de 2018 a las 9:57


  • @GaspardP ¿Se puede usar la decodificación de Polfosol en la codificación de Jouni?

    – Sam Tomás

    24 de mayo de 2018 a las 1:13

  • @SamThomas sí, todos los métodos probados proporcionan el mismo resultado y son intercambiables.

    – GaspardP

    13 de diciembre de 2020 a las 0:23

  • @GaspardP, ¿le gustaría actualizar un poco su repositorio y sus pruebas? 🙂 Aquí está la fuente reescrita con jounimalinen para codificar y polfosol para decodificar. Se reescribió para cumplir con las normas de automoción/aviónica siguiendo la guía de codificación MISRA C:2012. github.com/IMProject/IMUtility/blob/main/Src/base64.c por cierto. no esperamos ser los más rápidos sino los más rápidos y seguros 🙂

    – Igor Misic

    7 abr a las 14:27


Pero también puedes hacerlo en openssl (openssl enc el comando lo hace….), mire el BIO_f_base64() función

  • Parece que el OP ya está usando OpenSSL por alguna otra razón, por lo que esta es probablemente la mejor manera de hacerlo.

    – joshk0

    30 de abril de 2009 a las 18:19

avatar de usuario
Schulwitz

Aquí está mi solución 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.
}

libb64 tiene API C y C++. Es ligero y quizás la implementación disponible públicamente más rápida. También es una biblioteca de codificación base64 independiente dedicada, lo que puede ser bueno si no necesita todas las otras cosas que surgen del uso de una biblioteca más grande como OpenSSL o glib.

  • Nota sobre libb64: BUFFERSIZE se define en un archivo de creación, por lo que si no usa make/cmake, deberá definirlo manualmente en los archivos de encabezado para que se compile. Funciona/probado brevemente VS2012

    – Tomás

    15 de septiembre de 2012 a las 3:41


  • Como Tom dijo: #define BUFFERSIZE 16777216 puede reemplazar a 65536 si necesita un búfer más pequeño.

    – jyz

    18/08/2013 a las 20:55


  • ¡Tener cuidado! Después de una hora de depuración, descubrí que libb64 asume que char está firmado en el sistema de destino… Esto es un problema ya que base64_decode_value podría devolver un número negativo que luego se convierte en char.

    – Negro

    29 de agosto de 2014 a las 10:07

  • Tenga en cuenta que la implementación de sourceforge agrega nuevas líneas que no son universalmente compatibles. UN bifurcación por BuLogics en github los elimina, y he generó una solicitud de extracción basado en su hallazgo extremadamente útil, @Noir.

    – alcalinidad

    3 de diciembre de 2015 a las 18:28

  • Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página enlazada cambia.

    – Las vidas de los uigures importan

    19 mayo 2016 a las 22:28

glib tiene funciones para la codificación base64: https://developer.gnome.org/glib/stable/glib-Base64-Encoding.html

  • Nota sobre libb64: BUFFERSIZE se define en un archivo de creación, por lo que si no usa make/cmake, deberá definirlo manualmente en los archivos de encabezado para que se compile. Funciona/probado brevemente VS2012

    – Tomás

    15 de septiembre de 2012 a las 3:41


  • Como Tom dijo: #define BUFFERSIZE 16777216 puede reemplazar a 65536 si necesita un búfer más pequeño.

    – jyz

    18/08/2013 a las 20:55


  • ¡Tener cuidado! Después de una hora de depuración, descubrí que libb64 asume que char está firmado en el sistema de destino… Esto es un problema ya que base64_decode_value podría devolver un número negativo que luego se convierte en char.

    – Negro

    29 de agosto de 2014 a las 10:07

  • Tenga en cuenta que la implementación de sourceforge agrega nuevas líneas que no son universalmente compatibles. UN bifurcación por BuLogics en github los elimina, y he generó una solicitud de extracción basado en su hallazgo extremadamente útil, @Noir.

    – alcalinidad

    3 de diciembre de 2015 a las 18:28

  • Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página enlazada cambia.

    – Las vidas de los uigures importan

    19 mayo 2016 a las 22:28

GNU coreutils lo tiene en lib/base64. Está un poco inflado pero se ocupa de cosas como EBCDIC. También puede jugar por su cuenta, por ejemplo,

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

Sepan todas las personas por estos regalos que no deben confundir “jugar por su cuenta” con “implementar un estándar”. Sí.

  • También, '+' es 62 y '/' es 63 en PEM base64 según lo solicitado por OP. Aquí hay una lista de variantes de codificación base64. No veo una variante de codificación base64 con el orden de los caracteres que usa. Pero las matemáticas detrás del algoritmo son correctas.

    – Patricio

    28 de octubre de 2012 a las 3:53

  • Como ya se dijo: tenga cuidado este algoritmo no es compatible con común base64

    – Cerbero

    15 de noviembre de 2012 a las 17:34

  • ¿Qué pasa con la codificación?

    – Geremia

    30 de enero de 2016 a las 1:28

¿Ha sido útil esta solución?