我有一个unsigned char变量中存储的二进制数据。 我需要将它们转换为C语言中的PEM base64格式。 我查阅了openssl库,但没有找到任何函数。 请问有人有什么想法吗?
优化ryyst的代码(获得最多票数的代码),不再使用动态分配的解码表,而是使用静态的预计算表。这样可以避免使用指针和初始化表,也可以避免在忘记使用base64_cleanup()清理解码表时出现内存泄漏(顺便说一下,在调用free(decoding_table)后,应该将decoding_table=NULL,否则在base64_cleanup()之后意外调用base64_decode会导致崩溃或未确定的行为)。另一种解决方案可能是使用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;
};
if (decoding_table == NULL) build_decoding_table();
,但忘记在void base64_cleanup() { free(decoding_table); }
中将指针设置为null。我不知道有一种可以使其“URL安全”的改进方法,但您可以相应地修改我的代码。 - OGCJNbase64_decode
和 base64_encode
中,在返回数据之前缺少字符串终止字符:decoded_data[output_length] = 0;
和 encoded_data[output_length] = 0;
。 - M.Lisciosize_t &output
更改为size_t *output_length
,并在每个函数体中的output_length
的所有出现前加上*
。 - undefined_UINT8
是一个无符号字符。/** 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];
}
}
如果有人需要c++解决方案,我整理了这个OpenSSL解决方案(用于编码和解码)。您需要链接“crypto”库(即OpenSSL)。已使用valgrind检查了内存泄漏(尽管您可以添加一些额外的错误检查代码使其更好一些-我知道至少写函数应该检查返回值)。
#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;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "b64.h"
int
main (void) {
unsigned char *str = "brian the monkey and bradley the kinkajou are friends";
char *enc = b64_encode(str, strlen(str));
printf("%s\n", enc); // YnJpYW4gdGhlIG1vbmtleSBhbmQgYnJhZGxleSB0aGUga2lua2Fqb3UgYXJlIGZyaWVuZHM=
char *dec = b64_decode(enc, strlen(enc));
printf("%s\n", dec); // brian the monkey and bradley the kinkajou are friends
free(enc);
free(dec);
return 0;
}
#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;
}
void datauriBase64EncodeBufferless(int (*putchar_fcptr)(int), const char* type_strptr, const void* data_buf, const size_t dataLength)
{
const char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const uint8_t *data = (const uint8_t *)data_buf;
size_t x = 0;
uint32_t n = 0;
int padCount = dataLength % 3;
uint8_t n0, n1, n2, n3;
size_t outcount = 0;
size_t line = 0;
putchar_fcptr((int)'d');
putchar_fcptr((int)'a');
putchar_fcptr((int)'t');
putchar_fcptr((int)'a');
putchar_fcptr((int)':');
outcount += 5;
while (*type_strptr != '\0')
{
putchar_fcptr((int)*type_strptr);
type_strptr++;
outcount++;
}
putchar_fcptr((int)';');
putchar_fcptr((int)'b');
putchar_fcptr((int)'a');
putchar_fcptr((int)'s');
putchar_fcptr((int)'e');
putchar_fcptr((int)'6');
putchar_fcptr((int)'4');
putchar_fcptr((int)',');
outcount += 8;
/* increment over the length of the string, three characters at a time */
for (x = 0; x < dataLength; x += 3)
{
/* these three 8-bit (ASCII) characters become one 24-bit number */
n = ((uint32_t)data[x]) << 16; //parenthesis needed, compiler depending on flags can do the shifting before conversion to uint32_t, resulting to 0
if((x+1) < dataLength)
n += ((uint32_t)data[x+1]) << 8;//parenthesis needed, compiler depending on flags can do the shifting before conversion to uint32_t, resulting to 0
if((x+2) < dataLength)
n += data[x+2];
/* this 24-bit number gets separated into four 6-bit numbers */
n0 = (uint8_t)(n >> 18) & 63;
n1 = (uint8_t)(n >> 12) & 63;
n2 = (uint8_t)(n >> 6) & 63;
n3 = (uint8_t)n & 63;
/*
* if we have one byte available, then its encoding is spread
* out over two characters
*/
putchar_fcptr((int)base64chars[n0]);
putchar_fcptr((int)base64chars[n1]);
outcount += 2;
/*
* if we have only two bytes available, then their encoding is
* spread out over three chars
*/
if((x+1) < dataLength)
{
putchar_fcptr((int)base64chars[n2]);
outcount += 1;
}
/*
* if we have all three bytes available, then their encoding is spread
* out over four characters
*/
if((x+2) < dataLength)
{
putchar_fcptr((int)base64chars[n3]);
outcount += 1;
}
/* Breaking up the line so it's easier to copy and paste */
int curr_line = (outcount/80);
if( curr_line != line )
{
line = curr_line;
putchar_fcptr((int)'\r');
putchar_fcptr((int)'\n');
}
}
/*
* create and add padding that is required if we did not have a multiple of 3
* number of characters available
*/
if (padCount > 0)
{
for (; padCount < 3; padCount++)
{
putchar_fcptr((int)'=');
}
}
putchar_fcptr((int)'\r');
putchar_fcptr((int)'\n');
}
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int main(void)
{
char str[] = "test";
datauriBase64EncodeBufferless(putchar, "text/plain;charset=utf-8", str, strlen(str));
return 0;
}
data:text/plain;charset=utf-8;base64,dGVzdA==
这个解决方案基于Schulwitz的答案(使用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;
}
}
使用 openSSL 进行 C 语言中的 Base64 编码和解码实际上非常容易,只需使用 EVP 代码。
以下是 openssl 3.1 版本所需编码和解码函数的文档:https://www.openssl.org/docs/man3.1/man3/EVP_EncodeUpdate.html
EVP 接口可能看起来很复杂,但一旦你理解了它,你可以轻松进行编码、加密等操作,而无需使用示例。不过别担心,我将为您提供一个适用于我的示例。
以下代码使用 g++ 和 std::string 进行编译。
在编译时请使用 -lcrypto
标志。
根据您的要求,随意清理代码以符合任何标准。
#include <openssl/evp.h>
#include <string>
#include <stdio.h>
size_t resultLen = 0;
bool failed = false;
string encode(string in, size_t inSize){
// Error handling stuff
string ret = "";
failed = false;
//Required variables, and proper size estimates
unsigned char *inbuf = new unsigned char[inSize];
size_t outBufCalculated = ((inSize/48) * 66) + 66;
unsigned char *outbuf = new unsigned char[outBufCalculated];
int outCount = 0;
// OpenSSL uses unsigned char buffers
for(int i=0; i<inSize; i++)
inbuf[i] = in[i];
EVP_ENCODE_CTX *encodeCtx = EVP_ENCODE_CTX_new();
EVP_EncodeInit(encodeCtx);
if(encodeCtx == NULL){
failed = true;
delete[] inbuf;
delete[] outbuf;
return "";
}
// Encode the "complete" blocks
if(EVP_EncodeUpdate(encodeCtx,
outbuf, &outCount,
(const unsigned char *)inbuf, inSize) != 1)
{
failed = true;
delete[] inbuf;
delete[] outbuf;
EVP_ENCODE_CTX_free(encodeCtx);
return "";
}
// You need to track the number of encoded bytes.
resultLen = outCount;
// Handle the encoding of incomplete blocks
EVP_EncodeFinal(encodeCtx, outbuf+resultLen, &outCount);
// Don't forget to track the number of remaining bytes encoded :)
resultLen += outCount;
// Get the results into that sweet, sweet string
for(int i=0; i<resultLen; i++)
ret += outbuf[i];
// Barny clean-up song
delete[] inbuf;
delete[] outbuf;
EVP_ENCODE_CTX_free(encodeCtx);
return ret;
}
string decode(string in, size_t inSize){
// Error handling stuff
string ret = "";
failed = false;
unsigned char *inbuf = new unsigned char[inSize];
size_t outBufCalculated = ((inSize/48) * 66) + 66;
unsigned char *outbuf = new unsigned char[outBufCalculated];
int outCount = 0;
for(int i=0; i<inSize; i++)
inbuf[i] = in[i];
// There's only one CTX function for both encode and decode.
EVP_ENCODE_CTX *encodeCtx = EVP_ENCODE_CTX_new();
EVP_DecodeInit(encodeCtx);
if(encodeCtx == NULL){
failed = true;
delete[] inbuf;
delete[] outbuf;
return "";
}
// Decode returns -1 on error, encode returns 1 on success.
if(EVP_DecodeUpdate(encodeCtx,
outbuf, &outCount,
(const unsigned char *)inbuf, inSize) == -1)
{
failed = true;
delete[] inbuf;
delete[] outbuf;
EVP_ENCODE_CTX_free(encodeCtx);
return "";
}
resultLen = outCount;
EVP_DecodeFinal(encodeCtx, outbuf+resultLen, &outCount);
resultLen += outCount;
for(int i=0; i<resultLen; i++)
ret += outbuf[i];
delete[] inbuf;
delete[] outbuf;
EVP_ENCODE_CTX_free(encodeCtx);
return ret;
}
int main(void){
string Message = "Z";
for(int i=0; i<100; i++){
Message = "a" + Message;
}
Message = encode(Message, Message.length());
// OpenSSL null terminates base64.
printf("Encoded Message : \n%s\n", Message.c_str());
Message = decode(Message, Message.length());
printf("Decoded Message : \n%s\n", Message.c_str());
return 0;
}
g++ foo.cc -lcrypto
./a.out
Encoded Message :
YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
YWFhYVo=
Decoded Message :
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZ
基于GaspardP的答案,这是Jouni Malinen的编码器的简化版本,我为自己参与的项目制作:
/* Character list for url-safe base64 encoding */
//char cl[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/* Character list for url-unsafe base64 encoding */
char cl[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @brief Encodes s_in into base64 and writes it to s_out.
* @param s_in Pointer to input buffer.
* @param s_out Pointer to output buffer.
* @return Pointer to end of output buffer
* @usage b64e("ABC",buf);
*/
char *b64e(char *s_in, char *s_out){
int i=0;
if (s_in[i]==0) return s_out;
if (s_in[i+1]==0 || s_in[i+2]==0) {
*s_out++= b64_cl[ s_in[i] >> 2 ];
if (s_in[i+1]==0) {
*s_out++ = b64_cl[ ( ( s_in[i] & 0b000011 ) << 4 ) ];
} else
if (s_in[i+2]==0) {
*s_out++ = b64_cl[ ( ( s_in[i] & 0b000011 ) << 4 ) + ( ( s_in[i+1] >> 4 ) & 0b001111 ) ];
*s_out++ = b64_cl[ ( ( s_in[i+1] & 0b001111 ) << 2 ) ];
}
return s_out;
}
*s_out++ = b64_cl[ s_in[i] >> 2 ];
*s_out++ = b64_cl[ ( (s_in[i] & 0b000011 ) << 4 ) + ( (s_in[i+1] >> 4) & 0b001111 ) ];
*s_out++ = b64_cl[ ( (s_in[i+1] & 0b001111 ) << 2 ) + ( (s_in[i+2] >> 6) & 0b000011 ) ];
*s_out++ = b64_cl[ ( s_in[i+2] & 0b111111 ) ];
return b64e( s_in+3, s_out );
}
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)'=';
}
}
libb64
本身是用c++实现的,我认为答案也可以被视为不相关。 - malat