【C语言】C语言编程实战:Base64编解码算法从理论到实现(文末附完整代码)

文章目录

  • 1. 概述
  • 2. 原理
    • 2.1 Base64编码表
    • 2.2 Base64编码步骤
    • 2.3 Base64解码步骤
  • 3. 核心代码解读
  • 4. 完整代码下载
  • 5. 总结

1. 概述

Base64算法是一种基于64个字符的编码算法,常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据。该算法使用可打印字符集来表示二进制数据,使得数据可以在文本格式中安全地传输和存储

2. 原理

为了保证所输出的编码为可读字符,Base64制定了一个由特定ASCII码组成的编码表,以便进行统一编码转换。编码表的大小为2^6=64,这就是Base64名称的由来。

如下所示,Base64编码表包括A-Za-z0-9+/共64个可打印字符。

2.1 Base64编码表

码值字符|码值字符|码值字符|码值字符
0A|16Q|32g|48w
1B|17R|33h|49x
2C|18S|34i|50y
3D|19T|35j|51z
4E|20U|36k|520
5F|21V|37l|531
6G|22W|38m|542
7H|23X|39n|553
8I|24Y|40o|564
9J|25Z|41p|575
10K|26a|42q|586
11L|27b|43r|597
12M|28c|44s|608
13N|29d|45t|619
14O|30e|46u|62+
15P|31f|47v|63/

2.2 Base64编码步骤

  1. 将源数据划分为每3个字节一组,一组组的进行运算。
  2. 将每一组的3个字节转换为二进制,得到24位的数据(如果最后一组源数据不足3字节,不足的部分补0,如M补成M00,Ma补成Ma0)。
  3. 24位数据按照每6位一组进行重新划分,得到4组长度为6位的新数据。
  4. 将每组6位长度的新数据(高2位补0)转换为10进制数,得到一个范围为[0, 63)之间的数字。4组新数据共得到4个10进制数字。
  5. 拿上一步计算出的4个10进制数字分别去查Base64编码表,得到4个ASCII符号,将其连起来,得到一个4字节长度的字符串,这就是这一组3字节源数据计算得到的base64值了。
  6. 重复步骤2-5,直到计算完成。把每一组3字节源数据计算得到的base64值连接起来。
  7. 如果源数据长度刚好是3的整数倍,那么上一步计算完就得到最终的base64编码了。如果源数据最后一组不足3字节,可能只有1个或2个字节,这时存在2个或者1个填充标记(0),此时需要将上一步计算出的base64编码结尾1到2个字符替换为=,有几个填充标记就就替换为几个=。以标示实际数据只占原来编码的一部分,解码时需要把这部分数据排除掉。

理论总是抽象的,下面我们直接看例子。

  • 举例子
  1. 假设需要编码的文本字符串是Man(在ASCII中,M=77, a=97, n=110)。

     将字符转换为ASCII值:M a n77 97 110将ASCII值转换为二进制:01001101 01100001 01101110重新分组为4个6位的单元:010011 010110 000101 101110将这些6位的单元转换为十进制:19 22 5 46根据Base64索引表找到对应的字符:T W F u
    

因此,Man这个字符串的Base64编码结果是TWFu

  1. 假设我们有一个字符串Ma,它只有两个字节(在ASCII中,M=77, a=97)。

     首先将字符转换为ASCII值,再将ASCII值转换为二进制形式:M a77 9701001101 01100001现在我们只有两个字节,少于一个完整的3字节组。为了形成一个完整的3字节组,我们需要对最后一个不完整的字节组进行填充。在这个例子中,我们添加一个字节的填充,即8个比特位的0。01001101 01100001 00000000接下来,我们将这个24位的数据重新划分为4个6位的单元:010011 010110 000100 000000将这些6位的单元转换为十进制数,然后对照Base64编码表找到相应的字符:19 22 4 0T W E A然而,因为我们进行了填充,所以要在Base64编码后加上等号。在这个例子中,我们进行了一个字节的填充,所以在Base64编码末尾添加一个等号。
    

