随着《完蛋!我被美女包围!》和《美女,别影响我学习》等影视互动游戏的爆火 公司最近也有款影视互动项目 于是乎就接到了对视频加密的任务 毕竟谁也不想直接被拿到几十个G的视频原文件看完直接退款吧 于是乎研究了一下当前的加密方案(仅限用Unity原生 VideoPlayer播放)

这种方式就不做过多介绍了 接触过热更新的小伙伴们基本手拿把掐 本质就是把视频源文件打成ab包 要播放视频的时候在异步解包播放
优点:操作简单方便 加载也比较快
缺点:有可能会被解包 防君子不防小人
这种方式本质就是将任意文件转换成字节数组 然后对字节数组进行加密解密操作 这个方式不限于视频文件 任何想要加密的文件都可以

加密方式也有许多 比如 将所有字节取相反的二进制 把所以字节移位、截取数组开头的一段数组放到数组末尾、自定义个字符串转换成数组拼接到数组开头 等等...
然后把加密后的字节数组在通过File.WriteAllBytes方法写成文件 解密的时候在进行加密时候的逆向操作即可
以取相反二进制为例
- //加密资源
- public static void BuildVideoByTurnByte()
- {
- //要加密的视频资源目录
- string videoPath = Application.streamingAssetsPath + "/Test/";
- //加密后的视频资源目录
- string videoOutPath = Application.streamingAssetsPath + "/Test2/";
-
- //示例 选取目录下的所有文件
- var files = Directory.GetFiles(videoPath, "*", SearchOption.AllDirectories);
- List<string> list = new List<string>();
- for (int i = 0; i < files.Length; i++)
- {
- //筛选掉.meta文件 不做操作
- string ext = Path.GetExtension(files[i]);
- if (ext == ".meta")
- continue;
-
- //获取文件名
- string fileName = Path.GetFileNameWithoutExtension(files[i]);
- //将文件转换为字节数组
- byte[] bytes = File.ReadAllBytes(files[i]);
- //加密
- byte[] buffer_ed = TurnByte(bytes);
- //输出加密后的文件 后缀名随你喜欢取 这里以哥哥为例
- File.WriteAllBytes(videoOutPath + fileName + ".cxk", buffer_ed);
-
- }
-
- Logger.Log("资源加密成功");
- }
-
-
- public static byte[] TurnByte(byte[] input)
- {
-
- for (int i = 0; i < input.Length; i++)
- {
- //隐式类型转换
- byte currentByte = input[i];
- //去相反的二进制
- byte shiftedValue = (byte)(~currentByte);
- //替换
- input[i] = shiftedValue;
- }
-
- return input;
- }
以上操作便可把.mp4文件写入成.cxk的文件 此时如果直接把更改后缀回.mp4 播放 会发现无法播放
Unity里播放如下
- public void PlayVideo(string name)
- {
- //VideoPlayer组件
- VideoPlayer mPlayer = gameObject.GetComponent
(); -
- //加密后的视频路径
- string resourceUrl = Application.streamingAssetsPath + "/Test2/";
- //解密后的临时视频路径
- string tempUrl = Path.GetTempPath() + "Temp/";
-
- //临时目录空的话就创建
- if (Directory.Exists(tempUrl))
- {
- //删除临时目录下的所有文件
- foreach (string filePath in Directory.GetFiles(tempUrl))
- {
- File.Delete(filePath);
- }
- }
- else
- {
- //创建目录
- Directory.CreateDirectory(tempUrl);
- }
-
- //如果加密路径下没有对应名字的加密文件 或者 已经临时目录下已有解密的视频文件 则不执行
- if (File.Exists(resourceUrl + name + ".cxk") && !File.Exists(tempUrl + name + ".mp4"))
- {
- //将文件转换成字节数组
- byte[] bytes = File.ReadAllBytes(resourceUrl + name + ".cxk");
- //取相反的二进制
- byte[] buffer_ed = TurnByte(bytes);
-
- //将字节数组写入到临时目录下
- File.WriteAllBytes(tempUrl + name + ".mp4", buffer_ed);
- }
-
- //给VideoPlayer组件赋值并播放
- mPlayer.url = tempUrl + name + ".mp4";
- mPlayer.Play();
- }
-
- public static byte[] TurnByte(byte[] input)
- {
- for (int i = 0; i < input.Length; i++)
- {
- byte currentByte = input[i];
-
- byte shiftedValue = (byte)(~currentByte);
- // 将移位后的字节添加到加密byte数组中
- input[i] = shiftedValue;
- }
-
- return input;
- }
这个时候运行游戏就可以正常播放视频啦

