深入剖析Base64加解密中遇到的坑点
前言
最近开发过程中遇到了关于使用base64加密传输遇到的神奇问题。需求就是用户的id在链接上露出时需要加密处理,于是后端把下发的用户id改成了base64加密处理后下发了,前端只需要把加密后的用户id原样传给后端就行。就是这个看似简单的流程,前端啥也没干只是原样透传,但后端有概率拿到的用户id不对。
问题描述
本地写个后端服务模拟当时的情景:
后端框架:nest
@Get('getUserInfo')
getUserInfo(@Req() req) {
const query = req.query
const cookie = req.cookies
console.log('cookie', cookie)
// 优先取参数中的userId,没有则取cookie中的uid
const userId = query.userId || cookie.uid
// base64加密
const token = Buffer.from(userId).toString('base64')
console.log('加密后的token', token)
// 返回base64加密后的token
return {
code: 0,
data: {
userId,
// base64加密
token
}
}
}
前端请求后:

服务这边能够正常拿到cookie并使用base64加密,然后把加密后的token返回给前端
前端也正常拿到了后端返回的加密后的token

最后前端只需要在用户分享时把加密的token带在链接上,从这个链接进入时再把链接上加密的token带给后端即可,中间不需要做任何处理。
就是这个过程,出现了奇怪的现象,绝大多数用户都是OK,但是会有一些用户的token带给后端时,后端解不出来了。
心想这跟前端好像没啥关系,因为前端压根没处理后端返回的token,后端给我啥,我只是原样给他传了啥。
经排查发现,所有有问题的用户id都是加密后的token中包含了+符号
比如这样的:zm+3DQ/gYeMzQ/HM2L76+CA==传到后端时,所有的+都变成了空格,导致后端解出来是错的

URL是如何进行编码的
这个问题的主要原因还是因为URL被编码造成的,由于请求是get请求,所以最终所有的参数都是拼接在链接上的,最开始前端传给后端的token是没有经过编码的,那它为什么自己编码了?并且编码后与预期的还不一致?
由于种种历史原因,RFC与W3C都定义过URL的编码标准
RFC规范
在RFC3986中提到:除了 数字 、 字母 、 -_.~ 不会被转义,其他字符都会被以百分号(%)后跟两位十六进制数 %{hex} 的方式进行转义。在这个规则中空格会被转为%20,而+会被转为%2B

W3C规范
在W3C规范中却又说空格可以被编码为+或%20

为什么会同时存在两种规范,这不是在挖坑吗?
因为URL中不能存在空格,所以在URL中的空格会自动替换成
+或%20
这就是上面出现+变空格的原因,在你不确定正在以哪一个规范进行编解码时,就很容易出现这个问题。它可能是浏览器造成的,也可能是开发语言的规范不同造成的。
比如Google搜索:
当我们搜索s+2时,地址栏出现的是s%2B2

当我们搜索s 2时,地址栏出现的却是s+2

