百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Step by Step之后端对称加解密的实现

toyiye 2024-07-08 00:49 18 浏览 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)

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码