Unity功能——资源、文件的AES加密解密

一、AES

1、简单介绍说明

使用AES 加密标准对项目的资源文件进行加密解密;

AES 高级加密标准,是下一代的加密算法标准,速度快,安全级别高,

目前 AES 标准的一个实现是 Rijndael 算法;

2、AES加密解密脚本

这个是个人学习Untiy相关知识时,课程老师提供的他网上找的一个AES加密解密脚本;

根据不同习惯需求,还可去网上搜其他的加密解密功能脚本。加密解密方式并不唯一。

这边只是简单学用加密解密功能,非相关从业者,就不具体研究加密解密写算法了;

cs 复制代码
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

/// <summary>
/// AES 加密
/// 高级加密标准,是下一代的加密算法标准,速度快,安全级别高,
/// 目前 AES 标准的一个实现是 Rijndael 算法
/// </summary>
public class AES
{
    /// <summary>
    /// AES 加密
    /// </summary>
    /// <param name="EncryptString">待加密密文</param>
    /// <param name="EncryptKey">加密密钥</param>
    public static string AESEncrypt(string EncryptString, string EncryptKey)
    {
        return Convert.ToBase64String(AESEncrypt(Encoding.Default.GetBytes(EncryptString), EncryptKey));
    }

    /// <summary>
    /// AES 加密
    /// </summary>
    /// <param name="EncryptString">待加密密文</param>
    /// <param name="EncryptKey">加密密钥</param>
    public static byte[] AESEncrypt(byte[] EncryptByte, string EncryptKey)
    {
        if (EncryptByte.Length == 0) { throw (new Exception("明文不得为空")); }
        if (string.IsNullOrEmpty(EncryptKey)) { throw (new Exception("密钥不得为空")); }
        byte[] m_strEncrypt;
        byte[] m_btIV = Convert.FromBase64String("Rkb4jvUy/ye7Cd7k89QQgQ==");
        byte[] m_salt = Convert.FromBase64String("gsf4jvkyhye5/d7k8OrLgM==");
        Rijndael m_AESProvider = Rijndael.Create();
        try
        {
            MemoryStream m_stream = new MemoryStream();
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(EncryptKey, m_salt);
            ICryptoTransform transform = m_AESProvider.CreateEncryptor(pdb.GetBytes(32), m_btIV);
            CryptoStream m_csstream = new CryptoStream(m_stream, transform, CryptoStreamMode.Write);
            m_csstream.Write(EncryptByte, 0, EncryptByte.Length);
            m_csstream.FlushFinalBlock();
            m_strEncrypt = m_stream.ToArray();
            m_stream.Close(); m_stream.Dispose();
            m_csstream.Close(); m_csstream.Dispose();
        }
        catch (IOException ex) { throw ex; }
        catch (CryptographicException ex) { throw ex; }
        catch (ArgumentException ex) { throw ex; }
        catch (Exception ex) { throw ex; }
        finally { m_AESProvider.Clear(); }
        return m_strEncrypt;
    }

    /// <summary>
    /// AES 解密
    /// </summary>
    /// <param name="DecryptString">待解密密文</param>
    /// <param name="DecryptKey">解密密钥</param>
    public static string AESDecrypt(string DecryptString, string DecryptKey)
    {
        return Convert.ToBase64String(AESDecrypt(Encoding.Default.GetBytes(DecryptString), DecryptKey));
    }

