公司项目需要过等保三级,找了找Unity和等保三相关的文章网上不多,就在这里记录下。
这篇文章算是开篇,边做边记录整个过程。
等保三级具体的背景就不聊了。
过等保三级涉及到各种加密,这时候就涉及到一个东西,国密加密算法。
国密标准目前了解的有sm1~sm9还有一个zuc算法
项目目前会用到sm2和sm4,sm2是非对称加密,sm4是分组对称加密。
SM4加密算法
具体用到的库是BouncyCastle.Crypto.dll
这个库的官方网址是
The Legion of the Bouncy Castlebouncycastle.org/csharp/index.html
这个库只有很简单sm2或者sm4实现,没有提供cbc,ecb等模式
具体实现可以参考国密的pdf,具体标准地址可以看这里
密码行业标准化技术委员会www.gmbz.org.cn/main/bzlb.html
当然github也有好心人分享的实现了cbc,ecb模式的工具函数
hz281529512/SecretTestgithub.com/hz281529512/SecretTest
分组加密的加密模式
每次只能加密固定长度的名为,如果需要加密更长的明文则需要对分组密码进行迭代。而分组密码的迭代方法就称为分组密码的模式。
分组加密有如下几种模式
- ECB模式:电子密码本模式
- CBC模式: 密码分组链接模式
- CFB模式: 密文反馈模式
- OFB模式: 输出反馈模式
- CTR模式: 计数器模式
其中ECB模式有一个显著的安全问题:如果使用相同的密钥,那么相同的明文块就会生成相同的密文块,不能很好的隐藏数据模式。这听起来没什么大事,但事实上这对数据安全是一个很大的威胁,下面这张图很明显的体现出了这个问题:
具体可以看下面两个文档
AI1379:AES加密(3):AES加密模式与填充43 赞同 · 9 评论文章
分组加密的填充模式
对称密钥加密设计的块密码工作模式要求输入明文长度必须是块长度的整数倍,因此信息必须填充至满足要求。由于没找到SM4具体支持的填充算法,不过看BouncyCastle.Crypto.dll里面的加密源码是必须是16的倍数这就和PKCS7比较一致,并且SecretTest中实现的填充算法也是符合PKCS7标准的
具体分组加密的填充规则可以看下面的文档
对称加密的填充方式www.jianshu.com/p/7c5b4a3c2c30
Unity的资源加密
Unity需要加密的资源大概有两类,一类是Assetbundle,另一类就是配置文件
配置文件都不大,直接整文件加密就好,没啥特殊需要处理的
但是AssetBundle不行了,因为通常都很大,尤其场景相关的,我们项目最大的场景AssetBundle有1G大小,如果整文件加密的话,就会有一些细节需要考虑了。
Unity常用的AssetBundle加载接口有3个
LoadFromFile 这个只能加载未加密的文件,无法使用
LoadFromMemory 这个虽然可以加密,但是内存中占用的内存过大,也舍弃了
LoadFromStream 这个可以用,但是需要设置每次读取的内存大小,这就需要对AssetBundle进行合适的改造,分段加密才能使用。
接口简单的使用方式可以看下面的文章
Unity3D研究院之加密Assetbundle不占内存(一百零五) | 雨松MOMO程序研究院www.xuanyusong.com/archives/4607
使用SM4加密Assetbundle
我们项目的AssetBundle都非常大,有的场景相关的AssetBundle有将近1G大小,加密时,如果使用LoadFromMemeory会导致巨大的GC问题,那就是剩LoadFromStream这种方式可用,其实这个接口依然会有GC,只是自己可以控制每次GC的大小。目前我选用的是32K大小。这样就面临一个问题,如果想使用SM4加密,就需要对AssetBundle进行分段加密,每32k加密一次,解密时也是每32k解密一次。还有就是资源的Padding问题,这是由SM4这个算法决定的。待加密的数据需要16byte对齐
填充模式和Assetbundle.LoadFromStream加载的冲突
SM4加密是分组加密,每次加密的数据都需要16byte对齐,因此需要选择合适的padding模式,常用的有PKCS7和NoPadding模式。
NoPadding模式
其实也需要16byte对齐,只是需要加密者需要保证加密的数据是16byte对齐的。
PKCS7模式
由于PKCS7的填充规则,当明文是16的倍数时,会在后面补上16byte的数据。
如果加密的是127byte的数据会被填充为128byte,如果是128byte的数据最后会加上16个byte的额外填充数据。
这样就和Assetbundle.LoadFromStream这个流加密冲突了,因为会有多的冗余数据,这样加密和解密的时候获取的分段长度是不一致的
权衡下来后,选用的NoPadding模式,这样加密流程就变成了
1.Padding,手动将原始AssetBundle填充为16的整数倍,目前使用0作为填充
2.使用SM4的NoPadding模式加密
3.重写自己的SM4Stream类,实现SM4的解密逻辑
4.使用AssetBundle.LoadFromStream接口调用SM4Stream读取分段加密后的资源
下面是一个自己写的简单的Demo,实现了上面4个过程,不定时更新
https://github.com/t1633361/AssetbundleSm4Encryptgithub.com/t1633361/AssetbundleSm4Encrypt
备注:
目前LoadFromStream这个接口,如果加载LZ4方式压缩的场景AssetBundle会导致崩溃,如果想加载场景的AssetBunle可以选用LZMA模式,非压缩模式未测试,不过应该没问题。
崩溃解决了,具体看我的另一篇文章
后记:
等保三已经过了,最后没用到SM4算法,而是用的AES,因为如果想用SM4还需要对SM4单独走验证流程,就切回了AES。
等保三对客户端没啥要求,主要还是在服务器测做的事情很多。客户端把所有数据文件和配置文件加个密,保证不是明文就OK了。当然我们的代码没有用il2cpp,用的反混淆,不过这个不是因为等保做的,而是之前处理的。