一、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包的资源文件都读进内存中,格外占内存。