首页 > 其他分享 >Unity制作影视互动游戏视频加密方案分享

Unity制作影视互动游戏视频加密方案分享

时间:2024-06-17 16:57:49浏览次数:24  
标签:加密 string buffer Unity 数组 影视 byte 字节

前言

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

 

一、Assetbundle打包

这种方式就不做过多介绍了 接触过热更新的小伙伴们基本手拿把掐 本质就是把视频源文件打成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<VideoPlayer>();
     
     //加密后的视频路径
     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加密的方式 安全性可以保障 具体的加密方式方法可以点最下方的链接去到原作者的博客 里面有非常详细的关于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<VideoPlayer>();
        
        //加密后的视频路径
        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

标签:加密,string,buffer,Unity,数组,影视,byte,字节
From: https://blog.csdn.net/2401_83152164/article/details/139620771

相关文章

  • Unity 读取xml
      ReadXml.cs内容:usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingSystem.Xml;publicclassReadXml:MonoBehaviour{privateXmlDocumentxmldoc;privateXmlNoderoot;privatestringurl;voidStart(......
  • Unity 脚本修改方块透明度使其有一个渐变效果
    usingUnityEngine;usingSystem.Collections;publicclassTestClass:MonoBehaviour{privatefloatAlphaValue=1.0f;privatefloattime=0.0f;privateboolstate=false;Materialmaterial;privatevoidStart(){mater......
  • 椭圆曲线加密算法中公钥与私钥互换性分析
    PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、密码学、联邦学习、同态加密等隐私计算领域的技术和内容。在现代密码学中,椭圆曲线加密算法(EllipticCurveCryptography,ECC)因其高效的加密速度、较小的密钥尺寸和较高的安全性而受到广泛关注。ECC基......
  • Unity学习笔记----摄像机组件信息相关知识点总结
    一.ClearFlags1.skybox天空盒一般用于3d游戏。2.SolidColor颜色填充一般用于2d游戏。3.Depthonly只画该层,背景透明与Depth配合使用,等会再写。4.Don'tClear不移除,渲染覆盖不会擦除上一帧的画面,一般不使用。默认二.CullingMask选择性渲染部分层级,可以指定渲染对......
  • C#.NET与JAVA互通之DES加密V2024
    C#.NET与JAVA互通之DES加密V2024 配置视频:  环境:.NETFramework4.6控制台程序JAVA这边:JDK8(1.8)控制台程序 注意点:1.由于密钥、明文、密文的输入输出参数,都是byte数组(byte[]),所以:字符串转byte数组(byte[])环节,双方要约定好编码。2.KEY和IV从字符串转byte数......
  • Unity的生命周期函数
    在Unity中,各个生命周期函数是在特定的时机被调用的,它们的执行顺序如下:1.Awake:当脚本实例被加载时调用,用于初始化数据。如果物体上有多个脚本,它们的Awake方法会在Start方法之前执行。2.OnEnable:当对象变为活动状态(enabled)或脚本被启用时调用。如果在场景加载后对象已经......
  • BitLocker加密分区丢失了如何恢复?
    关于BitLocker加密分区丢失与恢复BitLocker是Windows操作系统提供的磁盘加密技术,可以更好的保护电脑中的数据。被BitLocker加密后的分区,在文件管理器中可以看到分区上会有个黄色的锁(如下图所示),双击该分区,会弹出窗口要求输入密码或是秘钥。输入正确的密码/秘钥后,即可解锁BitLocke......
  • unitycatalog datagrics 开源的data&ai 多模catalog
    unitycatalogdatagrics开源的data&ai多模catalog包含的特性支持任意格式、引擎、资产的多摸接口 支持包含了deltalake,iceberg,uniform,paquert,csv。。。等格式,超越表,支持非结构化数据以及ai资产,插件化的架构,可以支持hms以及icebergrestcatalog以及其他插件(比如ai),与delt......
  • Unity 利用Cache实现边下边玩
    现在手机游戏的常规更新方案都是在启动时下载所有资源更新,游戏质量高的、用户粘性大的有底气,先安装2个G,启动再更新2个G,文件小了玩家还觉得品质不行不想玩。最近在做微信、抖音小游戏,使用他们提供的资源缓存方案,现在要转成AndroidAPP,也想用这种边下边玩的机制把首包做小。其实......
  • Http压缩zip,加密base64发送与获取
    //数据压缩成Zip再发送publicstaticstringZipBasebyte(stringxml){byte[]bytesToCompress=Encoding.GetEncoding("GBK").GetBytes(xml);MemoryStreamms=newMemoryStream();ZipEntryze=newZipEntry("servlets......