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

🧑 作者简介:阿里巴巴嵌入式技术专家,深耕嵌入式+人工智能领域,具备多年的嵌入式硬件产品研发管理经验。

📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导、简历面试辅导、技术架构设计优化、开发外包等服务,有需要可私信联系。

🗄️ 专栏介绍:本文归属于专栏《C/C++》,专栏文章平均质量分92,持续更新中,欢迎大家免费订阅关注。

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/733514.shtml

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

相关文章

你应该打好你的日志,起码避免被甩锅

大家好&#xff0c;我是蓝胖子,相信大家或多或少都有这样的经历&#xff0c;当你负责的功能出现线上问题时&#xff0c;领导第一时间便是找到你询问原因&#xff0c;然而有时问题的根因或许不在你这儿&#xff0c;只是这个功能或许依赖了第三方或者内部其他部门&#xff0c;这个…

PHP端口批量查询工具单文件

查询速度较慢&#xff0c;建议输入20个以内端口&#xff0c;暂未整理端口服务信息所以暂时不显示。 <?php error_reporting(0); //打开报错&#xff1a;去行首双斜杠 header(Content-type:text/html;charset utf-8); //如果乱码删行首双斜杠 header(Cache-Control: no-ca…

【Unity InputSystem】实用指南:在PC端(鼠标与键盘)、手机端(触摸屏)、主机手柄上同步实现角色移动与跳跃功能

前引 随着Unity的不断发展&#xff0c;开发者对于项目的输入系统要求也日益提高。在进行多平台适配和跨平台移植时&#xff0c;常常需要改变输入系统&#xff0c;这给开发者带来了不少困扰。而Unity官方推出的InputSystem插件&#xff0c;则是为了解决这一问题而推出的全新输入…

Linux内存管理--系列文章壹

一、引子 作者、我在上班闲着没事的时候&#xff0c;看了一些关于Linux内存管理和程序装载、链接的文章&#xff0c;然后自己就总结出了一些东西。 本系列文章一方面将资料中的长篇大论总结到最少、以方便可以直接找到答案&#xff0c;一方面也是方便面试的时候可以吹牛逼。 L…

【快捷键】Mac如何打出数学符号和希腊字母

下面是一些常见的希腊字符及其对应的快捷键&#xff1a; α (Alpha): Option Aβ (Beta): Option Bγ (Gamma): Option Gδ (Delta): Option Dε (Epsilon): Option Eζ (Zeta): Option Zη (Eta): Option Hθ (Theta): Option Qι (Iota): Option Iκ (Kappa): Opti…

【Docker】golang使用DockerFile正确食用指南

【Docker】golang使用DockerFile正确食用指南 大家好 我是寸铁&#x1f44a; 总结了一篇golang使用DockerFile正确食用指南✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 问题背景 今天寸铁想让编写好的go程序在docker上面跑&#xff0c;要想实现这样的效果&#xff0c;就需要用…

小程序 van-field label和输入框改成上下布局

在组件上面加个样式就行&#xff1a;custom-style"display:block;" <van-field label"备注说明" type"textarea" clearable title-width"100px" custom-style"display:block;" placeholder"请输入" /> …

详细比较Python、Julia、Rust

大家好&#xff0c;Python、Julia和Rust三种编程语言具有一定相似性&#xff0c;也有一些明显的差异。本文对这些语言进行详细的比较&#xff0c;举例说明三者的主要特点和特性&#xff0c;帮助大家更好地了解这三种语言&#xff0c;并能在编程时选择合适的语言进行开发。 1.语…

大载重无人机基础技术,研发一款50KG负重六旋翼无人机技术及成本分析

六旋翼无人机是一种多旋翼无人机&#xff0c;具有六个旋翼&#xff0c;通常呈“X”形布局。它采用电动串列式结构&#xff0c;具有垂直起降、悬停、前飞、后飞、侧飞、俯仰、翻滚等多种飞行动作的能力。六旋翼无人机通常被用于航拍、农业植保、环境监测、地形测绘等领域。 六旋…

GRU-深度学习循环神经网络情感分类模型搭建

