惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
V
Visual Studio Blog
F
Full Disclosure
B
Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
Lohrmann on Cybersecurity
月光博客
月光博客
I
Intezer
博客园 - 三生石上(FineUI控件)
Hacker News - Newest:
Hacker News - Newest: "LLM"
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园_首页
P
Proofpoint News Feed
C
Check Point Blog
N
News | PayPal Newsroom
H
Heimdal Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
G
GRAHAM CLULEY
WordPress大学
WordPress大学
C
CERT Recently Published Vulnerability Notes
Y
Y Combinator Blog
Recorded Future
Recorded Future
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog
W
WeLiveSecurity
L
LINUX DO - 热门话题
Microsoft Azure Blog
Microsoft Azure Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Schneier on Security
Schneier on Security
爱范儿
爱范儿
Martin Fowler
Martin Fowler
U
Unit 42
T
Troy Hunt's Blog
S
Securelist
V
V2EX
V2EX - 技术
V2EX - 技术
MongoDB | Blog
MongoDB | Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
罗磊的独立博客
小众软件
小众软件
阮一峰的网络日志
阮一峰的网络日志
Vercel News
Vercel News

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 Hugo PaperMod 主题 去长鹿旅游休博园 1Password 自动化登录堡垒机 深入 Hyperf:Inject 注解是如何工作的? 译|理解容器镜像层 使用 Rust 实现高性能预写日志(Write Ahead Log) 译|使用 Linux 命名空间隔离系统 深入 Hyperf:HTTP 服务是如何处理请求的? Linux 文件权限学习笔记 译|了解 Bitcask:基于日志结构的 KV 存储引擎 还原 SM2 压缩公钥的几种方法 深入 Hyperf:HTTP 服务启动时发生了什么? 一次 Hyperf 注解失效问题分析 将博客迁移到又拍云 译|PHP 7 新的 Hashtable 实现 排查 ES 查询问题:深入了解 json_encode() 函数 浅析 Redlock 分布式锁实现原理 Yar 源码阅读笔记:RPC 服务端 Yar 源码阅读笔记:客户端的并行调用 Yar 源码阅读笔记:客户端的同步调用 Yar 源码阅读笔记:数据传输模块 Yar 源码阅读笔记:消息编码模块 Yar 源码阅读笔记:RPC 通信协议 Yar 源码阅读笔记:开篇 PHP 8 新特性介绍 基于 OpenSSL 实现国密 SM4 加解密 PHP 源码阅读笔记:编译与调试 PHP 什么是惊群问题 大端模式和小端模式 基于 GitHub Actions 定时推送网址到百度站长平台 聊聊五种 I/O 模型 Workerman 源码分析:文件上传 浅入浅出 HTTP 协议 PHP 网络编程:构建 MySQL 蜜罐获取攻击者微信 ID 使用 Workerman 接入 Bilibili 直播弹幕协议 使用 GitHub Actions 自动部署 Hexo 什么是二进制安全 【转载】PHP 程序员进阶之路 优化 Workerman 检查主进程是否存活的逻辑 基于 Redis 实现分布式锁 《PHP 实现 Base64 编码/解码》笔记 PHP 多进程下载必应壁纸 基于 Redis 实现延迟队列 使用 MySQL FIELD() 函数自定义排序规则 Laravel 的 Facades 实现原理 C语言单链表实现约瑟夫环 数据结构学习笔记:顺序栈和链栈(C语言) 使用 QueryList + Redis 下载壁纸 在 Laravel 登录/注册中使用 mews/captcha 扩展包 使用 mews/captcha 扩展包为 Laravel 应用添加图片验证码 给 YOURLS 短网址系统编写插件《批量生成短网址》 给 YOURLS 短网址系统编写插件《Hello World!》 PHP 7.1 使用 json_encode 函数造成浮点类型数据出现精度问题 Laravel 授权策略(Policy)的基本使用 Ubuntu 系统下 WiFi 频繁掉线解决方法 Laravel 应用部署到 Nginx 服务器上的第一个坑 Ubuntu 16.04 LST 安装 Redis 和 Composer PHP 采集逐浪小说章节列表 Nginx 环境 SSL 强制 HTTP 301 跳转到 HTTPS 【工作&生活】第一周 ServiceStack.Redis 与多线程 Count the number of Duplicates C# Base64 加密及解密 ASP.NET MVC 使用动态类型传递数据 Django 使用 order_by() 对数据进行排序操作 ASP.NET + jQuery + AJAX 实现用户登录 ASP.NET 从客户端中检测到有潜在危险的 Request.Form 值 ASP.NET 使用 FormData 实现 AJAX 上传图片 Python 使用 BeautifulSoup 抓取网页 贪心算法之字符串的完美度 一言不和就造轮子之 CookieHelper ASP.NET 上传图片 DBHelper 类 C# 捕获屏幕源码 用 JavaScript 做一些有趣的事情 第一篇文章 好久不见。 如何防止非法调用 PHP 文件 清明小记。 PHP 生成随机字符串 Z-Blog PHP 程序做 301 跳转 PHP 处理数组常用的几个函数 Sublime Text 3 快捷键总结 PHP 使用 file_get_contents() 函数实现采集网页 使用 C# 编写学生信息管理系统 终于到了。 zblog报错: magic_quotes_gpc 和 safe_mode 世事无常。 如何使用 PHP 上传文件 安卓手机编写和运行 PHP 程序的软件 使用 AJAX 刷新、验证 PHP 图片验证码 PHP 生成图片验证码 PHP 之 session 的使用方法 PHP 之 cookie 的使用方法 PHP 实现分页原理详解 Thinkphp 框架中系统常量在 Js 文件中不解析的解决方法
【转载】PHP 实现 Base64 编码/解码
她和她的猫 · 2020-01-08 · via 文章列表 - 她和她的猫

