如何在C语言中进行Base64编码(解码)?

162

我有一个unsigned char变量中存储的二进制数据。 我需要将它们转换为C语言中的PEM base64格式。 我查阅了openssl库,但没有找到任何函数。 请问有人有什么想法吗?


12
我有一个 Github 存储库(https://github.com/superwills/NibbleAndAHalf),其中包含经过测试的 base64 和 unbase64 函数。你所需要的唯一头文件是 base64.h(https://github.com/superwills/NibbleAndAHalf/blob/master/NibbleAndAHalf/base64.h)。 - bobobobo
8
很遗憾,这里的大部分回答都完全不相关。 C++ 不是 C。 - Joe Coder
@JoeCoder 请查看下面关于libb64的评论。 - Jonathan Ben-Avraham
@JonathanBen-Avraham 由于libb64本身是用c++实现的,我认为答案也可以被视为不相关。 - malat
@malat OP提到他查看了openssl库函数,这表明他不关心库语言是什么,libb64作为一个维护良好、经过测试的解决方案肯定是相关的,比自己编写的解决方案更可取。OP没有指出任何平台限制,如裸机、FreeRTOS、MS Windows等。 - Jonathan Ben-Avraham
20个回答

127
这是我正在使用的一个:
#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);
}

请注意,这种方法在解码时不进行任何错误检查——非base64编码的数据也会被处理。


9
如果有库可用,使用这个没有任何意义。 - Diego Woitasen
12
在base64编码函数的开头使用 *output_length = ((input_length - 1) / 3) * 4 + 4; 可以跳过libm和math.h的依赖,也不需要使用浮点数运算(在某些硬件上速度较慢)。请注意,这句话的意思是输出长度可以根据输入长度计算出来。 - Fabian Henze
9
我知道这里没有错误检查,但是要特别注意的是,虽然解码器中的解码表是一个包含256个元素的数组,但由于在大多数结构体系中char类型是有符号类型,因此实际上您要从-128到127进行索引。任何高位为1的字符都会导致您读取分配外的内存。强制数据查找为无符号char类型可以解决这个问题。尽管输入垃圾数据将得到垃圾输出,但不会出现段错误。 - bitmusher
1
build_decoding_table 中存在数组越界问题。encoding_table[64]encoding_table[255] 不存在。 - bobobobo
6
解码也无法处理缺失填充 "=" 的情况。加上所有其他错误,这就是一个相当糟糕的实现。 - Lothar
显示剩余8条评论

110

我知道这个问题很古老,但是由于提供的解决方案数量众多,并且每个解决方案都声称自己更快、更好,所以我感到困惑。我在github上创建了一个项目来比较base64编码器和解码器:https://github.com/gaspardpetit/base64/

目前为止,我并没有局限于C算法——如果一个实现在C++中表现良好,它很容易被移植到C中。此外,测试是使用Visual Studio 2015进行的。如果有人想要用clang/gcc更新这个答案的结果,请随意参与。

最快的编码器: 我发现的两个最快的编码器实现分别是Jouni Malinen的http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c和Apache的https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c

以下是我目前测试过的不同算法对32K数据进行编码所需的时间(以微秒为单位):

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

(René Nyffenegger的解决方案,另一个回答已经给出了他的名字,这里列出作为adp_gmbh)。

这是Jouni Malinen的一个解决方案,我稍微修改了一下以返回一个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;
}

最快的解码器:以下是解码结果,我必须承认我有点惊讶:

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

Polfosol从base64解码c++代码段中摘取的片段是快速的,大约比其他代码块快了将近两倍。

为了完整起见,这里附上代码:

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

30
我很确定std::string和你使用的其他函数不是ANSI C的一部分。 问题要求使用C代码,并标记为C,而在C++中获得了最多赞的答案。 - SF.
1
如果想要一种既适用于解码又适用于编码而不必从两个地方取代码的解决方案,我会选择 Apache 版本的 C 和 polfosol 的 C++ 解决方案。 - DaedalusAlpha
1
@GaspardP Polfosol的解码能用于Jouni的编码吗? - Sam Thomas
1
@GaspardP,你想更新一下你的代码库和测试吗? :) 这里有一个使用jounimalinen进行编码和polfosol进行解码的重写源代码。它已经按照MISRA C:2012编码指南进行了改写,以符合汽车/航空电子标准。https://github.com/IMProject/IMUtility/blob/main/Src/base64.c顺便说一句,我们不期望成为最快的,但是最安全/最可靠的 :) - Igor Mišić
1
如果你正在寻找基于SIMD的Base64,我推荐 https://github.com/powturbo/Turbo-Base64 - 这是迄今为止我见过的最快实现。 - GaspardP
显示剩余4条评论

35

你也可以使用openssl执行此操作(openssl enc命令可以实现...),查看BIO_f_base64()函数。


2
看起来OP已经在使用OpenSSL进行其他操作,所以这可能是最好的方法。 - joshk0

19

