How do I base64 encode (decode) in C?

后端 未结 17 1494
孤城傲影
孤城傲影 2020-11-22 06:27

I have binary data in an unsigned char variable. I need to convert them to PEM base64 in c. I looked in openssl library but i could not find any function. Does any body have

相关标签:
17条回答
  • 2020-11-22 06:32

    This solution is based on schulwitz answer (encoding/decoding using OpenSSL), but it is for C++ (well, original question was about C, but there are already another C++ answers here) and it uses error checking (so it's safer to use):

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

    Note that base64_decode returns empty string, if input is incorrect base64 sequence (openssl works in such way).

    0 讨论(0)
  • 2020-11-22 06:33

    Here's my solution using 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.
    }
    
    0 讨论(0)
  • 2020-11-22 06:33

    In case people need a c++ solution, I put this OpenSSL solution together (for both encode and decode). You'll need to link with the "crypto" library (which is OpenSSL). This has been checked for leaks with valgrind (although you could add some additional error checking code to make it a bit better - I know at least the write function should check for return value).

    #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;
    
    }
    
    0 讨论(0)
  • 2020-11-22 06:33

    Small improvement to the code from ryyst (who got the most votes) is to not use dynamically allocated decoding table but rather static const precomputed table. This eliminates the use of pointer and initialization of the table, and also avoids memory leakage if one forgets to clean up the decoding table with base64_cleanup() (by the way, in base64_cleanup(), after calling free(decoding_table), one should have decoding_table=NULL, otherwise accidentally calling base64_decode after base64_cleanup() will crash or cause undetermined behavior). Another solution could be to use std::unique_ptr...but I'm satisfied with just having const char[256] on the stack and avoid using pointers alltogether - the code looks cleaner and shorter this way.

    The decoding table is computed as follows:

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

    and the modified code I am using is:

            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 讨论(0)
  • 2020-11-22 06:37

    libb64 has both C and C++ APIs. It is lightweight and perhaps the fastest publicly available implementation. It's also a dedicated stand-alone base64 encoding library, which can be nice if you don't need all the other stuff that comes from using a larger library such as OpenSSL or glib.

    0 讨论(0)
  • 2020-11-22 06:38

    I fix @ryyst answer's bug and this is a url safe version:

        #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <string.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};
     
    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);
    } 
     
    char *base64_encode(const 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];
        }
     
        //int i=0;
        for (int i = 0; i < mod_table[input_length % 3]; i++)
            encoded_data[*output_length - 1 - i] = '=';
        
        *output_length  = *output_length -2 + mod_table[input_length % 3];
        encoded_data[*output_length] =0;
    
        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;
    }
     
    int main(){
        
        const char * data = "Hello World! 您好!世界!";
        size_t input_size = strlen(data);
        printf("Input size: %ld \n",input_size);
        char * encoded_data = base64_encode(data, input_size, &input_size);
        printf("After size: %ld \n",input_size);
        printf("Encoded Data is: %s \n",encoded_data);
        
        size_t decode_size = strlen(encoded_data);
        printf("Output size: %ld \n",decode_size);
        unsigned char * decoded_data = base64_decode(encoded_data, decode_size, &decode_size);
        printf("After size: %ld \n",decode_size);
        printf("Decoded Data is: %s \n",decoded_data);
        return 0;
    }
    
    0 讨论(0)
提交回复
热议问题