作者:Dennis_Ritchie 原文地址:https://learnku.com/articles/36655

多看看外面的世界

对于现在很多的 PHP 程序员而言,绝大部分时间都是在做业务有关的代码,其它方面可能涉及的比较少,因此今天准备和大家讲讲不一样的知识,Base64加密算法,上午花了一点儿时间用PHP重新实现了一遍,因为之前使用c写的,中间也出现了一些bug,但是很快修复了,代码我已经上传到了码云php-base64-implemention,希望大家下载下来仔细的分析一哈。

知识储备

如果对位操作不熟悉的读者,建议先看一下这方面的内容,非常简单,几分钟就可以了。

友情链接

ASCII图

base64的作用是把任意的字符序列转换为只包含特殊字符集的序列,那么base64加密之后的文本包含哪些字符呢?

  • A-Z
  • a-z
  • 0-9
  • +和/

上面总共包含64个字符,所以每个字符都使用6位来表示,下面有一张表,可以清晰的说明这个问题

PHP实现Base64加密算法

这个是我在维基百科的截图,举个例子,对于Base64加密之后的字符A,对应的数值为0,二进制表示就是000000,如果你现在不懂,没关系,后面我会仔细的讲解加密和解密的过程。

Base64加密

上面我已经提到了,每个Base64字符用6位来表示,但是一个字节是8位,所以3个字节刚好可以生成4个Base64字符,这应该很容易计算出来,下面我给大家举个例子,假如说现在有个字符串为"123",1的ASCII为49,那么转换为二进制就是 00110001,2的ASCII为50,那么转换为二进制就是00110010,3的ASCII为51,那么转换为二进制就是00110011,这三个二进制组合在一起就是这样:001100010011001000110011 上面的二进制位总共24位,从左到右依次取6位,对应关系如下:

  • 第一个6位,001100,查阅上面的图标,对应M
  • 第二个6位,010011,同样的操作,对应T
  • 第三个6位,001000,对应I
  • 第四个6位,110011,对应z

所以经过上面的分析,123转换为Base64之后,就是MTIz,是不是很简单?正常情况下都是很美好的,但是我们刚才的分析建立在加密之前的字节数是3的倍数,那么如果不是呢,比如剩下一个字节,或者是2个,别急,下面来一一分析。

补齐

如果剩下一个字节,那么也就是说剩下8位,因为6位才能组合成一组啊,所以我们需要给它补上,补多少呢?只要4位就行了,12位刚好可以凑成2个Base64字符,那么补什么呢?很简单,补0000就可以了,还是以上面的123为例,但是我们给它加上一个4,所以现在是“1234”,根据上面的分析,123刚好可以转换为4个Base64字符,所以不管它,和上面的一模一样,。现在我们只需要分析后面的4,4的ASCII为52,转换为二进制就是00110100,我们给它加上4个0,那么结果就是001101000000,再对它进行6位分割,001101和000000,查表得到N和A,没错,这就是正确答案,但是为了后面的解码,我们需要在加密后的字符串末尾加上2个“=”,就是“MTIzNA==”。