    /// <summary>
    /// AES 解密
    /// </summary>
    /// <param name="DecryptString">待解密密文</param>
    /// <param name="DecryptKey">解密密钥</param>
    public static byte[] AESDecrypt(byte[] DecryptByte, string DecryptKey)
    {
        if (DecryptByte.Length == 0) { throw (new Exception("密文不得为空")); }
        if (string.IsNullOrEmpty(DecryptKey)) { throw (new Exception("密钥不得为空")); }
        byte[] m_strDecrypt;
        byte[] m_btIV = Convert.FromBase64String("Rkb4jvUy/ye7Cd7k89QQgQ==");
        byte[] m_salt = Convert.FromBase64String("gsf4jvkyhye5/d7k8OrLgM==");
        Rijndael m_AESProvider = Rijndael.Create();
        try
        {
            MemoryStream m_stream = new MemoryStream();
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(DecryptKey, m_salt);
            ICryptoTransform transform = m_AESProvider.CreateDecryptor(pdb.GetBytes(32), m_btIV);
            CryptoStream m_csstream = new CryptoStream(m_stream, transform, CryptoStreamMode.Write);
            m_csstream.Write(DecryptByte, 0, DecryptByte.Length);
            m_csstream.FlushFinalBlock();
            m_strDecrypt = m_stream.ToArray();
            m_stream.Close(); m_stream.Dispose();
            m_csstream.Close(); m_csstream.Dispose();
        }
        catch (IOException ex) { throw ex; }
        catch (CryptographicException ex) { throw ex; }
        catch (ArgumentException ex) { throw ex; }
        catch (Exception ex) { throw ex; }
        finally { m_AESProvider.Clear(); }
        return m_strDecrypt;
    }

}

二、AES加密方法

1、自定义AES加密方法
(1)判断文件中是否存在特定字节头

用于判断是否加密解密过,避免多次重复加密

cs 复制代码
// 字节头
private static string AESHead = "AESEncrypt";            

// 检查文件是否有特定字节头
public static void CheckHead(string path, string EncrptyKey)
{
    ... ...
    using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
    {
        if (fs != null)
        {
            byte[] headBuff = new byte[10];//字节头AESHead长度为10
            fs.Read(headBuff, 0, headBuff.Length);//读取字节头
            string headTag = Encoding.UTF8.GetString(headBuff);
            if (headTag == AESHead)//,判断是否已经加密过了
            {
#if UNITY_EDITOR
                Debug.Log(path + "文件中存在字节头!");
#endif
                return;
            }
        }
    }
    ... ...
}
(2)向文件中添加字节头

用于标记该文件被加密过,可根据需求自定义设置头节点内容

cs 复制代码
// 字节头
private static string AESHead = "AESEncrypt";            

// 文件里添加头节点
public static void AddHead(string path, string EncrptyKey)
{
    ... ...
    using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
    {
        if (fs != null)
        {
            fs.Seek(0, SeekOrigin.Begin);//把光标置为0位置!
            byte[] buffer = new byte[fs.Length];
            fs.Read(buffer, 0, Convert.ToInt32(fs.Length));//把fs读出到buffer里
            fs.Seek(0, SeekOrigin.Begin);//把光标放到起始0位置
            fs.SetLength(0);//清空数据
            byte[] headBuffer = Encoding.UTF8.GetBytes(AESHead);//UTF-8编码转为byte
            fs.Write(headBuffer, 0, headBuffer.Length);//将头节点写入
            fs.Write(EncBuffer, 0, EncBuffer.Length);//把数据写回字节流中
        }
    }
    ... ...
}
(3)AES不重复加密方法
cs 复制代码
    /// <summary>
    /// 文件加密,传入文件路径;
    /// path:filePath带文件名和后缀,EncrptyKey:设置的密钥
    /// </summary>
    /// <param name="path">待加密的文件的路径</param>
    /// <param name="EncrptyKey">自定义的密钥</param>
    public static void AESFileEncrypt(string path, string EncrptyKey)
    {
        if (!File.Exists(path))return;
        try
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))//打开文件流,读文件
            {
                if (fs != null)
                {
                    //读取字节头,判断是否已经加密过了
                    byte[] headBuff = new byte[10];
                    fs.Read(headBuff, 0, headBuff.Length);
                    string headTag = Encoding.UTF8.GetString(headBuff);
                    if (headTag == AESHead)
                    {
#if UNITY_EDITOR
                        Debug.Log(path + "已经加密过了!");
#endif
                        return;
                    }
                    //加密并且写入字节头
                    fs.Seek(0, SeekOrigin.Begin);//读之前光标置为0位置!
                    byte[] buffer = new byte[fs.Length];
                    fs.Read(buffer, 0, Convert.ToInt32(fs.Length));
                    fs.Seek(0, SeekOrigin.Begin);//把操作字节流的光标放到起始0位置
                    fs.SetLength(0);//清空字节流里的数据
                    byte[] headBuffer = Encoding.UTF8.GetBytes(AESHead);
                    fs.Write(headBuffer, 0, headBuffer.Length);//将头节点写入fs中
                    byte[] EncBuffer = AESEncrypt(buffer, EncrptyKey);//对buffer进行加密
                    fs.Write(EncBuffer, 0, EncBuffer.Length);//把加密后的数据写回fs中
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }
2、AES加密使用

注:做成了编辑器下功能按钮,AESFileTool这个脚本应放到Editor文件夹中

可点击Unity菜单栏"Tools--AES加密"按钮,对m_BundleTargetPath文件夹里的资源文件进行加密

cs 复制代码
public class AESFileTool
{
    // 资源文件地址
    private static string m_BunleTargetPath = Application.dataPath + "/../" + "TestAESFile";

    [MenuItem("Tools/AES加密")]
    public static void AES_EncryptAB()
    {
        DirectoryInfo directory = new DirectoryInfo(m_BunleTargetPath);//获取目标文件夹
        FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories);//获取所有的资源文件
        for (int i = 0; i < files.Length; i++)
        {
            if (!files[i].Name.EndsWith("meta") && !files[i].Name.EndsWith("manifest"))
                AES.AESFileEncrypt(files[i].FullName, "AES");//密钥AES,可自定义设置,设置不同密钥,加密结果不同
        }
        Debug.Log("加密完成!");

    }
}