因此,字符串Ma的Base64编码结果是TWE=

  1. 假设我们有一个字符串Ma,它只有一个字节(在ASCII中,M=77)。

     首先将字符 "M"转换为ASCII值,并将该值转换为二进制形式:M77二进制: 01001101现在我们只有一个字节,远少于一个完整的3字节组。为了形成一个完整的3字节组,我们需要对其进行填充。在这个例子中,我们添加两个字节的填充,即16个比特位的0:01001101 00000000 00000000接下来,我们将这个24位的数据重新划分为4个6位的单元:010011 010000 000000 000000将这些6位的单元转换为十进制数,然后对照Base64编码表找到相应的字符:19 16 0 0T Q A A由于我们进行了填充,所以要在Base64编码后加上两个等号==,以标示实际数据只占原来编码的一部分。在这个例子中,我们进行了两个字节的填充,所以在Base64编码末尾添加两个等号。
    

因此,字符串M的Base64编码结果是TQ==

2.3 Base64解码步骤

解码Base64编码的过程与编码相反,将每个Base64字符转换为对应的6位二进制值,然后将这些6 位值组合成原始的二进制数据,再还原回去即可。

3. 核心代码解读

  1. base64编码表
char *base64_encodetable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  1. base64最终结果长度计算
//每3个字节一组转化为4字节
//因此计算可以分多少组,将组数乘以4就是编码后的长度
codelength = ((length / 3) + (length % 3 > 0 ? 1 : 0)) * 4;
  1. 每3个字节一组转化为4字节并进行计算
/** src为源数据* length为源数据长度* base64code用于存储最终的base64编码结果**///每3个字节分一组,调用_base64_section_encode()计算其base64,然后把每一组的base64值拼接起来。
for (i = 0; i < length / 3; i++) {tmp = _base64_section_encode(src + i * 3, 3);strcat(base64code, tmp);
}//如果源数据长度不是3个整数倍,那么将剩余的1个或者2个字节数据单独分一组计算其base64,然后把计算出的base64值拼接到之前的结果。
if (length % 3) {tmp = _base64_section_encode(src + length - (length % 3), length % 3);strcat(base64code, tmp);
}
  1. 源数据3字节分组计算base64
//数字字符串表
static char *num_table = "0123456789";/** char类型数转换为2进制字符串格式, 如2->"10", 6->"110"**/
static char *char2binstr(char value)
{int i = 0;char binstr[9] = {};//取到每一位之后,查表得到对应的字符拼接成一个字符串for (i = 0; i < 8; i++)binstr[7 - i] = num_table[(value & (0x1 << i)) >> i];return strdup(binstr);
}/** 2进制字符串格式数据转换为10进制数字,如"10"->2, "110"->6**/
static char binstr2char(char *binstr)
{int i = 0;char value = 0;int length = 0;if (!binstr)return 0;length = strlen(binstr);//取得2进制字符串的每一个字节,将其转化对应的数字,然后还原数据。for (i = 0; i < length; i++)value += (binstr[length - 1 - i] - 0x30) << i;return value;
}/** 将源数据每3字节作为一组数据进行一次转换,并计算这一组的base64值。**/
static char *_base64_section_encode(char *substr, int length)
{int i = 0;/** 用于存储每一组数据计算得到的base64值。* 每个分组计算完base64,都会得到一个4字节长度的ASCII码。即使源数据不足3字节,也会在计算出的base64结果后面使用=填充到4个字节。* 这里我们直接把4个字节全部预初始化为=,这样做的好处是当最后一次运算源数据不足3位时,就不用再补位了,节省几行代码。**/char dest[5] = "====";/** 用于存储转换后的24字节二进制数格式字符串。当源数据不足3字节时,低位补0填充。* 这里我们直接把24字节全部预初始化为0,当源数据不足3位时,就不用再补位了,节省代码。**/char binstr[24] = "000000000000000000000000";//存储运算过程中的一些临时结果char tmp[7] = {};char *tmp1 = NULL;//先将源数据转换为一个连续的2进制格式字符串,长度为24字节for (i = 0; i < length; i++) {tmp1 =  char2binstr(substr[i]);strncpy(binstr + i * 8, tmp1, 8);free(tmp1);}/** 转换后得到的24字节2进制格式字符串,分为4组,每组取6个字节,将这6个字节的2进制格式字符串转换回字符,将字符(大小范围是0-63)作为数组下标查表得到一个ASCII字符。* 这里分3种情况:*   1.如果最后一次转换只有1个字节数据,那么会转换出2个新字节,即查表2次,此时还剩余2个空白字节不参与转换,需要在编码结果后加2个=补位。*   2.如果最后一次转换只有2个字节数据,那么会转换出3个新字节,即查表3次,此时还剩余1个空白字节不参与转换,需要在编码结果后加1个=补位。*   3.如果最后一次转换有完整的3个字节数据,那么会转换出完整的4个新字节,即查表4次,此时无需补位。* 由此可见,实际计算次数此时就是在源数据字节数基础上+1。源数据字节数length的可能值是1、2、3**/for (i = 0; i < length + 1; i++) {strncpy(tmp, binstr + 6 * i, 6);dest[i] = base64_encodetable[binstr2char(tmp)];}return strdup(dest);
}

