web-dev-qa-db-fra.com

Encodage et décodage Base64 avec OpenSSL

J'ai essayé de comprendre la documentation openssl pour le décodage et l'encodage en base64. J'ai trouvé des extraits de code ci-dessous

#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

char *base64(const unsigned char *input, int length)
{
  BIO *bmem, *b64;
  BUF_MEM *bptr;

  b64 = BIO_new(BIO_f_base64());
  bmem = BIO_new(BIO_s_mem());
  b64 = BIO_Push(b64, bmem);
  BIO_write(b64, input, length);
  BIO_flush(b64);
  BIO_get_mem_ptr(b64, &bptr);

  char *buff = (char *)malloc(bptr->length);
  memcpy(buff, bptr->data, bptr->length-1);
  buff[bptr->length-1] = 0;

  BIO_free_all(b64);

  return buff;
}

char *decode64(unsigned char *input, int length)
{
  BIO *b64, *bmem;

  char *buffer = (char *)malloc(length);
  memset(buffer, 0, length);

  b64 = BIO_new(BIO_f_base64());
  bmem = BIO_new_mem_buf(input, length);
  bmem = BIO_Push(b64, bmem);

  BIO_read(bmem, buffer, length);

  BIO_free_all(bmem);

  return buffer;
}

Cela ne semble fonctionner que pour des chaînes simples telles que "Démarrer", au moment où j'introduis des chaînes complexes avec des nouvelles lignes et des espaces, etc., il échoue terriblement.

Il n'est même pas nécessaire que ce soit OpenSL, une simple classe ou un ensemble de fonctions qui font la même chose irait, il y a un processus de construction très compliqué pour la solution et j'essaie d'éviter d'avoir à y aller et à faire plusieurs changements . La seule raison pour laquelle je me suis tourné vers OpenSSL est que la solution est déjà compilée avec les bibliothèques.

19
Bernard

Personnellement, je trouve l’API OpenSSL incroyablement pénible à utiliser, je l’évite sauf si le coût de l’éviter est extrêmement élevé. Je trouve assez bouleversant qu'elle soit devenue l'API standard dans le monde de la cryptographie.

Je m'ennuyais et je vous en ai écrit un en C++. Celui-ci devrait même gérer les cas Edge pouvant causer des problèmes de sécurité, tels que, par exemple, le codage d'une chaîne entraînant un dépassement d'entier, car elle est trop grande.

J'ai fait quelques tests unitaires dessus, donc ça devrait marcher.

#include <string>
#include <cassert>
#include <limits>
#include <stdexcept>
#include <cctype>

static const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static const char reverse_table[128] = {
   64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
   64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
   64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
   52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
   64,  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, 64, 64, 64, 64, 64,
   64, 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, 64, 64, 64, 64, 64
};

::std::string base64_encode(const ::std::string &bindata)
{
   using ::std::string;
   using ::std::numeric_limits;

   if (bindata.size() > (numeric_limits<string::size_type>::max() / 4u) * 3u) {
      throw ::std::length_error("Converting too large a string to base64.");
   }

   const ::std::size_t binlen = bindata.size();
   // Use = signs so the end is properly padded.
   string retval((((binlen + 2) / 3) * 4), '=');
   ::std::size_t outpos = 0;
   int bits_collected = 0;
   unsigned int accumulator = 0;
   const string::const_iterator binend = bindata.end();

   for (string::const_iterator i = bindata.begin(); i != binend; ++i) {
      accumulator = (accumulator << 8) | (*i & 0xffu);
      bits_collected += 8;
      while (bits_collected >= 6) {
         bits_collected -= 6;
         retval[outpos++] = b64_table[(accumulator >> bits_collected) & 0x3fu];
      }
   }
   if (bits_collected > 0) { // Any trailing bits that are missing.
      assert(bits_collected < 6);
      accumulator <<= 6 - bits_collected;
      retval[outpos++] = b64_table[accumulator & 0x3fu];
   }
   assert(outpos >= (retval.size() - 2));
   assert(outpos <= retval.size());
   return retval;
}

