Step by Step之后端对称加解密的实现
lipiwang 2024-11-04 14:33 12 浏览 0 评论
接着上一篇视频播放实现的方案,虽然实现了播放端的token认证,但并未进行加扰加密,安全性较差,所以本篇增加了前后端加扰环节,来提升整体解决方案的安全性。
一、视频播放认证安全
1、Web应用安全体系
Web应用安全体系的基础是HTTPS,它的核心就是在密钥交换时采用非对称加密方式,在数据传送时采用对称加密方式。
现在Web应用普遍采用HTTPS方式进行线上部署,通过运维工程手段即可实现,研发人员都不需要了解其中的技术细节,他们需要关注更多的是跨域认证、Session保持等内容。
视频播放属于Web应用中的一个环节,总体上沿用认证登录和Session保持机制,只需将认证登录后产生的token进行携带,并在OpenResty端进行安全校验即可。
2、Web应用常用加解密方法
(1)主流的对称加密算法是AES(高级加密标准),分组长度只能是128位,即每个分组16个字节,密钥的长度可以使用128位、192位或256位,密钥长度不同,推荐的加密轮数也不同,分别对应10轮、12轮和14轮。
AES使用时的参数配置主要包括加密模式和填充方式。
(2)AES的主要加密模式
最简单的是ECB电话本模式,一般也是默认模式,它将整个明文分成若干段相同的小段,然后对每一小段进行加密,具有简单、可并行、不传送误差等优点,但掩盖不了明文结构信息,难以抵抗统计分析攻击。
复杂模式最常用的CBC密码分组链模式,先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。优点是能掩盖明文结构信息,保证相同密文可得不同明文,所以不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL和IPSec的标准。缺点:不利于并行计算;会传递误差——前一个出错则后续全错;第一个明文块需要与一个初始化向量IV进行抑或,初始化向量IV的选取比较复杂。
其他模式还有OFB输出反馈模式、计数器模式CRT、密码反馈模式CFB等。
(3)AES的填充模式
常用有NoPadding、ZerosPadding、PKCS#5 padding、PKCS#7 padding等。
NoPadding是一种不使用填充算法的加密模式,即输入数据将被直接编码为字节序列,而不会受到填充的影响。
ZerosPadding是一种简单的填充算法,将输入数据的最后一个字节替换为0。这种填充方式适用于所有AES加密模式,包括ECB、CBC和CTR模式。
PKCS#5 padding是一种常见的填充算法,最初设计用于加密块大小为8字节(64位)的算法,这种填充方式使用一个固定长度的填充字节序列,以确保输入数据被完全填充。
PKCS#7 padding是是PKCS#5的通用版,适用于更大的块大小,是PKCS#5 padding的超集,可以互相替代使用,尤其是在处理AES等块大小为16字节的算法时。
3、加解扰Base64
Base64是二进制到字符编码的一种方案,将二进制数据使用ASCII字符串格式表示,即翻译为基数为64的一种表示。每个Base64数字表示一个6比特的数据。三个字节(共24个比特)因此可以被表示为4个Base64数字。
当未编码的输入的字节数不是3的倍数时,编码输出必须加上缀词来使得输出的长度是4的倍数。这个缀词便是=。
Base64在加解密中经常使用,主要场景包括:(1)加密通信的辅助,在某些加密协议中,Base64用于编码加密后的密文,使其能作为文本安全地在网络中传输,而不必担心特殊字符引起的问题;(2)可以将敏感信息(如密码或密钥)编码为不可直接阅读的形式,增加一定的安全性,这在URL参数中传输敏感数据时尤其有用,一般还需配合HTTPS等加密协议。
4、技术选型
在Java后端,选择Hutool库的AES模块,因为Hutool本身集成了其他许多常用组件,项目中本就已经包含,加密时,采用其默认加密方式:ECB+PKCS5,或者说与之等效的PKCS7填充方式。
在视频认证的场景下,后端根据一定的规则生成Token,认证通过后反馈给客户端(前端或小程序端),客户端对Token进行保存,并在每次发送信息时放在请求头部(Header)或请求主体(如Path变量或Body中),因此客户端不需要进行加解密或加解扰操作。
在我们的视频简化解决方案中,OpenResty提供了视频流播放服务,所以需要在OpenResty端对视频播放请求携带的Token进行解扰解密,并与Redis进行交互以验证Token的有效性。在lua中,选择使用其默认包含的resty.aes库进行加解密。
二、核心实现代码
1、Java后端实现
因为Token的加解密都在服务器端,不需要与客户端进行密钥交换,所以在Java端和OpenResty端保持一致即可。
import cn.hutool.crypto.symmetric.AES;
import java.nio.charset.StandardCharsets;
public class TokenBase {
public String tokenKey = "0123456789012345"; // 16位密钥
public Integer expiredTime = 3600; // 有效时间为一小时
// 加密函数
private String encryptToken(String token) {
AES aes = new AES(tokenKey.getBytes(StandardCharsets.UTF_8));
// 加密Token中包括了生成时的时间,以便进行有效期控制
return aes.encryptBase64(token + System.currentTimeMillis());
}
// 解密函数
private String decryptToken(String data) {
AES aes = new AES(tokenKey.getBytes(StandardCharsets.UTF_8));
String decryptData;
String decryptToken;
try {
decryptData = aes.decryptStr(data);
decryptToken = decryptStr.substring(0,32);
// 创建token的时间
long tokenCreateTime = Long.parseLong(decryptData.substring(32,45));
// 判断token是否过期
if (System.currentTimeMillis() - tokenCreateTime > expiredTime * 1000L) {
// 进行token过期的异常处理
}
}
catch (Exception e) {
// 进行其他异常处理
}
return decryptToken;
}
}
2、OpenResty端实现
核心代码如下:
// nginx的配置文件
server {
listen 443 ssl;
server_name abc.com www.abc.com;
// 其他配置信息
location /media {
auth_request /media_auth;
root /usr/local/openresty/nginx/html;
error_page 401 = @error401;
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 10m;
}
location /media_auth {
content_by_lua_block {
-- 从URL的Path变量中获取token
local _, _, token = string.find(ngx.var.request_uri, "%?token=(.*)&?")
if token == nil or token == "" then
ngx.log(ngx.ERR, "token not found!")
return ngx.exit(401)
end
-- 解扰和解密token
local resty_aes = require "resty.aes"
local resty_string = require "resty.string"
local key = "0123456789012345" -- 与java端保持一致即可
local aes_obj = resty_aes:new(key, nil, resty_aes.cipher(128, "ecb"),{iv="0000000000000000"})
local des_token = ngx.decode_base64(token)
local decrypted, err = aes_obj:decrypt(des_token)
if not decrypted then
ngx.log(ngx.ERR, "Failed to decrypt the token: ", err)
ngx.exit(401)
end
local decryptToken = string.sub(decrypted, 1, 32)
-- 检查redis是否存在对应的token键值
local redis = require "redis.redis_resty"
local opts = { ip = "redis-ip", port = "6379", password = "redis-pass", db_index = 0 }
local conn = redis:new(opts)
local ok, err = conn:get("redis-key-prefix" .. decryptToken)
if not ok then
ngx.log(ngx.ERR, "token not found in redis! error message is :", err)
return ngx.exit(401)
end
return ngx.exit(200)
}
}
Lua是踩坑较多的地方,这可能也体现了一种不够流行语言的通病,即Bug或问题较多,因为用的人不够,没有踩遍各种坑,这些坑也就没人填。
(1)base64解扰,一开始百度和找大模型,先告诉直接使用自带的string库,结果直接提示找不到该函数,因为项目使用了最新的openresty镜像,找了源码确实没有这个函数,于是先网上找了一个源码,后来在用python进行验证时发觉不对,这么简单的代码怎么可能没有,于是在镜像的lualib目录下进行grep,结果发现在/resty/core目录下有base64.lua文件,定义了ngx.decode_base64和ngx.encode_base64函数,于是就直接拿来用了。
(2)团队调试花时间最多的在aes库的new函数上,百度上最多和大模型最初的反馈都是很简单的调用,new(key, "ecb"),结果直接报错,提示没有size字段,继续百度,发现第二个参数为resty_aes.cipher(128, "ecb"),继续报错,于是翻看源码aes.lua,发现需要第二个参数,于是增加了一个nil,总算new函数不再报错。但马上就是解密报错,提示EVP_DecryptFinal_ex failed,因为解密函数调用参数非常简单,还是怀疑为new的参数问题,百度发现有人提到即使用ecb模式,也需要填写cbc才需要的初始化向量iv,增加了参数后终于ok了。
而按照理论上ecb是不需要iv的,尝试过将iv设置为其他的16位字符串,解密同样没有问题,所以估计是代码的bug吧,因为最终代码是c的库,所以就没再深究下去了。
3、Python实现
在查找问题时为了对照,用Python也写了同样的代码,发现它无愧于排名第一的编程语言,组件库很多,三两下就弄出来了。
// 使用pip安装库pycryptodome,base64自身就有
from Crypto.Cipher import AES
import base64
key='0123456789012345'
token = '需解扰解密的token串'
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
str = base64.b64decode(token)
out = cipher.decrypt(str)
相关推荐
- 前端入门——css 网格轨道详细介绍
-
上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...
- Islands Architecture(孤岛架构)在携程新版首页的实践
-
一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...
- HTML中script标签中的那些属性
-
HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...
- CSS 中各种居中你真的玩明白了么
-
页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...
- CSS样式更改——列表、表格和轮廓
-
上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...
- 一文吃透 CSS Flex 布局
-
原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...
- css实现多行文本的展开收起
-
背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...
- css 垂直居中的几种实现方式
-
前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...
- WordPress固定链接设置
-
WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...
- 面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿
-
前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...
- 3种CSS清除浮动的方法
-
今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...
- 2025 年 CSS 终于要支持强大的自定义函数了?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...
- css3属性(transform)的一个css3动画小应用
-
闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...
- CSS基础知识(七)CSS背景
-
一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...
- CSS 水平居中方式二
-
<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)