Как base64 кодировать (декодировать) в C? - PullRequest
124 голосов
/ 05 декабря 2008

У меня есть двоичные данные в переменной без знака. Мне нужно преобразовать их в PEM base64 в c. Я посмотрел в библиотеке openssl, но я не мог найти какую-либо функцию У кого-нибудь есть идеи?

Ответы [ 14 ]

4 голосов
/ 21 января 2011

Я написал один для использования с C ++, он очень быстрый, работает с потоками, бесплатный и с открытым исходным кодом:

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

Не стесняйтесь использовать его, если он соответствует вашим целям.

Редактировать: добавлен код, встроенный по запросу.

Повышение производительности достигается с помощью таблицы поиска для кодирования и декодирования. _UINT8 - это unsigned char на большинстве ОС.

/** 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];
    }
}
2 голосов
/ 16 февраля 2018

Небольшое улучшение кода от ryyst (который набрал наибольшее количество голосов) состоит в том, чтобы не использовать динамически распределяемую таблицу декодирования, а использовать статическую таблицу const precomputed. Это исключает использование указателя и инициализации таблицы, а также предотвращает утечку памяти, если забывают очистить таблицу декодирования с помощью base64_cleanup () (кстати, в base64_cleanup () после вызова free (decoding_table) нужно иметь decoding_table = NULL, в противном случае случайный вызов base64_decode после base64_cleanup () приведет к сбою или приведет к неопределенному поведению). Другим решением может быть использование std :: unique_ptr ... но я доволен только наличием const char [256] в стеке и избегаю использования указателей вообще - код выглядит чище и короче таким образом.

Таблица декодирования рассчитывается следующим образом:

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

и модифицированный код, который я использую:

        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;

        };
0 голосов
/ 10 декабря 2015

Это решение основано на ответе Шульвица (кодирование / декодирование с использованием OpenSSL), но оно предназначено для C ++ (ну, оригинальный вопрос был о C, но здесь уже есть другие ответы C ++), и в нем используется проверка ошибок (поэтому безопаснее использовать):

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

Обратите внимание, что base64_decode возвращает пустую строку, если ввод неправильной последовательности base64 (openssl работает таким образом).

0 голосов
/ 29 июля 2013

Вот оптимизированная версия кодера для принятого ответа, которая также поддерживает разрыв строки для MIME и других протоколов (оптимизация simlar может применяться к декодеру):

 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)'=';
    }
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...