::std::string base64_decode(const ::std::string &ascdata)
{
   using ::std::string;
   string retval;
   const string::const_iterator last = ascdata.end();
   int bits_collected = 0;
   unsigned int accumulator = 0;

   for (string::const_iterator i = ascdata.begin(); i != last; ++i) {
      const int c = *i;
      if (::std::isspace(c) || c == '=') {
         // Skip whitespace and padding. Be liberal in what you accept.
         continue;
      }
      if ((c > 127) || (c < 0) || (reverse_table[c] > 63)) {
         throw ::std::invalid_argument("This contains characters not legal in a base64 encoded string.");
      }
      accumulator = (accumulator << 6) | reverse_table[c];
      bits_collected += 6;
      if (bits_collected >= 8) {
         bits_collected -= 8;
         retval += static_cast<char>((accumulator >> bits_collected) & 0xffu);
      }
   }
   return retval;
}
37
Omnifarious

Voici un exemple de codage/décodage OpenSSL base64 que j'ai écrit:

Remarquez que j'ai écrit quelques macros/classes dans le code, mais aucune d'entre elles n'est importante pour l'exemple. Voici simplement quelques wrappers C++ que j'ai écrits:

buffer base64::encode( const buffer& data )
{
    // bio is simply a class that wraps BIO* and it free the BIO in the destructor.

    bio b64(BIO_f_base64()); // create BIO to perform base64
    BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);

    bio mem(BIO_s_mem()); // create BIO that holds the result

    // chain base64 with mem, so writing to b64 will encode base64 and write to mem.
    BIO_Push(b64, mem);

    // write data
    bool done = false;
    int res = 0;
    while(!done)
    {
        res = BIO_write(b64, data.data, (int)data.size);

        if(res <= 0) // if failed
        {
            if(BIO_should_retry(b64)){
                continue;
            }
            else // encoding failed
            {
                /* Handle Error!!! */
            }
        }
        else // success!
            done = true;
    }

    BIO_flush(b64);

    // get a pointer to mem's data
    char* dt;
    long len = BIO_get_mem_data(mem, &dt);

    // assign data to output
    std::string s(dt, len);

    return buffer(s.length()+sizeof(char), (byte*)s.c_str());
}
8
TCS

Réponse améliorée du TCS pour supprimer les macros/infrastructures de données

unsigned char *encodeb64mem( unsigned char *data, int len, int *lenoutput )
{
// bio is simply a class that wraps BIO* and it free the BIO in the destructor.

BIO *b64 = BIO_new(BIO_f_base64()); // create BIO to perform base64
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

BIO *mem = BIO_new(BIO_s_mem()); // create BIO that holds the result

// chain base64 with mem, so writing to b64 will encode base64 and write to mem.
BIO_Push(b64, mem);

// write data
bool done = false;
int res = 0;
while(!done)
{
    res = BIO_write(b64, data, len);

    if(res <= 0) // if failed
    {
        if(BIO_should_retry(b64)){
            continue;
        }
        else // encoding failed
        {
            /* Handle Error!!! */
        }
    }
    else // success!
        done = true;
}

BIO_flush(b64);

// get a pointer to mem's data
unsigned char* output;
*lenoutput = BIO_get_mem_data(mem, &output);

// assign data to output
//std::string s(dt, len2);

return output;
}

Écrire dans un fichier

int encodeb64(unsigned char* input, const char* filenm, int leni)
{
BIO *b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);

BIO *file = BIO_new_file(filenm, "w");
BIO *mem = BIO_new(BIO_f_buffer());
BIO_Push(b64, mem);
BIO_Push(mem, file);

// write data
bool done = false;
int res = 0;
while(!done)
{
    res = BIO_write(b64, input, leni);

    if(res <= 0) // if failed
    {
        if(BIO_should_retry(b64)){
            continue;
        }
        else // encoding failed
        {
            /* Handle Error!!! */
        }
    }
    else // success!
        done = true;
}

BIO_flush(b64);
BIO_pop(b64);
BIO_free_all(b64);

    return 0;
}

Encodage Base64 de fichier à fichier. Plusieurs fois, en raison de contraintes de fichiers, nous avons lu des fragments de données et encodé. Ci-dessous le code.

int encodeb64FromFile(const char* input, const char* outputfilename)
{
BIO *b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
int leni = 3*64;
unsigned char *data[3*64];
BIO *file = BIO_new_file(outputfilename, "w");
BIO *mem = BIO_new(BIO_f_buffer());
BIO_Push(b64, mem);
BIO_Push(mem, file);

FILE *fp = fopen(input, "rb");
while ((leni = fread(data, 1, sizeof data, fp)) > 0) {
    // write data
    bool done = false;
    int res = 0;
    while(!done)
    {
        res = BIO_write(b64, data, leni);

        if(res <= 0) // if failed
        {
            if(BIO_should_retry(b64)){
                continue;
            }
            else // encoding failed
            {
                /* Handle Error!!! */
            }
        }
        else // success!
            done = true;
    }

 }

 BIO_flush(b64);
BIO_pop(b64);
BIO_free_all(b64);
fclose(fp);

return 0;
 }