三、AES解密方式

1、两种解密方法
(1)第一种解密方式

直接读文件数据,清空文件,将解密后的数据再写回文件中

注:会改动加密文件,不适合运行时

cs 复制代码
/// <summary>
/// 文件解密,传入文件路径(会改动加密文件,不适合运行时)
/// </summary>
/// <param name="path"></param>
/// <param name="EncrptyKey"></param>
public static void AESFileDecrypt(string path, string EncrptyKey)
{
    if (!File.Exists(path)) return;
    try
    {
        using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
        {
            if (fs != null)
            {
                byte[] headBuff = new byte[10];
                fs.Read(headBuff, 0, headBuff.Length);
                string headTag = Encoding.UTF8.GetString(headBuff);
                if (headTag == AESHead)//确定有加密过再进行解密修改文件
                {
                    byte[] buffer = new byte[fs.Length - headBuff.Length];
                    fs.Read(buffer, 0, Convert.ToInt32(fs.Length - headBuff.Length));//将数据读到buffer中
                    fs.Seek(0, SeekOrigin.Begin);//光标放到起始0位置处
                    fs.SetLength(0);//清空
                    byte[] DecBuffer = AESDecrypt(buffer, EncrptyKey);//解密
                    fs.Write(DecBuffer, 0, DecBuffer.Length);//将解密后的数据写回
                }
            }
        }
    }
    catch (Exception e)
    {
        Debug.LogError(e);
    }
}
(2)第二种解密方式

不改变文件,直接将解密后的数据返回出去,外部接受解密后的数据

cs 复制代码
/// <summary>
/// 文件解密,传入文件路径,返回字节
/// </summary>
/// <returns></returns>
public static byte[] AESFileByteDecrypt(string path, string EncrptyKey)
{
    if (!File.Exists(path)) return null;
    byte[] DecBuffer = null;
    try
    {
        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            if (fs != null)
            {
                byte[] headBuff = new byte[10];
                fs.Read(headBuff, 0, headBuff.Length);
                string headTag = Encoding.UTF8.GetString(headBuff);
                if (headTag == AESHead)
                {
                    byte[] buffer = new byte[fs.Length - headBuff.Length];
                    fs.Read(buffer, 0, Convert.ToInt32(fs.Length - headBuff.Length));
                    //不对字节流进行处理,只是获取解密后的数据
                    DecBuffer = AESDecrypt(buffer, EncrptyKey);
                }
            }
        }
    }
    catch (Exception e)
    {
        Debug.LogError(e);
    }
    return DecBuffer;//把解密后的数据返回出去
}
2、AES解密使用
(1)直接对资源解密