基本上就可以满足加密的需求了 毕竟别人也很难猜到你对字节做了什么羞羞的事情
优点:视频加密解密速度快
缺点:有被破解的风险(不过谁会这么无聊呢)
这种方式基本上很难破解了 因为基于AES加密的方式 安全性可以保障 具体的加密方式方法可以点最下方的链接去到原作者的博客 里面有非常详细的关于AES加密的介绍 不过加密解密的过程也会比较耗时 同样的也是可以把任何格式的文件都加密 毕竟本质上也是对字节数组进行操作 只不过是方法二字节加密的Plus版本罢了

下面是代码
VideoDecryptTools.cs 加密方法类
- using System.IO;
- using System.Security.Cryptography;
- using System.Text;
- using System;
-
- public class VideoDecryptTools
- {
- //读取视频文件
- public static void LoadVideo(string dataUrl, string res, string tempUrl, string password)
- {
- //获取对应的视频文件转换为字节数组
- byte[] bytes = AuthGetFileData(dataUrl + res + ".cxk");
- byte[] buffer_ed = DecryptByte(bytes, password);
-
- if (buffer_ed != null)
- {
- File.WriteAllBytes(tempUrl + res + ".mp4", buffer_ed);
- }
- }
-
-
- //将文件转换成字节数组
- public static byte[] AuthGetFileData(string fileUrl)
- {
- FileStream fs = new FileStream(fileUrl, FileMode.Open, FileAccess.Read);
- byte[] buffur = new byte[fs.Length];
-
- fs.Read(buffur, 0, buffur.Length);
- fs.Close();
- return buffur;
- }
-
- //加密
- public static byte[] DecryptByte(byte[] buffer, string password)
- {
- byte[] decrypted;
- using (Aes aes = Aes.Create())
- {
- //设定密钥和向量
- (aes.Key, aes.IV) = GenerateKeyAndIV(password);
- //设定运算模式和填充模式
- aes.Mode = CipherMode.CBC;
- aes.Padding = PaddingMode.PKCS7;
- //创建解密器对象(加解密方法不同处仅仅这一句话)
- var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
- using (MemoryStream msDecrypt = new MemoryStream(buffer))
- {
- try
- {
- //选择Read模式
- using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
- {
- //创建临时数组,用于包含可用字节+无用字节
- byte[] buffer_T = new byte[buffer.Length];
- //对加密数组进行解密,并通过i确定实际多少字节可用
- int i = csDecrypt.Read(buffer_T, 0, buffer.Length);
-
- //使用Read模式不能有此句,但write模式必须要有。
- //csDecrypt.FlushFinalBlock();
-
- //创建只容纳可用字节的数组
- decrypted = new byte[i];
-
- //从bufferT拷贝出可用字节到decrypted
- Array.Copy(buffer_T, 0, decrypted, 0, i);
- }
- return decrypted;
- }
- catch (CryptographicException)
- {
- Logger.Log("线程未加载完成视频后 切换线程 可忽略!");
- return null;
- }
- }
- }
- }
-
- //解密
- public static byte[] EncryptByte(byte[] buffer, string password)
- {
- byte[] encrypted;
- using (Aes aes = Aes.Create())
- {
- //设定密钥和向量
- (aes.Key, aes.IV) = GenerateKeyAndIV(password);
- //设定运算模式和填充模式
- aes.Mode = CipherMode.CBC;
- aes.Padding = PaddingMode.PKCS7;
- //创建加密器对象(加解密方法不同处仅仅这一句话)
- var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
- using (MemoryStream msEncrypt = new MemoryStream())
- {
- using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))//选择Write模式
- {
- //对原数组加密并写入流中
- csEncrypt.Write(buffer, 0, buffer.Length);
- //使用Write模式需要此句,但Read模式必须要有。
- csEncrypt.FlushFinalBlock();
- //从流中写入数组(加密之后,数组变长,详见方法AesCoreSingleTest内容)
- encrypted = msEncrypt.ToArray();
- }
- }
- }
- return encrypted;
- }
-
- public static (byte[] Key, byte[] IV) GenerateKeyAndIV(string password)
- {
- byte[] key = new byte[32];
- byte[] iv = new byte[16];
- byte[] hash = default;
- if (string.IsNullOrWhiteSpace(password))
- throw new ArgumentException("必须输入口令!");
- using (SHA384 sha = SHA384.Create())
- {
-
- byte[] buffer = Encoding.UTF8.GetBytes(password);
- hash = sha.ComputeHash(buffer);
- }
- //用SHA384的原因:生成的384位哈希值正好被分成两段使用。(32+16)*8=384。
- Array.Copy(hash, 0, key, 0, 32);//生成256位密钥(32*8=256)
- Array.Copy(hash, 32, iv, 0, 16);//生成128位向量(16*8=128)
- return (Key: key, IV: iv);
- }
- }
参数:
name:为你要加密的文件名
password:为你的加密秘钥
加密:
- //加密资源
- public static void BuildVideoByTurnByte()
- {
- //要加密的视频资源目录
- string videoPath = Application.streamingAssetsPath + "/Test/";
- //加密后的视频资源目录
- string videoOutPath = Application.streamingAssetsPath + "/Test2/";
-
- //示例 选取目录下的所有文件
- var files = Directory.GetFiles(videoPath, "*", SearchOption.AllDirectories);
- List<string> list = new List<string>();
- for (int i = 0; i < files.Length; i++)
- {
- //筛选掉.meta文件 不做操作
- string ext = Path.GetExtension(files[i]);
- if (ext == ".meta")
- continue;
-
- //获取文件名
- string fileName = Path.GetFileNameWithoutExtension(files[i]);
- //将文件转换为字节数组
- byte[] bytes = VideoDecryptTools.AuthGetFileData(files[i]);
- //加密 秘钥为 jinitaimei
- byte[] buffer_ed = VideoDecryptTools.EncryptByte(bytes, "jinitaimei");
-
- }
-
- Logger.Log("资源加密成功");
- }
播放视频:
- public void PlayVideo(string name,string password)
- {
- //password 为上段代码片段的 "jinitiaomei"
-
- //VideoPlayer组件
- VideoPlayer mPlayer = gameObject.GetComponent
(); -
- //加密后的视频路径
- string resourceUrl = Application.streamingAssetsPath + "/Test2/";
- //解密后的临时视频路径
- string tempUrl = Path.GetTempPath() + "Temp/";
-
- //临时目录空的话就创建
- if (Directory.Exists(tempUrl))
- {
- //删除临时目录下的所有文件
- foreach (string filePath in Directory.GetFiles(tempUrl))
- {
- File.Delete(filePath);
- }
- }
- else
- {
- //创建目录
- Directory.CreateDirectory(tempUrl);
- }
-
- //如果加密路径下没有对应名字的加密文件 或者 已经临时目录下已有解密的视频文件 则不执行
- if (File.Exists(resourceUrl + name + ".cxk") && !File.Exists(tempUrl + name + ".mp4"))
- {
- VideoDecryptTools.LoadVideo(resourceUrl, name, tempUrl, password);
- }
-
- //给VideoPlayer组件赋值并播放
- mPlayer.url = tempUrl + name + ".mp4";
- mPlayer.Play();
- }
以上整个加密解密的流程就完毕了
需要注意的是 :
视频解密同样是跟方法二一样将视频缓存到本地的缓存目录 如果视频文件很大 解密的过程会比较慢 就会导致点击播放视频 要卡个几秒才会播放视频 对于游戏的体验观感十分不友好 如果用这种方式的话 建议提前缓存下一个视频

优点:安全 还是安全
缺点:加密解密的时间损耗比较大
以上就是本篇的全部内容了 如果有帮到你的话还请给我点个免费的赞
当然以上内容只是本人新手的一些见解 如果有大佬还请勿喷 多多指出问题 谢谢嘻嘻
AES加密参考:https://www.cnblogs.com/syzcyyx/articles/13657222.html