编码流程的核心代码如上所示,解码过程大同小异,这里就不再讲解了,请自行下载完整代码查阅。

为了提高效率,代码逻辑上做了一些优化,并非逐字逐句按照编码步骤编写的代码。当然,也还存在很多优化点,比如二进制转换部分其实可以不用使用字符串,使用位运算来替换,在高频应用场景下,可以进一步提高算法执行效率。

4. 完整代码下载

代码写于多年之前,已应用到很多项目中,欢迎大家拍砖。该项目起初开源于码云平台,现已mirror到CSDN的gitcode平台,代码地址:https://gitcode.com/g310773517/base64。欢迎大家Watch、Star、Fork。

5. 总结

Base64编码具有以下特点:

  • 编码后的数据长度总是比原始数据长约 1/3。
  • Base64 编码是一种可逆的编码方式,可以通过解码还原出原始数据。

总的来说,Base64算法是一种方便、简单且广泛使用的编码方式,用于在文本格式中安全地传输和存储二进制数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/724380.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于 selinux 规则

1. 查看selinux状态 SELinux的状态&#xff1a; enforcing&#xff1a;强制&#xff0c;每个受限的进程都必然受限 permissive&#xff1a;允许&#xff0c;每个受限的进程违规操作不会被禁止&#xff0c;但会被记录于审计日志 disabled&#xff1a;禁用 相关命令&#xf…

2024 年如何复用 ChatGPT 从头开始​​快速学习 Python

今天聊聊计算机专业的学习以及一个升级打怪路线。 有需自取 计算机必看经典书单和课程资源(含下载方式) 随着 Chat GPT 等聊天机器人的兴起&#xff0c;许多人想知道人工智能助手是不是可以做编程老师。在这篇文章里&#xff0c;我会分享作为一个完全的小白如何用 ChatGPT 学…

flutter_gen依赖

flutter_gen 5.4.0 flutter项目内终端&#xff1a; dart pub global activate flutter_gen export PATH“ P A T H " : " PATH":" PATH":"HOME/.pub-cache/bin” fluttergen

薪资18K需要什么水平?来看看97年测试工程师的面试全过程…

我的情况 大概介绍一下个人情况&#xff0c;男&#xff0c;本科&#xff0c;三年多测试工作经验&#xff0c;懂python&#xff0c;会写脚本&#xff0c;会selenium&#xff0c;会性能&#xff0c;然而到今天都没有收到一份offer&#xff01;从年后就开始准备简历&#xff0c;年…

iOS OC与Swift文件相互调用

在OC工程中接入纯swift二方库时二方库开发者提供了swift开发代码示例&#xff0c;因此如何在本项目的OC工程中引入swift文件并引用成为了现在面临的一个问题。&#xff08;本博客未记录OC工程引入swift打包编译阶段需修改的内容&#xff09; 1. OC工程中引入swift文件并调用 …

C++从零开始的打怪升级之路(day38)

这是关于一个普通双非本科大一学生的C的学习记录贴 在此前&#xff0c;我学了一点点C语言还有简单的数据结构&#xff0c;如果有小伙伴想和我一起学习的&#xff0c;可以私信我交流分享学习资料 那么开启正题 今天分享的是关于适配器了解以及一些简单适配器实现 1.容器适配…

python基础——基础语法

文章目录 一、基础知识1、字面量2、常用值类型3、注释4、输入输出5、数据类型转换6、其他 二、字符串拓展1、字符串定义2、字符串拼接3、字符串格式化4、格式化精度控制 三、条件/循环语句1、if2、while3、for循环 四、函数1、函数定义2、函数说明文档3、global关键字 五、数据…