这里就是空格被编码为
+了,你要是不了解W3C这条规范,是不是觉得匪夷所思了
前端编码规范
在JS中对字符串进行编码的方法有三个:escape、encodeURI、encodeURIComponent
escape已经被废弃了,不再推荐使用,所以我们这里只需要关注后面两个的区别
encodeURI
该函数只会编码URI中完全禁止的字符。该函数的目的是对URI进行完整的编码,因此对以下在URI中具有特殊含义的 ASCII 标点符号,
encodeURI是不会进行转义的(;/?&=+$,#)
所以对于encodeURI来说,空格会被编码为%20,但是+并不会编码。因为空格是URI中禁止的字符,而+不是

总结来说就是:
encodeURL除了这些A-Z a-z 0-9 ; , / ? : @ & = + $ – _ . ! ~ * ‘ ( ) # 不会被编码,其余字符都会被编码
encodeURIComponent
功能与encodeURI类似,但是
encodeURIComponent编码的范围更广,并且该函数一般用于对URI的参数部分进行编码
对于encodeURIComponent来说,空格会被编码为%20,+会被编码为%2B

总结来说就是:
encodeURLComponent除了这些A-Z a-z 0-9 - _ . ! ~ * ' ( ) 不会被编码,其余字符都会被编码
两者使用场景的差异
- 当encode的内容不作为URI参数时,使用
encodeURI进行编码
const url = encodeURI('https://www.qidian.com')
// 'https://www.qidian.com'
- 当encode的内容作为URI参数时,使用
encodeURIComponent进行编码
const deepLink = `weixin://webview?url=${encodeURIComponent('https://www.baidu.com')}`
// weixin://webview?url=https%3A%2F%2Fwww.baidu.com
结论
对于JS的编码方法来说,只有encodeURIComponent会对+进行编码,并且编码规范是RFC3986,也就是说使用这个方法空格会被转为%20,而+会被转为%2B。从而也就不会出现+变空格或空格变+的问题。
上述问题是如何产生的?
上面分别介绍了URL的编码规范,以及前端编码方法应用的规范。总结下来就是空格不会在前端产生,前端应用的编码规范不会将空格编码成+,也不会把+解码成空格。

并且特意写了个node服务来模拟当时的场景。
结论是:只要传给后端的base64字符串在前端经过了编码就不会有问题。因为上面我们介绍过浏览器的编码规范,确实是会存在+变空格的问题。所以我们需要主动编码,不要把编码的机会留给浏览器。
- 前端编码了,后端拿到的也是正常的

- 没编码,后端拿到的
+变成空格了

所以当时前端未进行编码时,从CURL中就能看到+已经变成了空格,但后面前端编码后,curl看是正常的,后端解码出来却还是有问题的。
我这边怎么都复现不了当时传给后端是编码过的base64字符串,后端拿到的却还是+变成了空格
没办法,只好找后端同学问问他当时是怎么解码的...
经过一番验证后,结论是他那边多解码了一次,他们框架层有一次自动解码
'zm%2B3DQ%2FgYeMzQ%2FHM2L76CA%3D%3D' ----> 'zm+3DQ/gYeMzQ/HM2L76CA=='
实际上这里就已经是正确的了,但后端同学又自己解码了一次,按理来说再次解码应该也不会有问题

但是!!!这是因为javascript遵循的是RFC3986规范,但java好像并不是
java自带的decode方法底层是这样实现的

这里是按W3C的规范,由于URL中不能存在空格,所以URL Encode 会把空格替换成+,然后解码也同样会将+替换成空格。真相了....
解决方案
- 按理来说我们只需要保证传给后端的
+字符按RFC3986规范编码成了%2B就不会有问题,不要把编码的机会留给浏览器,在JS中只需调用encodeURIComponent即可 - 后端接收到带空格的
base64字符串时,通过正则将空格替换为+,因为base64中不会出现空格 - 由于标准
Base64编码包含64个字符A-Z, a-z,0-9,+,/,=,有一种URL safe的base64格式,把其中的+,/换成-,_,也能够解决上面的问题。
深入剖析Base64加解密中遇到的坑点的更多相关文章
- java base64加解密
接上篇java Base64算法. 根据之前过程使用base64加解密,所以写成了工具类. 代码示例; public class Base64Util { private static Logger ...
- QuickBase64 - Android 下拉通知栏快捷base64加解密工具
Android Quick Setting Tile Base64 Encode/Decode Tool Android 下拉通知栏快捷 base64 加解密,自动将剪切板的内容进行 base64 E ...
- java之BASE64加解密
1.简介 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到. 注:位于jdk的java.util包中. 2. ...
- JAVA加解密 -- Base64加解密
Base64算法实现:可以将任意的字节数组数据,通过算法,生成只有(大小写英文.数字.+./)(一共64个字符)内容表示的字符串数据. private static final String str ...
- base64加解密字符串
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOExceptio ...
- oracle里面base64加解密
1. base64 的解密函数select utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw('dGVzdA= ...
- [编码解码] 关于AES加解密中CBC模式的IV初始化向量的安全性问题
copy from : https://www.jianshu.com/p/45848dd484a9 前段时间,在研究HLS的AES加密,由于一个地方电视台的HLS流有AES加密,在查看了相关的加解密 ...
- python编写自己的base64加解密工具
0x00 Base64编码的用途 在网络传输中,不是所的的内容都是可打印字符,其中绝大多数数据是不可见字符,base64可以基于64个可打印字符来表示这些带有不可打印字符的传输数据. 0x01 Bas ...
- JavaScript Base64加解密
Base64加密算法是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045-RFC2049,上面有MIME的详细规范.Base64编码可用于在HTTP环境下传递较长的标识信 ...
- Pyton基础-base64加解密
base64加密后是可逆的,所以url中传输参数一般用base64加密 import base64 s='username=lanxia&username2=zdd' new_s=base64 ...
随机推荐
- 鸿蒙NEXT元服务:利用App Linking实现无缝跳转与二维码拉起
[效果] 元服务链接格式(API>=12适用):https://hoas.drcn.agconnect.link/ggMRM 生成二维码后效果: [参考网址] 使用App Linking实现 ...
- 从零打造基础HTTP服务器:揭秘背后的技术魔法-MiniTomcat
<从零打造基础HTTP服务器:揭秘背后的技术魔法-MiniTomcat> 嘿,各位技术发烧友们!今天咱们要一起踏上一段超级刺激的技术之旅,去揭开从零实现一个基础HTTP服务器的神秘面纱.这 ...
- 揭秘UGO SQL审核功能4大特性,让业务平滑迁移至GaussDB
业务挑战 数据库是企业应用系统的核心,SQL作为数据库查询.更新等操作的标准语言,重要性不言而喻.然而在实际的SQL开发过程中,也面临着诸多挑战: 数据库应用开发人员的SQL能力良莠不齐,经常写出不符 ...
- 【kernel】从 /proc/sys/net/ipv4/ip_forward 参数看如何玩转 procfs 内核参数
本文的开篇,我们先从 sysctl 这个命令开始. sysctl 使用 sysctl 是一个 Linux 系统工具,后台实际上是 syscall,它允许用户查看和动态修改内核参数. # 查看当前设置的 ...
- 张高兴的 Raspberry Pi AI 开发指南:(二)使用 Python 进行目标检测
目录 Python 环境配置 实现 USB 摄像头的目标检测 参考 在上一篇博客中,探讨了使用 rpicam-apps 通过 JSON 文件配置并运行目标检测示例程序.虽然这种方法可以实现有效的检测, ...
- C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- Flutter ListView顶部空白去除
Flutter ListView顶部空白去除 当listview没有和AppBar一起使用时,顶部会有一个自动的空白部分,可以用MediaQuery.removePadding去掉 return Me ...
- NATS: 对象存储
https://natsbyexample.com/examples/os/intro/dotnet2 NATS 中的对象存储能力是在 Stream 之上的一种抽象,它将消息的主题视为类似键值对中的键 ...
- 中电金信:向“新”而行—探索AI在保险领域的创新应用
大模型的应用已经渗透到各个领域,并展现出惊人的潜力.在自然语言处理方面,大模型用于机器翻译.文本摘要.问答系统等:在计算机视觉领域,应用于图像识别.目标检测.视频分析等:此外,大模型也应用于语音识别. ...
- 【Javaweb】基础开发流程与介绍
本文档写于2022年7月29日,由于个人水平有限,可能存在一些问题,因此仅供参考 @萌狼蓝天 JavaWeb基础开发流程 1.确定系统和功能 在此以"宠物管理系统"为例,要开发一个 ...