对应上面第一种解密方式;

注:和上面的AES加密AES_EncryptAB()工具,都是写在了AESFileTool.cs脚本里。

cs 复制代码
public class AESFileTool
{
    // 资源文件地址
    private static string m_BunleTargetPath = Application.dataPath + "/../" + "TestAESFile";

    [MenuItem("Tools/解密AB包")]
    public static void AES_DecryptAB()
    {
        DirectoryInfo directory = new DirectoryInfo(m_BunleTargetPath);//获取文件夹
        //获取所有的资源文件
        FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories); 
        for (int i = 0; i < files.Length; i++)
        {
            if (!files[i].Name.EndsWith("meta") && !files[i].Name.EndsWith("manifest"))
                AES.AESFileDecrypt(files[i].FullName, "AES");//对应前面加密时设置的密钥解密
        }
        Debug.Log("解密完成!");
    }
}
(2)不修改资源文件外部解密

在项目资源处理时,调用第二种解密方式进行解密使用;

cs 复制代码
// 加载单个assetbundle
private AssetBundle LoadAssetBundle(string fullPath)
{
    AssetBundle assetBundle = null;
    //assetBundle = AssetBundle.LoadFromFile(fullPath);//原:根据文件名获取资源

    //解密获得解密后的byte数组
    byte[] abDecBytes = AES.AESFileByteDecrypt(fullPath, "AES");
    assetBundle = AssetBundle.LoadFromMemory(abDecBytes);//根据数据获取资源

    if (assetBundle == null)Debug.LogError(" Load AssetBundle Error:" + fullPath);
        ... ...
    
    return assetBundle;
}
3、两种解密方式比较

两种解密区别,主要在对文件是否修改和资源文件加载方式上不同,以ab资源为例:

(1)第一种解密:

AES.AESFileDecrypt(files[i].FullName, "AES");

1)LoadFromFile方式加载:AssetBundle.LoadFromFile(FullPath);

根据文件路径来加载文件的形式读取资源文件。

2)优点:只会把文件头加载进来,不会把整个文件加载进来

3)缺点:会改动加密文件,不适合运行时使用;

(2)第二种解密:

byte[] abDecBytes = AES.AESFileByteDecrypt(fullPath, "AES");

1)LoadFromMemory方式加载:AssetBundle.LoadFromMemory(abDecBytes);

会把解密后返回的整个解密文件(byte数组形式)加载进来,从内存中读取数据;

2)优点:不改变原文件,且使后面实例化真正使用资源时,加载东西会更快;

3)缺点:每个ab包的资源文件都读进内存中,格外占内存。

相关推荐
时光追逐者7 分钟前
分享6个.NET开源的AI和LLM相关项目框架
人工智能·microsoft·ai·c#·.net·.netcore
friklogff35 分钟前
【C#生态园】提升C#开发效率:深入了解自然语言处理库与工具
开发语言·c#·区块链
仙魁XAN1 小时前
Unity 设计模式 之 创造型模式-【工厂方法模式】【抽象工厂模式】
unity·设计模式·工厂方法模式·抽象工厂模式
__water9 小时前
『功能项目』回调函数处理死亡【54】
c#·回调函数·unity引擎
__water9 小时前
『功能项目』眩晕图标显示【52】
c#·unity引擎·动画事件
__water10 小时前
『功能项目』第二职业法师的平A【57】
c#·unity引擎·魔法球伤害传递
躺下睡觉~11 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~11 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
__water12 小时前
『功能项目』战士的伤害型技能【45】
c#·unity引擎·战士职业伤害型技能
君莫愁。13 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