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

推荐订阅源

Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
C
Cisco Blogs
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
S
Security Affairs
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
小众软件
小众软件
Security Latest
Security Latest
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
F
Full Disclosure
S
Schneier on Security
L
LangChain Blog
MyScale Blog
MyScale Blog
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
Google Online Security Blog
Google Online Security Blog
Scott Helme
Scott Helme
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
A
Arctic Wolf
Martin Fowler
Martin Fowler
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
Latest news
Latest news
F
Fortinet All Blogs
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
Hacker News: Ask HN
Hacker News: Ask HN

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 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的底层知识和其它编程知识。