如果你想在项目工程上操作,又发现项目工程并没导入Tools,可以从xLua-master工程拷贝到项目工程Assets同级目录。
创建一个.bat文件(LuaSignBat.bat)
cd /d %~dp0
rmdir /q /s LuaSign
"FilesSignature.exe" "../Assets/GameMain/LuaScripts" "../Assets/GameMain/LuaSign"
"FilesSignature.exe"是执行签名的exe
"../Assets/GameMain/LuaScripts"是工程内要被签名的Lua文件根目录
"../Assets/GameMain/LuaSign"是签名之后的Lua文件根目录
注意:这个签名流程如果是放在有自动构建包体的项目里是要写入自动流程内的,比如打包ab包时,打的Lua文件ab包不是LuaScripts而是LuaSign,Lua的加载器也是加载的是LuaSign的,关于这部分不在本文讲解。
签名后的文件如下
仅有第一行是密文,其他还是明文状态的,签名仅是对文件头部加密,避免程序直接读取二进制文件,如果要全内容加密或混淆就要在Loader加载器解密,以及自己写个API对全部签名后的文件加密。
针对签名后的文件加载器的代码要修改为如下:
loader = new SignatureLoader(PUBLIC_KEY, 原加载器方法)
PUBLIC_KEY是KeyPairsGen.exe生成的key_ras.pub文件内容,可以文本形式打开复制。
如果要使用luaEnv.DoString(byte[], chunkName, luaTable)这类代码去获取lua对应的LuaTable表,那么我们就必须传入的byte[]是签名解码后的byte[],而这个解码API就位于SignatureLoader.cs脚本内。我们可以针对它进行封装一个提供外部的解码方法如下:
cs
byte[] load_and_verify(ref string filepath)
{
byte[] data = userLoader(ref filepath);
return GetVerifyBytes(data, filepath);
}
public byte[] GetVerifyBytes(byte[] data, string filepath)
{
if (data == null)
{
return null;
}
if (data.Length < 128)
{
throw new InvalidProgramException(filepath + " length less than 128!");
}
byte[] sig = new byte[128];
byte[] filecontent = new byte[data.Length - 128];
Array.Copy(data, sig, 128);
Array.Copy(data, 128, filecontent, 0, filecontent.Length);
#if !UNITY_WSA || UNITY_EDITOR
if (!rsa.VerifyData(filecontent, sha, sig))
{
throw new InvalidProgramException(filepath + " has invalid signature!");
}
#else
if (!CryptographicEngine.VerifySignature(key, CryptographicBuffer.CreateFromByteArray(filecontent), CryptographicBuffer.CreateFromByteArray(sig)))
{
throw new InvalidProgramException(filepath + " has invalid signature!");
}
#endif
return filecontent;
}
其中load_and_verify是原有的方法,GetVerifyBytes是我自定义的提供外部解码byte[]的,需自行加载文件原始byte[],以及传入lua文件相对路径(不带.lua后缀)实际也可以写个不用传路径的方法,因为只是用来警告提示的。
cs
string filepaths = Application.dataPath + "/XLua/Examples/10_SignatureLoader/signatured1.lua";
byte[] bytes = File.ReadAllBytes(filepaths);
bytes = loader.GetVerifyBytes(bytes, "signatured1");
luaenv.DoString(bytes, "chunk", luaTable);
luaTable.Get("OnStart", out luaOnStart);
luaOnStart();
特别注意:luaTable是必须设置元表的一个表,不然无法识别各种全局变量,例如: 'require'
csluaMetaTable = luaenv.NewTable(); luaMetaTable.Set("__index", luaenv.Global); luaTable = luaenv.NewTable(); luaTable.SetMetaTable(luaMetaTable);
加密部分:(针对签名后的文件加密)
cs
using UnityEngine;
using UnityEditor;
using System.IO;
public static class EncryptFileEditor
{
[MenuItem("Lua/加密Lua")]
public static void EncryptLuaFile()
{
//异或 加密,再用一次这个方法就是解密了
string luaFileDir = Application.dataPath + "//XLua/Examples/10_SignatureLoader";
byte[] key_char = System.Text.Encoding.Unicode.GetBytes("abcd");
string[] files = Directory.GetFiles(luaFileDir, "*.lua");
foreach (string filePath in files)
{
byte[] bytes = File.ReadAllBytes(filePath);
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)(bytes[i] ^ key_char[i % key_char.Length]);
}
File.WriteAllBytes(filePath, bytes);
}
}
}
解密部分:(针对签名后的文件解密,而签名解码是在后面执行的)
cs
loader = new SignatureLoader(PUBLIC_KEY, (ref string filepath) =>
{
filepath = Application.dataPath + "/XLua/Examples/10_SignatureLoader/" + filepath.Replace('.', '/') + ".lua";
if (File.Exists(filepath))
{
byte[] bytes = File.ReadAllBytes(filepath);
//解密
byte[] key_char = System.Text.Encoding.Unicode.GetBytes("abcd");
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)(bytes[i] ^ key_char[i % key_char.Length]);
}
return bytes;
}
else
{
return null;
}
});
使用方法:
cs
string filepaths = Application.dataPath + "/XLua/Examples/10_SignatureLoader/signatured1.lua";
byte[] bytes = File.ReadAllBytes(filepaths); //读取原始文件
bytes = Decrypt(bytes); //先解密
bytes = loader.GetVerifyBytes(bytes, "signatured1");//后解签名
luaenv.DoString(bytes, "chunk", luaTable);
luaTable.Get("OnStart", out luaOnStart);
luaOnStart();
注意事项:必须是先签名、后加密,除非修改SignatureLoader.cs类的load_and_verify方法,将这个方法直接改写原本如下:
cs
byte[] load_and_verify(ref string filepath)
{
byte[] data = userLoader(ref filepath);
return GetVerifyBytes(data, filepath);
}
userLoader就是进入到我们在实例化SignatureLoader时传入的第二个参数(一个委托对象),委托执行了加载原文件字节数组、解密操作,而load_and_verify函数是先进行的userLoader,拿到解密后的byte[]再进行签名解码,所以只能先签名文件、后加密文件,对应先解密文件,后解码签名。