一、前言
Base64编码是一种广泛使用的编码方案,将任意二进制数据转换为可打印的ASCII字符字符串。这种编码方式之所以重要,是因为许多通信协议和存储介质对数据的可传输性和可存储性有特定的要求,它们可能无法直接处理或有效传输二进制数据。Base64编码通过使用64个字符的标准字符集——包括大写字母A-Z、小写字母a-z、数字0-9以及符号“+”和“/”,来表示二进制数据中的每一个6位组。为了标识编码的结束,Base64还使用了=
作为填充字符。
在实际应用中,Base64编码常见于电子邮件附件、在URLs中嵌入二进制数据、在网页中内联图像和字体文件、以及在配置文件和数据库中存储非文本数据等多种场景。例如,在HTML或CSS文件中,可以使用Base64编码的图像数据直接作为背景图像,而无需额外的HTTP请求,这在某些情况下可以提高页面加载速度,尽管这样做可能会增加文件大小,因为Base64编码通常会使原始数据膨胀约33%左右。
在C语言中,Base64编码的实现主要涉及几个关键步骤:首先,输入的二进制数据被分成6位的区块;然后,每个6位区块被映射到Base64字符集中相应的字符;接下来,如果最后一个区块不足6位,使用0进行填充,并添加等于号作为填充字符以保持输出的长度一致。
编码过程可以分解为以下步骤:
- 将输入的二进制数据读入内存缓冲区。
- 遍历缓冲区,每次取出24位数据(即3个字节),这足以生成4个Base64字符。
- 将这24位分为4个6位组。
- 使用6位组索引Base64字符集,找到对应的字符并输出。
- 如果到达缓冲区末尾时剩余不足24位,使用0填充剩余位数,并输出相应的Base64字符,同时在输出字符串末尾添加等于号作为填充。
在C语言中实现Base64编码时,可以定义一个包含64个字符的数组,存储Base64字符集,通过循环和位操作来处理数据。由于C语言提供了对内存和位操作的直接访问,因此在性能敏感的应用中,使用C语言实现的Base64编码可以非常高效。
二、代码实操
2.1 将二进制数据转为Base64编码
下面是C语言程序示例,将给定的一串二进制数据转换成Base64编码并打印出来。程序使用了标准库函数,并且没有依赖任何外部库。程序中包含了创建Base64编码所需的所有步骤,如初始化字符集、读取输入数据、编码数据并打印结果。
#include <stdio.h>
#include <stdlib.h>
#define BASE64_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
#define CHUNK_SIZE 3
#define BASE64_CHUNK_SIZE 4
void base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len, char *out_text)
{
unsigned char const *in = bytes_to_encode;
unsigned char *out = (unsigned char*)out_text;
unsigned int i;
unsigned int j;
unsigned int val;
for (i = 0, j = 0; i < in_len - 2; i += CHUNK_SIZE, j += BASE64_CHUNK_SIZE) {
val = ((in[i] & 0xFC) >> 2);
out[j] = BASE64_CHARS[val];
val = ((in[i] & 0x03) << 4) | ((in[i + 1] & 0xF0) >> 4);
out[j + 1] = BASE64_CHARS[val];
val = ((in[i + 1] & 0x0F) << 2) | ((in[i + 2] & 0xC0) >> 6);
out[j + 2] = BASE64_CHARS[val];
val = (in[i + 2] & 0x3F);
out[j + 3] = BASE64_CHARS[val];
}
// Handle the last chunk gracefully.
switch (in_len % CHUNK_SIZE) {
case 1:
out[j] = BASE64_CHARS[((in[i] & 0xFC) >> 2)];
out[j + 1] = BASE64_CHARS[((in[i] & 0x03) << 4)];
out[j + 2] = '=';
out[j + 3] = '=';
break;
case 2:
val = ((in[i] & 0xFC) >> 2);
out[j] = BASE64_CHARS[val];
val = ((in[i] & 0x03) << 4) | ((in[i + 1] & 0xF0) >> 4);
out[j + 1] = BASE64_CHARS[val];
out[j + 2] = BASE64_CHARS[((in[i + 1] & 0x0F) << 2)];
out[j + 3] = '=';
break;
}
}
int main()
{
unsigned char data[] = {0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x27, 0x0b, 0xcf, 0xa3, 0x57, 0x67};
unsigned int data_len = sizeof(data);
char encoded_data[100]; // Assuming enough space for the encoded string.
base64_encode(data, data_len, encoded_data);
encoded_data[data_len * 4 / 3] = '\0'; // Null terminate the string.
printf("Original data: ");
for (int i = 0; i < data_len; i++) {
printf("%02x ", data[i]);
}
printf("\n");
printf("Encoded data: %s\n", encoded_data);
return 0;
}
在例子中,data
数组包含了要被编码的数据。base64_encode
函数接受这些数据,并将其转换为Base64编码。编码后的字符串被存储在 encoded_data
数组中。注意,encoded_data
数组的大小应该足够容纳编码后的字符串,因为Base64编码后的字符串长度通常是原始数据长度的4/3倍。
这个程序将打印出原始数据和编码后的Base64字符串。可以根据需要修改 data
数组的内容,以便测试不同的输入。
2.2 实现图片的base64编码和解码
下面是一个完整的C语言程序,实现了将图片文件编码为Base64字符串,并且可以将Base64字符串解码为图片并保存到本地磁盘。这个示例程序使用标准C库,不依赖于任何第三方库。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 函数:将二进制数据编码为Base64字符串
char* base64_encode(const unsigned char* src, size_t len) {
static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char* out, * pos;
const unsigned char* end, * in;
size_t olen;
int line_len;
olen = len * 4 / 3 + 4; // 输出长度
olen += olen / 72; // 换行符
olen++; // 结尾的NULL字符
out = (char*)malloc(olen);
if (out == NULL) return NULL;
end = src + len;
in = src;
pos = out;
line_len = 0;
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 (line_len += 4, line_len == 72) {
*pos++ = '\n';
line_len = 0;
}
}
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++ = '=';
}
*pos = '\0';
return out;
}
// 函数:将Base64字符串解码为二进制数据
unsigned char* base64_decode(const char* src, size_t* out_len) {
static const unsigned char base64_table[] = {
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // +10
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, // +20
0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, // +30
0x3c, 0x3d, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x3f, // +40
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, // +50
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // +60
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // +70
0x18, 0x19, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // +80
0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, // +90
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, // +100
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 // +110
};
unsigned char dtable[256], * out, * pos, block[4], tmp;
size_t i, count, olen;
int pad = 0;
memset(dtable, 0x80, 256);
for (i = 0; i < sizeof(base64_table); i++)
dtable[base64_table[i]] = (unsigned char)i;
dtable['='] = 0;
count = 0;
for (i = 0; i < strlen(src); i++) {
if (dtable[src[i]] != 0x80)
count++;
}
if (count == 0 || count % 4)
return NULL;
olen = count / 4 * 3;
pos = out = (unsigned char*)malloc(olen);
if (out == NULL) return NULL;
for (i = 0; i < strlen(src); i++) {
tmp = dtable[src[i]];
if (tmp == 0x80) continue;
if (src[i] == '=')
pad++;
block[count++] = tmp;
if (count == 4) {
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
count = 0;
if (pad) {
if (pad == 1) pos--;
else if (pad == 2) pos -= 2;
else {
free(out);
return NULL;
}
break;
}
}
}
*out_len = pos - out;
return out;
}
int main() {
FILE* fp;
char* base64_data;
unsigned char* decoded_data;
size_t decoded_len, base64_len;
char* filename = "test.png"; // 替换为你的图片文件名
char* output_filename = "decoded_image.png"; // 解码后保存的文件名
// 读取图片文件
fp = fopen(filename, "rb");
if (!fp) {
fprintf(stderr, "无法打开文件 %s\n", filename);
return 1;
}
fseek(fp, 0, SEEK_END);
base64_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
unsigned char* image_data = (unsigned char*)malloc(base64_len);
fread(image_data, 1, base64_len, fp);
fclose(fp);
// 将图片数据编码为Base64字符串
base64_data = base64_encode(image_data, base64_len);
free(image_data);
if (!base64_data) {
fprintf(stderr, "Base64 编码失败\n");
return 1;
}
// 输出Base64编码后的数据
printf("Base64 编码结果:\n%s\n", base64_data);
// 解码Base64字符串为图片数据
decoded_data = base64_decode(base64_data, &decoded_len);
free(base64_data);
if (!decoded_data) {
fprintf(stderr, "Base64 解码失败\n");
return 1;
}
// 将解码后的图片数据保存为文件
fp = fopen(output_filename, "wb");
if (!fp) {
fprintf(stderr, "无法打开文件 %s 进行写入\n", output_filename);
free(decoded_data);
return 1;
}
fwrite(decoded_data, 1, decoded_len, fp);
fclose(fp);
free(decoded_data);
printf("图片已成功解码并保存到 %s\n", output_filename);
return 0;
}