如果剩下2个字节的话,2个字节刚好16位,6位一组的话,也就是说,少了2位,这样就可以组合成18位了(3个Base64字符),这里我们以字符串“12”为例,1的ASCII转换为二进制是00110001,2的ASCII转换为二进制是00110010,我们将它组合在一起然后补齐之后(加上2个0),就是001100010011001000,按照6位一组进行分割,然后查表求得,结果是MTI,但是为了后面的解码,我们需要在加密后的字符串末尾加上1个“=”,就是“MTI=”。

Base64解密

有了加密的基础,解密就很简单了,以上面的加密结果为例 “MTIzNA==”,下面我们分别分析:

  • 我们首先判断字符串末尾是否有“=”,如果没有的话,那么也就是说,原始字符串没有补位操作,按照4个Base64字符转换为3个8位的字节算法就可以了,4个字符组合起来就是24位,按照8位一个字节,就是三个字节。
  • 如果末尾有2个等于号“==”,也就是说之前进行了补位操作,通过上面加密的流程可以知道,原始字节流中,剩余1个字节,补了4个0,得到了2个Base64字符,所以加密字符串中,除了最后2个字符,其余按照没有补位的转换操作就可以了,对于最后的2个Base64字符,我们把他们对应的二进制位组合起来,然后再进行 右移 4位,就得到了一个8位的字节。
  • 如果末尾有一个等于号“=”,也就是说未加密之前,剩余2个字节,所以按照上面所说的,加密的时候,需要补齐2个0,这样就形成了三个Base64字符,那么除了最后的三个字符,其余的按照正常的转换就可以了,对于最后的三个Base64字符,我们把他们的二进制位组合起来总共18位,然后右移2位,就得到了16位的2个字节。

Base64解密的时候,需要查上面的表,进行反向操作,举个例子,对于Base64字符M,查表得到它对应的6位二进制位为001100,一定要谨记这一点

Base64 代码实现

上面讲解了Base64的加密和解密方法,说起来容易做起来难啊,在PHP里面尤其如此

6位数字 转换为Base64字符(参考上图)

function normalToBase64Char($num)
{
    if ($num >= 0 && $num <= 25) {
        return chr(ord('A') + $num);
    } else if ($num >= 26 && $num <= 51) {
        return chr(ord('a') + ($num - 26));
    } else if ($num >= 52 && $num <= 61) {
        return chr(ord('0') + ($num - 52));
    } else if ($num == 62) {
        return '+';
    } else {
        return '/';
    }
}

上面的代码就是截图的PHP代码实现,这里我提醒大家不要把Base64的a字符和ASCII的a字符混淆起来,两种情况下存在着上图的映射关系,再次提醒一下,这个函数传入的是6位的数据。

Base64字符转换为 6位数字

这个过程就是 6位数字 转换为Base64字符的逆过程,代码如下:

function base64CharToInt($num)
{
    if ($num >= 65 && $num <= 90) {
        return ($num - 65);
    } else if ($num >= 97 && $num <= 122) {
        return ($num - 97) + 26;
    } else if ($num >= 48 && $num <= 57) {
        return ($num - 48) + 52;
    } else if ($num == 43) {
        return 62;
    } else {
        return 63;
    }
}

对于任意一个Base64字符,我们首先要获取到它对应的ASCII值,再根据这个值,通过上面的表的映射关系,求出它对应Base64数值,这个数据就是未加密数据的真实字节数据。

加密代码实现