这是我使用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
在“添加空终止符”行上,我得到了一个AddressSanitizer错误,写入超出了堆1个字节的范围。 - bparker
谢谢,我已经纠正了错误,并进行了大量测试,使用随机大小的随机字节字符串来确保代码按照广告所述正常工作。 :) - schulwitz
1
太好了!我使用cc -o base base.c -lssl -lcrypto编译它,没有错误。它产生了以下输出:原始字符字符串为:Base64 encode this string! Base-64编码字符串为:QmFzZTY0IGVuY29kZSB0aGlzIHN0cmluZyE= Base-64解码字符串为:Base64 encode this string! - clearlight
1
@SamThomas 在我的示例中使用strlen是有效的,因为我创建了一个字符串,其中只存在一个空终止符(并且它在字符串的末尾)。请参见:https://www.tutorialspoint.com/cprogramming/c_strings.htm 使用strlen读取jarFile将失败,因为空终止符很可能存在于二进制文件的中间,从而破坏bytes_to_decode值。请参见:https://stackoverflow.com/questions/24596189/does-strlen-return-same-value-for-a-binary-and-ascii-data 以不同的方式查找文件的大小:https://dev59.com/UHVC5IYBdhLWcg3wnCf6 - schulwitz
1
这个程序存在内存泄漏问题,至少在Ubuntu 20.04上的openssl1.1版本中存在。 - ericcurtin
显示剩余3条评论

19

libb64 有C和C++的API。它非常轻量级,也可能是目前最快的公开实现之一。它还是一个专用的独立的base64编码库,如果您不需要使用较大的库(如OpenSSL或glib)中的所有其他内容,那么这可能会很好。


5
关于libb64的说明:BUFFERSIZE在一个make文件中被定义,所以如果你不使用make/cmake,你需要在头文件中手动定义它才能进行编译。在VS2012上已经测试通过。 - Tom
3
正如Tom所说:“#define BUFFERSIZE 16777216”,如果你需要一个更小的缓冲区,可以把它替换为65536。 - jyz
2
注意!调试了一个小时后,我发现libb64假定目标系统上的“char”是有符号的...这是一个问题,因为“base64_decode_value”可能会返回一个负数,然后被强制转换为char。 - Noir
1
请注意,SourceForge实现添加了不被所有平台支持的换行符。BuLogics在Github上的分支移除了这些换行符,并且我基于你非常有用的发现,@Noir,已经生成了一个pull request - alkalinity
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。仅有链接的答案如果链接页面发生更改可能会变得无效。 - Uyghur Lives Matter
自2017.11以来,libb64已在Buldroot中得到支持。 - Jonathan Ben-Avraham

17

9
虽然这个链接可能会回答问题,但最好在此处包含答案的基本部分,并提供链接作为参考。仅提供链接的答案如果链接页面更改可能会变得无效。 - Uyghur Lives Matter
@UyghurLivesMatter ...现在已经发生了。 :| - Per Lundberg

15

GNU coreutils中的lib/base64库包含相关内容。尽管有些臃肿但可以处理像EBCDIC这样的东西。您也可以自行尝试,例如:

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

特此知悉:您不应将“自己玩耍”与“实施标准”混淆。天啊。


3
根据提问者的要求,PEM base64 编码中 '+' 对应 62,'/' 对应 63。在 base64编码的变种 列表中,没有发现使用您所用字符排序的 base64 编码变种。但是,该算法背后的数学是正确的。请注意不要修改原文意思。 - Patrick
3
如前所述:请注意,此算法与“通用”base64不兼容。 - Cerber
编码方面怎么样? - Geremia

15
我需要一个在std::string上工作的C++实现。没有任何答案能够满足我的需求,我需要一个简单的两个函数的解决方案来进行编码和解码,但是我太懒了,不想自己写代码,所以我找到了这个:

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

代码的制作归功于René Nyffenegger。
为防网站崩溃,以下是代码:

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

Usage

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

8

使用EVP_EncodeBlockEVP_DecodeBlock函数非常简单:

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

8

这是我多年来一直在使用的解码器...

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

在开始时,*dest = 0; 是用来做什么的? - Tim
1
这只是一个非常简单的操作,它确保在调用之前将目标缓冲区设置为NULL,并且如果解码失败,则返回的缓冲区长度为零。我没有说过我对这个例程进行了调试、跟踪和分析,它只是我多年来一直在使用的例程。当我现在看它时,它真的不需要存在,所以,为什么我们不把它称为“读者练习”呢?哈哈..也许我会编辑掉它。感谢你指出它! - LarryF
3
如果目标缓冲区的确切大小与解码Base64编码字符串所需的大小相同,那么您的UnBase64函数可能会损坏目标缓冲区之后的内存。例如,尝试将以下Base64编码字符串“BQ==”解码为单个BYTE(即unsigned char Result = 0; UnBase64(&Result, "BQ==", 4);)时,它将破坏堆栈!请注意修复此问题。 - Mike Dinescu
4
是的,这导致我们的应用程序出现了严重的错误。不建议使用。 - Harald Maassen
嗨,Larry,感谢分享你的代码。它非常有用! - Federico
“我已经使用了多年…”的意思是代码已经运行了多年,因为有许多缓慢的strchr调用,对吧?;)…我的意思是至少不使用反向查找表可以节省一些内存。 - Steven Spark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接