3
Satish

Base64 est vraiment très simple; vous ne devriez pas avoir de difficulté à trouver un nombre quelconque d'implémentations via un rapide Google. Par exemple ici est une implémentation de référence en C de Internet Software Consortium, avec des commentaires détaillés expliquant le processus.

L’implémentation d’openssl soulève beaucoup de complexité avec le truc "BIO" qui n’est pas très utile (IMHO) si vous ne faites que décoder/encoder.

2
David Gelhar
  #include <openssl/bio.h>

  typedef unsigned char byte;      

  namespace base64 {
    static void Encode(const byte* in, size_t in_len,
                       char** out, size_t* out_len) {
      BIO *buff, *b64f;
      BUF_MEM *ptr;

      b64f = BIO_new(BIO_f_base64());
      buff = BIO_new(BIO_s_mem());
      buff = BIO_Push(b64f, buff);

      BIO_set_flags(buff, BIO_FLAGS_BASE64_NO_NL);
      BIO_set_close(buff, BIO_CLOSE);
      BIO_write(buff, in, in_len);
      BIO_flush(buff);

      BIO_get_mem_ptr(buff, &ptr);
      (*out_len) = ptr->length;
      (*out) = (char *) malloc(((*out_len) + 1) * sizeof(char));
      memcpy(*out, ptr->data, (*out_len));
      (*out)[(*out_len)] = '\0';

      BIO_free_all(buff);
    }

    static void Decode(const char* in, size_t in_len,
                       byte** out, size_t* out_len) {
      BIO *buff, *b64f;

      b64f = BIO_new(BIO_f_base64());
      buff = BIO_new_mem_buf((void *)in, in_len);
      buff = BIO_Push(b64f, buff);
      (*out) = (byte *) malloc(in_len * sizeof(char));

      BIO_set_flags(buff, BIO_FLAGS_BASE64_NO_NL);
      BIO_set_close(buff, BIO_CLOSE);
      (*out_len) = BIO_read(buff, (*out), in_len);
      (*out) = (byte *) realloc((void *)(*out), ((*out_len) + 1) * sizeof(byte));
      (*out)[(*out_len)] = '\0';

      BIO_free_all(buff);
    }
  }
1
fsenart

Cela fonctionne pour moi et ne vérifie aucune fuite de mémoire avec valgrind.

#include <openssl/bio.h>
#include <openssl/evp.h>
#include <cstring>
#include <memory>
#include <string>
#include <vector>

#include <iostream>

namespace {
struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } };
}

std::string Base64Encode(const std::vector<unsigned char>& binary)
{
    std::unique_ptr<BIO,BIOFreeAll> b64(BIO_new(BIO_f_base64()));
    BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
    BIO* sink = BIO_new(BIO_s_mem());
    BIO_Push(b64.get(), sink);
    BIO_write(b64.get(), binary.data(), binary.size());
    BIO_flush(b64.get());
    const char* encoded;
    const long len = BIO_get_mem_data(sink, &encoded);
    return std::string(encoded, len);
}

// Assumes no newlines or extra characters in encoded string
std::vector<unsigned char> Base64Decode(const char* encoded)
{
    std::unique_ptr<BIO,BIOFreeAll> b64(BIO_new(BIO_f_base64()));
    BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
    BIO* source = BIO_new_mem_buf(encoded, -1); // read-only source
    BIO_Push(b64.get(), source);
    const int maxlen = strlen(encoded) / 4 * 3 + 1;
    std::vector<unsigned char> decoded(maxlen);
    const int len = BIO_read(b64.get(), decoded.data(), maxlen);
    decoded.resize(len);
    return decoded;
}

int main()
{
    const char* msg = "hello";
    const std::vector<unsigned char> binary(msg, msg+strlen(msg));
    const std::string encoded = Base64Encode(binary);
    std::cout << "encoded = " << encoded << std::endl;
    const std::vector<unsigned char> decoded = Base64Decode(encoded.c_str());
    std::cout << "decoded = ";
    for (unsigned char c : decoded) std::cout << c;
    std::cout << std::endl;
    return 0;
}

Compiler:

g++ -lcrypto main.cc

Sortie:

encoded = aGVsbG8=
decoded = hello
1
Matt