摘要&#xff1a; 本文详细介绍了基于GRU的深度学习循环神经网络在情感分类任务中的应用&#xff0c;涵盖基础知识回顾、功能实现、技巧与实践、性能优化与测试&#xff0c;以及常见问题解答等环节。 阅读时长&#xff1a;约30分钟 关键词&#xff1a;GRU, 深度学习, 循环神经…

Day34-Linux网络管理4

Day34-Linux网络管理4 1. IP地址分类与子网划分基础1.1 什么是IP地址1.2 十进制与二进制的转换1.3 IP地址的分类1.4 私网地址和局域网地址 2. 通信类型3. 子网划分讲解3.1 为什么要划分子网&#xff1f;3.2 什么是子网划分&#xff1f;3.3 子网划分的作用&#xff1f;3.4 子网划…

云计算项目十一:构建完整的日志分析平台

检查k8s集群环境&#xff0c;master主机操作&#xff0c;确定是ready 启动harbor [rootharbor ~]# cd /usr/local/harbor [rootharbor harbor]# /usr/local/bin/docker-compose up -d 检查head插件是否启动&#xff0c;如果没有&#xff0c;需要启动 [rootes-0001 ~]# system…

如何使用Hexo搭建个人博客

文章目录 如何使用Hexo搭建个人博客环境搭建连接 Github创建 Github Pages 仓库本地安装 Hexo 博客程序安装 HexoHexo 初始化和本地预览 部署 Hexo 到 GitHub Pages开始使用发布文章网站设置更换主题常用命令 插件安装解决成功上传github但是web不更新不想上传文章处理方式链接…

Yolov8模型用torch_pruning剪枝

目录 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; 原理 遍历所有分组 高级剪枝器 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680…

JVM基本概念、命令、参数、GC日志总结

原文: 赵侠客 一、前言 NPE&#xff08;NullPointerException&#xff09;和OOM&#xff08;OutofMemoryError&#xff09;在JAVA程序员中扮演着重要的角色&#xff0c;它也是很多人始终摆脱不掉的梦魇&#xff0c;与NPE不同的是OOM一旦在生产环境中出现就意味着只靠代码已经无…

java集合题库详解

1. Arraylist与LinkedList区别 可以从它们的底层数据结构、效率、开销进行阐述哈 ArrayList是数组的数据结构&#xff0c;LinkedList是链表的数据结构。 随机访问的时候&#xff0c;ArrayList的效率比较高&#xff0c;因为LinkedList要移动指针&#xff0c;而ArrayList是基于索…

深入理解数据压缩流程及 zlib 库中相关函数

数据压缩是一种常见的操作&#xff0c;可以有效地减小数据的体积&#xff0c;节省存储空间和网络带宽。在本文中&#xff0c;我们将深入讨论数据压缩的流程&#xff0c;并详细解释 zlib 库中相关函数的使用&#xff0c;包括 deflateInit2()、deflate()、deflateEnd() 等。我们将…

Java 客户端向服务端上传文件(TCP通信)

一、实验内容 编写一个客户端向服务端上传文件的程序&#xff0c;要求使用TCP通信的的知识&#xff0c;完成将本地机器输入的路径下的文件上传到D盘中名称为upload的文件夹中。并把客户端的IP地址加上count标识作为上传后文件的文件名&#xff0c;即IP&#xff08;count&#…

OpenSearch 与 Elasticsearch:哪个开源搜索引擎适合您?

当谈论到搜索引擎产品时&#xff0c;Elasticsearch 和 OpenSearch 是两个备受关注的选择。它们都以其出色的功能和灵活性而闻名&#xff0c;但在一些方面存在一些差异。在本文中&#xff0c;我们将从功能和延展性、工具与资源、价格和许可这三个角度对这两个产品进行论述。通过…

qt+opencv人脸人眼检测识别

项目运行涉及到opencv库&#xff0c;以及haarcascade_frontalface_default.xml和haarcascade_eye_tree_eyeglasses.xml。qt配置opencv可见先前文章&#xff0c;另外这两份OpenCV 中用于眼睛检测的级联分类器xml文件&#xff0c;是我在网上下载的。 把要使用到的文件都放到当前…