function encode($content)
{
    $len = strlen($content);
    $loop = intval($len / 3);//完整组合
    $rest = $len % 3;//剩余字节数,需要补齐
    $ret = "";
    //首先计算完整组合
    for ($i = 0; $i < $loop; $i++) {
        $base_offset = 3 * $i;
        //每三个字节组合成一个无符号的24位的整数
        $int_24 = (ord($content[$base_offset]) << 16)
            | (ord($content[$base_offset + 1]) << 8)
            | (ord($content[$base_offset + 2]) << 0);
        //6位一组,每一组都进行Base64字符串转换
        $ret .= self::normalToBase64Char($int_24 >> 18);
        $ret .= self::normalToBase64Char(($int_24 >> 12) & 0x3f);
        $ret .= self::normalToBase64Char(($int_24 >> 6) & 0x3f);
        $ret .= self::normalToBase64Char($int_24 & 0x3f);
    }
    //需要补齐的情况
    if ($rest == 0) {
        return $ret;
    } else if ($rest == 1) {
        //剩余1个字节,此时需要补齐4位
        $int_12 = ord($content[$loop * 3]) << 4;
        $ret .= self::normalToBase64Char($int_12 >> 6);
        $ret .= self::normalToBase64Char($int_12 & 0x3f);
        $ret .= "==";
        return $ret;
    } else {
        //剩余2个字节,需要补齐2位
        $int_18 = ((ord($content[$loop * 3]) << 8) | ord($content[$loop * 3 + 1])) << 2;
        $ret .= self::normalToBase64Char($int_18 >> 12);
        $ret .= self::normalToBase64Char(($int_18 >> 6) & 0x3f);
        $ret .= self::normalToBase64Char($int_18 & 0x3f);
        $ret .= "=";
        return $ret;
    }
}

上面的代码和我之前分析的一模一样。

解密代码实现

解密的过程复杂一点儿,但是只要你看懂上面我所说的,肯定没问题。

function decode($content)
{
    $len = strlen($content);
    if ($content[$len - 1] == '=' && $content[$len - 2] == '=') {
        //说明加密的时候,剩余1个字节,补齐了4位,也就是左移了4位,所以除了最后包含的2个字符,前面的所有字符可以4个字符一组
        $last_chars = substr($content, -4);
        $full_chars = substr($content, 0, $len - 4);
        $type = 1;
    } else if ($content[$len - 1] == '=') {
        //说明加密的时候,剩余2个字节,补齐了2位,也就是左移了2位,所以除了最后包含的3个字符,前面的所有字符可以4个字符一组
        $last_chars = substr($content, -4);
        $full_chars = substr($content, 0, $len - 4);
        $type = 2;
    } else {
        $type = 3;
        $full_chars = $content;
    }

    //首先处理完整的部分
    $loop = strlen($full_chars) / 4;
    $ret = "";
    for ($i = 0; $i < $loop; $i++) {
        $base_offset = 4 * $i;
        $int_24 = (self::base64CharToInt(ord($full_chars[$base_offset])) << 18)
            | (self::base64CharToInt(ord($full_chars[$base_offset + 1])) << 12)
            | (self::base64CharToInt(ord($full_chars[$base_offset + 2])) << 6)
            | (self::base64CharToInt(ord($full_chars[$base_offset + 3])) << 0);
        $ret .= chr($int_24 >> 16);
        $ret .= chr(($int_24 >> 8) & 0xff);
        $ret .= chr($int_24 & 0xff);
    }
    //紧接着处理补齐的部分
    if ($type == 1) {
        $l_char = chr(((self::base64CharToInt(ord($last_chars[0])) << 6)
                | (self::base64CharToInt(ord($last_chars[1])))) >> 4);
        $ret .= $l_char;
    } else if ($type == 2) {
        $l_two_chars = ((self::base64CharToInt(ord($last_chars[0])) << 12)
                | (self::base64CharToInt(ord($last_chars[1])) << 6)
                | (self::base64CharToInt(ord($last_chars[2])) << 0)) >> 2;
        $ret .= chr($l_two_chars >> 8);
        $ret .= chr($l_two_chars & 0xff);
    }
    return $ret;
}

告诫

任何代码都不能缺少理论的支撑,所以在看代码前,请仔细的阅读Base64的基本原理,一旦原理看懂了,阅读代码就不是那么难了,任何时候阅读别人的代码,这都是应该谨记的地方,之前就已经告诉大家了,代码已经上传到码云,php-base64-implemention,代码没有问题,完全可以运行,如果有问题可以找我,博文的最后面有我的联系方式,祝您假期愉快。

交流学习

我建了一个qq群,大家平时可以交流学习,我也会给大家讲解Laravel的底层知识和其它编程知识。