<<<<<<< HEAD 文档开发中,敬请期待...... =======
005:Air8101-LuatOS-软件指南-常用功能实现-通用加解密(crypto)
一、主要的加密方法概要
1.1 MD5 及 HMAC_MD5
MD5 信息摘要算法(英语:MD5 Message-Digest Algorithm),是一种被广泛使用的密码散列函数,可以产生出一个 128 位(16 字节)的散列哈希值,用于确保信息传输完整一致。MD5 由美国密码学家罗纳德•李维斯特(Ronald Linn Rivest)设计,于 1992 年公开,用以取代 MD4 算法。1996 年后该算法被证实存在弱点,对于需要高度安全性的数据,建议改用其他算法,如 SHA-2。
HMAC-MD5(基于哈希的消息认证码)是一种结合了 MD5 散列函数和密钥的消息认证码算法,用于验证消息的完整性和身份。它通过结合密钥和消息内容生成一个长度为 128 位的散列(哈希)值。
MD5 一般用于以下几个方面
Ⅰ. 用于密码管理;
Ⅱ. 电子签名;
Ⅲ. 垃圾邮件筛选;
Ⅳ.文件完整性校验。
1.2 AES 加密算法
密码学中的高级加密标准(Advanced Encryption Standard,AES),又称 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的 DES(Data Encryption Standard),已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院 (NIST)于 2001 年 11 月 26 日发布于 FIPS PUB 197,并在 2002 年 5 月 26 日成为有效的标准。2006 年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
对称/分组密码一般分为流加密(如 OFB、CFB 等)和块加密(如 ECB、CBC 等)。对于流加密,需要将分组密码转化为流模式工作。对于块加密(或称分组加密),如果要加密超过块大小的数据,就需要涉及填充和链加密模式。
Ⅰ. ECB(Electronic Code Book 电子密码本)模式;
ECB 模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。优点是简单,有利于并行计算,而且误差不会被传送;缺点是不能隐藏明文的模式,可能对明文进行主动攻击。正因如此,此模式适于加密小消息。
Ⅱ. CBC( Cipher Block Chaining 加密块链 )模式;
此种加密模式的优点是不容易主动攻击,安全性好于 ECB,适合传输长度长的报文,是 SSL、IPSe c 的标准;缺点是不利于并行计算且需要初始化向量 IV。
Ⅲ. CFB( Cipher FeedBack Mode 加密反馈 )模式;
此模式的主要优点是隐藏了明文模式,分组密码转化为流模式,可以及时加密传送小于分组的数据;缺点是不利于并行计算,一个明文单元损坏影响多个单元且需要唯一的初始化向量 IV。
Ⅳ. OFB( Output FeedBack 输出反馈 )模式。
OFB 模式的主要优点是隐藏了明文模式,分组密码转化为流模式,可以及时加密传送小于分组的数据;缺点是不利于并行计算,对明文的主动攻击是可能的,一个明文单元损坏影响多个单元。
1.3 DES 加密算法
DES 全称为 Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977 年被美国阿斗太子政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
DES 加密算法运用非常普遍,是一种标准的加密算法,3DES 是 DES 的加强版本。DES 设计中使用了分组密码设计的两个原则:混淆( confusion )和扩散( diffusion ),其目的是抗击敌手对密码系统的统计分析。
二、演示内容简介
2.1 演示概要
本演示对目前常用的加密解密方法进行了具体的讲解,为在具体的应用中如网络、传输、通讯等具体项目,扫清了路障。尽管加密与解密不是项目的全部,但是各应用不可或缺的一部分,对这部分知识的了解、熟知,是项目顺利完成的基础,其重要性不言而喻。同时,本演示对于串行口应用,字符串解析也提供了具体的实例,大家可以参考。
2.2 Air8101 模块简介
Air8101 以 WiFi6 低功耗音视频 SoC 芯片为核心,针对物联网量身定制,具有功耗低、集成度高、安全性能高、接口与资源丰富等优势,同时集成音频、视频解码接口,是中控显示、智能门锁、远程监控等应用的不二选择。
三、 硬件环境
演示基于 Ari8101 开发板,除此外,需要串行口通讯模块一个、RS232 串行工具一套,可以使用 USB 转 TTL,也可以使用 RS232 转 TTL,具体依各位的 PC 机硬件环境与使用习惯先择即可,本文演示时使用的是 USB 转 TTL,杜邦线若干。
具体的硬件环境的搭建,各位还可以参考:硬件环境清单 - luatos@air8101 - 合宙文档中心。
四、软件环境
Ⅰ. Luatools 最新版,大家可以从软件环境清单 - luatos@air780e - 合宙文档中心处下载。
Ⅱ. 内核固件,可以从 Index of /air8101/ 处下载,一般建议使用最新内核版本。
Ⅲ. 脚本文件与资源文件,包括 main.lua 脚本与 luatos_uploadFile.txt 两个文件。有关具体内容请参考《平台搭建》章节的项目建立部分。
五、平台搭建
5.1 硬件平台的搭建
本演示使用 Air8101 开发板作为硬件平台,如图所示(板子出来后再替换):
通过开发板的通讯模块,接到 DL_UART0 的端口,如上图,将跳线子按图中所示配置好,接上 USB 口与 PC 机的 USB 口连接线,此时 PC 设备管理器将出新出现一个串行口设备,记住其端口号备用。
本演示使用了串行口 1,即开发板上标示 UART1 的位置,通过连线与电脑进行连接,如下图所示。
图中左面的小工具是 wch_link,作为串行口转 TTL 很好用,上图中所示接好线,将 wch_link 插入电脑。当然大家也可以使用自己习惯的 USB 工具,因而此处也就不再提供 wch_link 的驱动程序。开发板串行口的驱动程序请从 合宙云盘目录 下载即可。
需要注意的是,UART0 与 UART1 两个串行口都要连接到 PC 机上,其中 UART0 是脚本与固件下载的接口,而 UART1 是本次演示用来进行功能演示的方式与方法,切记勿忘!
如果开发板是第一次使用,还需要下载核心固件。在连接正常后,起动 Luatools 工具软件,如下图所示:
点击"下载固件"按钮,选择最新固件 “LuatOS-SoC_V10001_Air8101_20241219_110636.soc”,如果手头没有最新固件,本文将在文后附加,请下载保存并选用。
在上图中,点击“下载”按钮,将固件下载到开发板。下载成功即表示硬件平台搭建完成
5.2 项目建立
在 Lautools 软件界面中,点击“项目管理测试”按钮,进入项目管理界面,如图:
在上图中,点击“创建”按钮并输入项目名称创建一个新项目,本测试使用名称 “cryptr”,名称可以任取,各位依兴趣或实际要求定义即可。
项目创建后,在界面的右边,点击“增加脚本或资源文件”,并选择 main.lua 文件,将测试文件加入到项目中。main.lua 文件列出于第一章 DEMO 开发中,各位可以复制粘贴创建一个 main.lua 文件,同时各位也可以从本节后面下载 main.lua 直接使用即可。同时,本演示还会用到文件,各位可以自己新建一个文件 luatos_uploadFile.txt 并输入一些内容后保存即可。文件建立 后,存入到 main.lua 所在目录的子目录 \luadb 下,然后将该文件也加入到项目,如上图。
5.3 演示的基本方式与方法
为了使体验更好,并且可以多次测试与体验,使用了串行口进行交互,通过串行口,发送字符串来定制具体要演示的加密方法,而不是固定一个或几个数据,且每次都得到雷同的信息。相信这样的设定会使体验感更强,对加密解密的理解也会更深。
为了完成这个功能,因而我们的项目中必须有串行口的使用,对串行口进行配置,如下代码块所示:
-- 定义串行口及其波特率
local uart_id = 1
local uart_baud = 115200
---- 其它内容
--配置并且打开串口
uart.setup(uart_id,uart_baud,8,1)
--注册串口的数据发送通知函数
uart.on(uart_id,"receive",function()
log.info("uart "," uart received ")
sys.publish("UART_RECEIVE")
end)
--将数据写入串行口
function write_pack(s)
--log.info("write_pack", s )
uart.write(uart_id,s.."\r\n")
end
上面代码块首先配置串行口 1 的通讯波特率为 115200,8 位数据,1 位停止位。然后设置了接收事件的处理方式,即发送一个系统消息“UART_RECEIVE”,然后我们只需要在协程内订阅此系统消息即可,具体的方法就是使用 sys.waitUntil("UART_RECEIVE" , 500) 这个函数,请参阅本演示所提交的代码进行具体了解也可以查阅有关资料手册进行具体学习。
为了将数据发送给 PC,同时也编写了一个函数 write_pack( s ),用于将数据发送给 PC,如上代码块所示。
这些工作完成后,我们还要编写一个工作协程,用于监听串行口 1 的请求数据,然后解析请求数据,并作出回应,这个协程的基本结构给出于下面的代码块,需要说明的是,此段代码仅示范一个代码的结构,仅演示了串行口通讯的具体方式与方法,具体的加密演示会随着演示的进行而一步步加入。当然如果大家不想这样一步一步跟着走,直接从本文最后下载最终的演示文件,就包含了全部的演示功能,直接测试体验。
sys.taskInit(function()
-- 数据接收缓冲区
-- 使用缓冲区的目的是为了接收全部数据后再分析
local cacheData = ""
sys.wait(1000)
while true do
::continue::
local s = uart.read(uart_id,1024)
if s == "" then
if cacheData == "" then
sys.waitUntil("UART_RECEIVE" , 500)
goto continue
end
-- 定义变量
local cry_str
local items_data
local cry_start,cry_end
-- 测试一下串行口的工作情况
cry_start,cry_end = cacheData:find( "uart" )
if cry_start ~= nil then
write_pack( "test uart[ baud=115200,8,0,1 ]\r\n" )
end
-- 读回所有支持的 cipher,因相关的内容较多,就不通过串行口发送了。
cry_start,cry_end = cacheData:find( "list" )
if cry_start ~= nil then
-- 打印所有支持的cipher
if crypto.cipher_list then
log.info("cipher", "list", json.encode(crypto.cipher_list()))
else
log.info("cipher", "当前固件不支持crypto.cipher_list")
end
end
-- 读回所有的 cipher suites 列表,因相关内容较多,就不通串行口发送了
cry_start,cry_end = cacheData:find( "suites" )
if cry_start ~= nil then
-- 打印所有支持的cipher
if crypto.cipher_list then
log.info("cipher", "suites", json.encode(crypto.cipher_suites()) )
else
log.info( "cipher", "当前固件不支持crypto.cipher_suites" )
end
end
--因为是讲结构嘛,因而也不放置太多的功能而导致看不清楚。
--此处可添加调用。
--数据处理完成,需要清空,以利于下次接收数据。
cacheData = ""
else
-- 如果数据一次没有读取完成,则可读取多次。
cacheData = cacheData..s
end
end
sys.wait(100000)
end)
在上面的代码中,我们放置了几个测试代码,分别对串行口进行测试,同时,也读取了 cipher 与 cipher suites 列表。下面我们将脚本下载到开发板,来体验一下演示流程。
启动串行口调试工具,具体工具不作要求,大家熟悉哪个就用哪个即可。选择串行口调试工具把串行口号(具体于我而言就是对应 wch_link 的端口号),将波特率设置为 115200,数据位设置为 8 位,1 个停止位,无校验,如下图所示:
因本演示全部使用字符进行,因而将接收与发送的字符编码都选择 ascii,如上图所示。
现在准备就绪,我们将脚本及相关文件下载到开发板,然后打开串行口调试工具,在输入栏内输入“uart” 后点击发送,演示结果如上图的接收区所示,接收到 test uart [baud=115200,8,0,1] 即表明串行口工作正常:
在上文所提到的框架代码中,我们还配置了两个 cipher 列表,可以一并试试,在串行口调试工具的输入栏内输入 list 或 suites,点发送即可。这个内容就有点多了,没有在串行口反馈,因而我们帖出 Luatools 工具的 trace 信息,如下图所示:
本节的讲解仅仅只是对演示环境的一个测试,具体的演示功能在下面再详细讲解。
六、加密功能演示
6.1 MD5、SHA1 等加密功能的演示
此类功能包括 md5、sha1、sha256、sha512 等,md5、sha1 等加密方法在电子签名,完整性验证等领域应用广泛,使用此类加密方法可对文本或文件等的完整性或唯一性进行识别,比如压缩软件对文件压缩与解压进行完整性检查,再比如文件下载,对下载文件的完整性进行检查等。下面来演示此类功能。
注:本文在行文中,对于 md5 等名称,标题使用大写,正文使用小写,一方面因为 crypto 相关的接口都是小写方式,另一方面,我在代码中也一般使用小写、或者是为了分辨字符串含义而使用大小写结合的方式;最后一个原因,是避免在串行口调试工具内输入命令串时,切换大小写不是太方便,也容易导致大小写混杂从而识别失败。
首先,我们编写一个函数,用于完成此类功能演示:
-- 参数说明
-- cry_name 具体的加密方法,如md5。
-- func 具体的执行函数如 crypto.md5。
-- buff 串行口接收到的数据
function crypto_todo( cry_name, func ,buff )
local cry_start,cry_end = buff:find( cry_name )
if cry_start ~= nil then
-- 取得尾部字符
items_data = buff:sub( cry_end+1 , -1 )
log.info( cry_name, func( items_data ) )
write_pack( cry_name.." <==> code [ "..func( items_data ).." ]" )
end
end
然后,我们在监听协程中加入以下调用:
crypto_todo("md5" , crypto.md5 , cacheData )
-- 输出 sha1 编码
crypto_todo("sha1" , crypto.sha1 , cacheData )
-- 输出sha256
crypto_todo("sha256" , crypto.sha256 , cacheData )
-- 输出sha512
crypto_todo("sha512" , crypto.sha512 , cacheData )
保存文件,然后使用 Lautools 下载到开发板。然后打开串行口调试器,输入相关字符串,具体格式为:方法名称 + 数据,中间不留空间或其它分隔符,其中方法名称为 “md5、sha1、sha256、sha512”等,数据任意,注意不要包含控制字符或其它非法字符。举例如下:
md5abcdefghi
sha1afafasfaf
sha256afasffff
sha512adfasfll
演示结果如下图所示:
大家也可以改变数据内容进行测试,字符串按格式来编写即可。
6.2 HMAC_MD5、HMAC_SHA1 等加密功能的演示
hmac 算法本质属于 mac 算法的一种。其保留了对称密钥、生成 mac 消息认证码的特点。但是进一步结合了哈希函数和密钥加密技术。根据用于计算 mac 的哈希函数,可以定义许多示例,目前主要集合了 md 和 sha 两大系列消息摘要算法,分别为 hmac_md5、hmac_sha1 等。
与上一节类似,编写一个函数比较方便:
-- 参数说明
-- cry_name 具体的加密方法,如md5。
-- func 具体的执行函数如 crypto.md5。
-- buff 串行口接收到的数据
function crypto_todo_hmac( cry_name, func ,buff )
local password
local cry_start,cry_end = buff:find( "password" )
if cry_start == nil then
password = "1234567890"
else
password = buff:sub( cry_end+1 , -1 )
buff = buff:sub( 1, cry_start-1)
--log.info("password ",password,buff)
end
cry_start,cry_end = buff:find( cry_name )
if cry_start ~= nil then
-- 取得尾部字符
items_data = buff:sub( cry_end+1 , -1 )
-- 为了简便起见,就不通过串行口发送密钥了,毕竟这不是一个串行口数据解析的演示
log.info( cry_name, func( items_data , password ) )
write_pack( cry_name.." <==> password [ "..password.." ] code [ "..func( items_data , password ).." ]\r\n" )
end
end
由上面的代码可知,本类操作包含有密码,可以在串行口工具中输入密码,也可以不输入,当没有输入密码时,使用默认的密码对数据进行加密。从上面的代码也可知具体的格式为:加密方法 + 数据 +password+ 密码。
举例如下:
hmac_md5abcdefghipasswordabcdedfhij
hmac_sha1afafasfaf
hmac_sha256afasffffpasswordabcdedfhij
hmac_sha512adfasfll
在监听协程中,加入此函数的相关调用,如下代码块所示:
-- 输出 MD5 编码
-- 当收到 hmac_md5 时,也会执行一次 md5,因只是作一个加密演示,作为演示
-- 未做过于复杂的逻辑判断,因而就让其输出吧,关系也不大。只是现在数据包括
-- password+密码而已。下面遇到类似情况就不再作说明了,请知悉!其它类似。
-- 当然,也可以使用注释符将 md5 等的调用注释掉。本演示不作处理。
-- 输出hmac_md5
crypto_todo_hmac("hmac_md5" , crypto.hmac_md5 , cacheData )
-- 输出 hmac_sha1 编码
crypto_todo_hmac("hmac_sha1" , crypto.hmac_sha1 , cacheData )
-- 输出hmac_sha256
crypto_todo_hmac("hmac_sha256" , crypto.hmac_sha256 , cacheData )
-- 输出hmac_sha512
crypto_todo_hmac("hmac_sha512" , crypto.hmac_sha512 , cacheData )
演示的结果如下图所示:
在上图中,可以看到,如果没有输入密码,则使用了默认的密码代替,然后对给定的数据进行了加密处理。同时,由于未注释掉 md5 等相关的调用,因而同时也执行了 md5 等相关的加密处理,只是此时的数据就要加上 password 及对应的密码,如果没有输入密码除外。
6.3 流数据的加密处理
在实际应用中,数据有时并不会一次传完,而是分时分包累积完成数据的传输工作,这种累积的加密处理,具体使用的方法也有几种,与前面类似,我们编写如下的函数:
-- 流数据校验演示
function toto_hash_init( cry_name , method , buff )
local password
local cry_start,cry_end = buff:find("password")
if cry_start ~= nil then
-- 读取密码
password = buff:sub(cry_end+1,-1)
buff = buff:sub( 1 , cry_start-1 );
else
password = ""
end
-- 本过程不再对拼接后的完整数据进行校验,如果有需要,请使用单独的MD5或SHA1加密方法进行验证即可。
-- 比如 MD5 的流校验操作,假如使用的文本是 "123456456",则验证可通过串行口发送
-- md5123456456123456456123456456123456456 来验证。这是因为演示就是连续操作四次发送的文本,来
-- 模拟流操作的过程,以演示累积的加密处理方法。
cry_start,cry_end = buff:find( cry_name..method )
if cry_start ~= nil then
local cry_text = buff:sub(cry_end+1,-1)
log.info("md5-----", cry_text, password , method)
if password == "" then
-- 转化为大写
local md5_obj = crypto.hash_init( method:upper() )
crypto.hash_update(md5_obj, cry_text)
crypto.hash_update(md5_obj, cry_text)
crypto.hash_update(md5_obj, cry_text)
crypto.hash_update(md5_obj, cry_text)
local md5_result = crypto.hash_finish(md5_obj)
write_pack( "stream encrypt <==>name [ "..cry_name.." ] method [ "..method.." ] text [ "..cry_text.." ] result [ "..md5_result.." ]\r\n" )
log.info("md5_stream", md5_result)
else
local md5_obj = crypto.hash_init( method:upper() , password )
crypto.hash_update(md5_obj, cry_text , password )
crypto.hash_update(md5_obj, cry_text , password )
crypto.hash_update(md5_obj, cry_text , password )
crypto.hash_update(md5_obj, cry_text , password )
local md5_result = crypto.hash_finish(md5_obj)
write_pack( "stream encrypt <==>password ["..password.."] name [ "..cry_name.." ] method [ hmac_"..method.." ] text [ "..cry_text.." ] result [ "..md5_result.." ]\r\n" )
log.info("md5_stream", md5_result)
end
end
end
将相关的调用加入到协程中,如下所示:
toto_hash_init("hash_init" , "md5" , cacheData )
toto_hash_init("hash_init" , "sha1" , cacheData )
toto_hash_init("hash_init" , "sha256" , cacheData )
toto_hash_init("hash_init" , "sha512" , cacheData )
由上面的代码,可分析得到串行口演示数据的格式为:hash_init+ 加密方法 + 数据 +password+ 密码。
加密方法有“md5、sha1、sha256、sha512”等几种,当带有密码时,是 hmac 算法,方法名不变,即不加“hmac”前缀。
举例如下:
hash_initmd51234567890passwordabcd123456
hash_initsha11234567890
hash_initsha2561234567890passwordabcd123456
hash_initsha5121234567890
演示结果如下图所示:
6.4 文件散列值测试
散列(hash)函数的主要功能,就是每一个计算结果会比较均匀的分布在值域中而尽量不重复(或称碰撞),每一个不同的输入,都有一个特别的值,既然是差异微小的输入,得到的值也可能很大。如果输入是文件,则可以认为其散列值是文件的特征值,相当于人的指纹,可以用于识别不同的文件。因而散列通常用来校验文件的完整性,判定文件在传输过程中,或在加工过程中,是否损坏。
这次编写函数时,我们对不同编码的散列计算一次性输出,具体函数如下面代码块所示:
-- 文件测试
function todo_md_file(buff)
local password
local cry_start,cry_end = buff:find( "password" )
if cry_start == nil then
password = ""
else
password = buff:sub( cry_end+1 , -1 )
buff = buff:sub( 1, cry_start-1)
log.info("md_file password ",password,buff)
end
log.info("文件hash值测试")
if crypto.md_file then
local md_file =crypto.md_file( "MD5" , "/luadb/luatos_uploadFile.txt" )
write_pack( "md_file encrypt MD5 <==> code [ "..md_file.." ]\r\n" )
md_file =crypto.md_file( "SHA1" , "/luadb/luatos_uploadFile.txt" )
write_pack( "md_file encrypt SHA1 <==> code [ "..md_file.." ]\r\n" )
md_file =crypto.md_file( "SHA256" , "/luadb/luatos_uploadFile.txt" )
write_pack( "md_file encrypt SHA256 <==> code [ "..md_file.." ]\r\n" )
md_file =crypto.md_file("MD5" , "/luadb/luatos_uploadFile.txt" , password )
write_pack( "md_file encrypt hmac_md5 <==> password [ "..password.." ] code [ "..md_file.." ]\r\n" )
md_file =crypto.md_file("SHA1" , "/luadb/luatos_uploadFile.txt" , password )
write_pack( "md_file encrypt hmac_sha1 <==> password [ "..password.." ] code [ "..md_file.." ]\r\n" )
md_file =crypto.md_file("SHA256" , "/luadb/luatos_uploadFile.txt" , password )
write_pack( "md_file encrypt hmac_sha256 <==> password [ "..password.." ] code [ "..md_file.." ]\r\n" )
else
log.info("文件hash值测试", "当前固件不支持crypto.md_file")
end
end
与流数据处理类似,当使用 hmac 算法时,有密码,只是我们不再作具体的区分,而是一次性都进行一次计算,需要密码的就使用接收到密码传入,不需要密码的,忽略接收的密码即可。此功能使用 md_file 作为识别关键字,又由于这是对文件进行处理,因而不再需要数据。至于文件就是前面加入到项目的 lautos_uploadFile.txt。因而此功能演示的格式是:md_file+password+ 密码。
举例如下:
md_filepassword1234567890
演示结果:
当然,如果想更换文件,则可以在项目管理操作时,将相关文件更换即可,如果文件名有变更,则请同时修订 main.lua 内的相关代码,将文件名修订为实际的文件名即可。
6.5 对称加密算法
对称加密算法主要有 DES、AES、RC 等几种,通过 cihper list 列表的读取,可知本模块对 DES、RES 的支持比较全面,因而本演示就针对这两个加密方法进行。
相关的函数有三个,一个执行有 IV 向量的加密功能,函数名 todo_cihper_ecb,一个执行无 IV 向量的加密功能,函数名 todo_cihper_not_ecb,这两个函数都由函数 crypto_todo_cipher 调用。具体的函数实现如下代码块所示:
function todo_cihper_ecb(name_full ,padding , items_data , password)
log.info("-------------------------" , name_full , padding , items_data , password )
data_encrypt = crypto.cipher_encrypt( name_full , padding , items_data , password )
log.info("-------------------------")
if data_encrypt ~= nil then
write_pack( name_full.." encrypt <==> ["..padding.."] password [ "..password.." ] code [ "..data_encrypt.." ]\r\n" )
write_pack( name_full.." encrypt <==> ["..padding.."] password [ "..password.." ] code_HEX [ "..data_encrypt:toHex().." ]\r\n" )
data_encrypt = crypto.cipher_decrypt(name_full, padding, data_encrypt, password )
write_pack( name_full.." decrypt <==> ["..padding.."] password [ "..password.." ] code [ "..data_encrypt.." ]\r\n" )
else
write_pack( name_full.." encrypt <==> ["..padding.."] password [ "..password.." ] code [ data_encrypt error ]\r\n" )
end
end
function todo_cihper_not_ecb(name_full , padding , items_data , password , iv)
log.info("-------------------------" , name_full ,padding , items_data , password , iv )
data_encrypt = crypto.cipher_encrypt( name_full , padding , items_data , password , iv )
log.info("-------------------------")
if data_encrypt ~= nil then
-- 输出原码与输出HEX各一组,方便比较
write_pack( name_full.." encrypt <==> ["..padding.."] password [ "..password.." ] IV [ "..iv.." ] code [ "..data_encrypt.." ]\r\n" )
write_pack( name_full.." encrypt <==> ["..padding.."] password [ "..password.." ] IV [ "..iv.." ] code_HEX [ "..data_encrypt:toHex().." ]\r\n" )
data_encrypt = crypto.cipher_decrypt(name_full, padding, data_encrypt, password,iv)
write_pack( name_full.." decrypt <==> ["..padding.."] password [ "..password.." ] code [ "..data_encrypt.." ]\r\n" )
else
write_pack( name_full.." encrypt <==> ["..padding.."] password [ "..password.." ] IV [ "..iv.." ] code [ data_encrypt error ]\r\n" )
end
end
-- AES 加密解密
-- cryname 加密名称+对齐方式+加密字符串
function crypto_todo_cipher( cry_name , padding , buff )
local password
local cry_start,cry_end = buff:find( "password" )
if cry_start == nil then
password = ""
else
password = buff:sub( cry_end+1 , -1 )
buff = buff:sub( 1,cry_start-1)
--log.info("1 password ",password,buff)
end
-- 匹配查找
cry_start,cry_end = buff:find( cry_name )
-- 找到加密名称
if cry_start ~= nil then
-- 取得加密方法名称
local name_full = buff:sub(cry_start,cry_end)
log.info( "<----->",name_full )
-- 取得尾部字符
cry_start,cry_end = buff:find( padding )
-- 找到对齐方式
if cry_start ~= nil then
local data_encrypt
local iv
local password_len
name_full = name_full:upper()
padding = padding:upper()
-- 取得加密数据
items_data = buff:sub( cry_end+1 , -1 )
--[[
参数
name_full 算法名称, 例如 AES-128-ECB/AES-128-CBC, 可查阅crypto.cipher_list()
padding 对齐方式, 支持PKCS7/ZERO/ONE_AND_ZEROS/ZEROS_AND_LEN/NONE
items_data 需要加密的数据
HZBIT@WLW/YSBKEY 密钥,需要对应算法的密钥长度
IV 非ECB算法需要]]
cry_end = buff:find("des")
-- 进行DES 加密 处理
if cry_end ~= nil then
if password == "" then
cry_start = buff:find( "ede3" )
if cry_start ~= nil then
password = "123456781234567812345678"
else
password = "12345678"
end
end
cry_start,cry_end = buff:find( "ecb" )
if cry_start ~= nil then
-- 应当也是有密码长度规范的,但此处不再进行判定处理。
-- 如果导致出错,请注意是不是密码长度引起。
todo_cihper_ecb( name_full , padding , items_data , password )
else
todo_cihper_not_ecb( name_full , padding , items_data , password , "12345678")
end
else
-- 进行字符串匹配 取得开始位置及加密长度 可以是 128、192、256等值
-- 一般来说,密钥的长度就是加密长度,比如128,就表示16个字节长度
cry_start,cry_end,password_len = buff:find( "aes[-](%d+)[-]%a%a%a" )
if password_len == nil then
password_len = "0"
end
--如果密码为空,则截取合适的长度字串作为密码
if password == "" then
password = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
password = password:sub(1, tonumber(password_len)/8 )
end
if tonumber(password_len) / 8 == password:len() then
log.info( "-----","password ok", password , name_full , padding)
cry_start = buff:find( "ecd" )
if cry_start ~= nil then
todo_cihper_ecb( name_full , padding , items_data , password )
else
todo_cihper_not_ecb( name_full , padding , items_data , password , "1234567890666666" )
end
else
log.info("-----","password error")
write_pack( "password length error ==="..password_len.."\r\n" )
end
end
end
end
end
如要正常演示,还需要在协程中加入调用,如下代码段所示:
-- 对称加密算法
crypto_todo_cipher( "aes[-](%d+)[-]%a%a%a", "zero",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]cbc", "zero",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ctr", "zero",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]xts", "zero",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]gcm", "zero",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ccm", "zero",cacheData)
crypto_todo_cipher( "aes[-](%d+)[-]%a%a%a", "pkcs7",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]cbc", "pkcs7",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ctr", "pkcs7",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]xts", "pkcs7",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]gcm", "pkcs7",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ccm", "pkcs7",cacheData)
crypto_todo_cipher( "aes[-](%d+)[-]%a%a%a", "one_and_zeros",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]cbc", "one_and_zeros",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ctr", "one_and_zeros",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]xts", "one_and_zeros",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]gcm", "one_and_zeros",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ccm", "one_and_zeros",cacheData)
crypto_todo_cipher( "aes[-](%d+)[-]%a%a%a", "zeros_and_len",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]cbc", "zeros_and_len",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ctr", "zeros_and_len",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]xts", "zeros_and_len",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]gcm", "zeros_and_len",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ccm", "zeros_and_len",cacheData)
crypto_todo_cipher( "aes[-](%d+)[-]%a%a%a", "none",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]cbc", "none",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ctr", "none",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]xts", "none",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]gcm", "none",cacheData)
--crypto_todo_cipher( "aes[-](%d+)[-]ccm", "none",cacheData)
crypto_todo_cipher( "des[-]ecb" , "pkcs7", cacheData )
crypto_todo_cipher( "des[-]ede[-]ecb" , "pkcs7", cacheData )
crypto_todo_cipher( "des[-]ede3[-]ecb" , "pkcs7", cacheData )
crypto_todo_cipher( "des[-]cbc" , "pkcs7", cacheData )
crypto_todo_cipher( "des[-]ede[-]cbc" , "pkcs7", cacheData )
crypto_todo_cipher( "des[-]ede3[-]cbc" , "pkcs7", cacheData )
crypto_todo_cipher( "des[-]ecb" , "zero", cacheData )
crypto_todo_cipher( "des[-]ede[-]ecb" , "zero", cacheData )
crypto_todo_cipher( "des[-]ede3[-]ecb" , "zero", cacheData )
crypto_todo_cipher( "des[-]cbc" , "zero", cacheData )
crypto_todo_cipher( "des[-]ede[-]cbc" , "zero", cacheData )
crypto_todo_cipher( "des[-]ede3[-]cbc" , "zero", cacheData )
由上面的调用代码看,相关的内容较多,我选几条进行测试,并贴出结果如下图:
上面代码只是非对称加密的几个具体示例,对应的内容太多,各位如果有兴趣,或者是自己想要的结果未在此示例内,可以自行组织测试字符串进行演示。具体格式为:方法名称 + 对齐方式 + 数据 +password+ 密码。其中“password + 密码”可以添加,也可以不添加,如果没有添加,将取用默认的密码字串,并在串口返回字符串中给出。举例如下:
另外需要说明一点就是,des 从加密方法名中,比较难以取得密码的合适长度,因而程序不好自动处理,因而这部分的密码如果长度不对,是会返回 error 的,如下图所示:
因而如果碰到这样的情况,就要注意是不是密码长度不匹配的问题,一般可以以 8 的倍数长度进行试错,具体我也没有好的办法。至于 aes 的相关加密方法,都有关于密码长度的数据,比如 128,因而此类方法都自动计算了长度,对密码长度进行了判定。
des-ede-cbczero1231878788731231password1234567812345678
aes-256-cbcpkcs71231878788731231
aes-192-cbczero1231878788731231
6.6 其它编码
其它编码包括 crc 校验,checksum 校验以及随机数据等内容,本演示只实现 base64 的演示,具体的方法与前面一样,就是编辑格式字符串通过串行口发送给开发板,格式为:base64+ 数据,有时为了方便,想只解码数据,因而另外追加了一个格式 base64_decode+base64 编码,因而这里有两格式:
base64+ 数据
base64_decode+base64 编码
未作严格差别,因而 base64_decode 格式时,也会被 base64 格式调用一次,请知悉。
相关代码如下:
cry_start,cry_end = cacheData:find( "base64" )
if cry_start ~= nil then
-- 打印所有支持的cipher
if crypto.base64_encode then
local in_text = cacheData:sub( cry_end + 1 , -1 );
local bas_text = crypto.base64_encode( in_text )
write_pack("base64 encode data [ "..in_text.." ] code [ " .. bas_text .. " ]" )
write_pack("base64 decode data [ "..bas_text.." ] code [ " .. crypto.base64_decode( bas_text ).." ]" )
else
log.info( "base64", "当前固件不支持 base64 " )
end
end
cry_start,cry_end = cacheData:find( "base64_decode" )
if cry_start ~= nil then
-- 打印所有支持的cipher
if crypto.base64_encode then
local in_text = cacheData:sub( cry_end + 1 , -1 );
local bas_text = crypto.base64_decode( in_text )
write_pack("base64 decode data [ "..in_text.." ] code [ " .. bas_text .." ]" )
else
log.info( "base64", "当前固件不支持 base64 " )
end
end
格式编码举例:
base64123456
base64_decodeMTIzNDU2
演示结果如下:
七、总结
本次进行加密解密的演示,碰到过一些问题,主要表现在三个方面:
Ⅰ. 串行口接口与原 Air724 参数顺序不兼容问题,导致开始测试串行口时出过一些错误;
Ⅱ. 串行口的读取接口,与 Air724 不兼容,不支持 “*l” 这样的参数,只能传入整数,导致测试串行口时出错。
Ⅲ. 字符串的匹配也遇到过一些阻碍,主要是字符 “-” 是特殊字符,而加官解密方法我不少都有这个字符,导致识别不了,为此学习了在关字符匹配的问题,文档中也有应用,感觉收获良多。也因此而简化了程序调用,比如传入匹配字符串 “aes[-]%d+[-]%a%a%a”,就将 aes-128-cbc、aes-192-cbc 等全包含了,减少了调用数量。
以上问题虽然是小问题,花费一点时间也就能得到解决,但是却很好地说明:参与即有收益。因而也希望大家能积极参与,共同学习,共同进步。同时也希望本文能带给大家启发,能给大家参考。
最后要说明的是,本文行一般随同代码使用小写字符,具体原因再次说明一下,首先是由于代码 api 多为小写;其次是为了在进行输入交互时,不需要频繁地大小写切换,避免切换大小导致的输入错误。
八、 完全代码
文件名:main.lua
固件如更新,一般也会同步更新 Luatools 工具包,因而具体使用时,当有 Luatools 更新时,及时更新即可。
184233cc (create 20250113 仓库建立)