Docker Register 搭建私有镜像仓库

1 安装 docker (1)更新软件源 sudo apt update (2)安装 docker 组件 sudo apt install docker.io (3)启动 docker 服务 sudo systemctl start docker (4)设置 docker 服务开机自启动 sudo systemctl enable docker (5)验证 docker 功能 sudo docker contai…

代码随想录 回溯算法-组合

目录 77.组合 216.组合总和||| 17.电话号码的字母组合 39.组合总和 40.组合总和|| 77.组合 77. 组合 中等 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4…

【论文精读】大语言模型融合知识图谱的问答系统研究

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

一文看懂 | 机器视觉前景

机器视觉概述&#xff1a;机器视觉是人工智能发展中的一个重要分支&#xff0c;其主要目标是用机器替代人眼进行测量和判断。 机器视觉系统通过图像摄取装置将目标转换成图像信号&#xff0c;然后传送给专用的图像处理系统&#xff0c;最终得到目标的形态信息。 全球机器视觉…

链路负载均衡之策略路由

一、策略路由的概念 一般来说&#xff0c;防火墙是根据目的地址查看路由&#xff0c;这种情况下只能根据报文的目的地址为用户提供服务&#xff0c;没办法更加灵活对内网用户进行区分&#xff0c;让不同用户流量走不同的链路转发&#xff0c;如根据源地址、应用协议等区分流量…

照片 200K 的尺寸是多大?如何调整照片到200k?

在数字时代&#xff0c;我们经常需要在互联网上上传和分享照片。在上传照片时&#xff0c;我们常常会遇到文件大小的限制&#xff0c;通常以“K”&#xff08;千字节&#xff09;为单位。如果您需要上传的照片大小为 200K&#xff0c;那么它的实际尺寸是多大呢&#xff1f;本文…

卫星通信基础知识

为了满足日益增长的卫星业务需求&#xff0c;多路径路由被用来通过多个传输路径并发地传输每个流。然而&#xff0c;动态变化的卫星拓扑结构带来了挑战&#xff0c;多路径路由规划和不同路径之间的业务分裂。 因此&#xff0c;在本文中&#xff0c;我们提出了一个GNN启用多路径…

栈【stack】 栈的【STL】运用 【单调栈】 【单调栈】的详细解释【图文讲解】 洛谷P5788 【模板】单调栈 【单调队列】

文章目录 栈的定义栈的STL运用 单调栈 & 单调队列单调栈的详细解释【图文讲解】例题&#xff1a;洛谷P5788 【模板】单调栈code↓洛谷P5788 【模板】单调栈 AC 栈的定义 栈的修改与访问是按照后进先出的原则进行的 栈通常被称为是后进先出&#xff08;last in first out&…

centos ps命令

1. ps &#xff1a; Process Status的缩写&#xff0c;用于查询系统的进程状态 2. 常用参数&#xff1a;-ef 返回系统中所有用户的所有进程的完整列表 -A 显示所有进程&#xff08;同-e&#xff09; -a 显示当前终端的所有进程 -u 显示进程的用户信息 -o 以用户…

33、一致性哈希算法分区

33、一致性哈希算法分区 一、背景二、介绍三、步骤1、算法构建一致性哈希环2、Redis服务器IP节点映射3、key落到服务器的落键规则 四、优点1、一致性哈希算法的容错性2、一致性哈希算法的扩展性 五、缺点1、一致性哈希算法的数据倾斜问题2、拓展 六、总结 一、背景 一致性哈希…

sylar高性能服务器-日志(P57-P60)内容记录

文章目录 P57-P60&#xff1a;序列化模块Varint&#xff08;编码&#xff09;Zigzag&#xff08;压缩&#xff09;class ByteArrayNode&#xff08;链表结构&#xff09;成员变量构造函数写入读取setPositionaddCapacity 测试 P57-P60&#xff1a;序列化模块 ​ 序列化模块通常…

LeetCode2643. Row With Maximum Ones

文章目录 一、题目二、题解 一、题目 Given a m x n binary matrix mat, find the 0-indexed position of the row that contains the maximum count of ones, and the number of ones in that row. In case there are multiple rows that have the maximum count of ones, t…

某酷ckey140逆向(之前下架了重新上传补发)

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018…