本文的重点在于如何使用UnrealPak的加密功能,以及相关的UE4源代码学习。本文参考了:https://www.cnblogs.com/shiroe/p/14803859.html 。
设置密钥
在编辑、项目设置中找到下面栏目,并点击“生成新的加密密钥”,就可以为Unreal Pak配置Encryption了。
保存后,观察到工程路径下 Config\DefaultCrypto.ini 具有这些配置:
[/Script/CryptoKeys.CryptoKeysSettings]
EncryptionKey=6A/h1s12rbKctPXXXXXXXXXX
bEncryptPakIniFiles=True
bEncryptPakIndex=True
bEncryptUAssetFiles=False
bEncryptAllAssetFiles=False
SigningPublicExponent=
SigningModulus=
SigningPrivateExponent=
bEnablePakSigning=False
其中的 bEncryptPakIniFiles 表示是否加密ini文件。bEncryptPakIndex 对应 UnrealPak.exe 的 -encryptindex 开关,它和 -encrypt 的区别是:
在UE4中,`-encryptindex`和`-encrypt`是两个命令行参数,用于对Pak文件进行加密的不同方式。
1. `-encryptindex`:这个参数用于对Pak文件中的索引进行加密。Pak文件的索引包含了文件名、文件路径和文件偏移等信息。通过对索引进行加密,可以防止未经授权的访问和修改Pak文件中的资源。
2. `-encrypt`:这个参数用于对Pak文件中的资源文件进行加密。资源文件包括游戏的纹理、模型、声音等实际的资源数据。通过对资源文件进行加密,可以保护这些资源的安全性,防止未经授权的访问和使用。
区别在于,`-encryptindex`只对Pak文件的索引进行加密,而`-encrypt`则对Pak文件中的资源文件进行加密。索引加密主要用于保护Pak文件的结构和元数据,而资源文件加密则用于保护实际的资源数据。
这两个参数可以根据项目的需求和安全性要求进行选择和组合使用。通常情况下,建议至少对资源文件进行加密,以保护游戏的核心资源。而对索引进行加密可以提供额外的保护层,增加Pak文件的安全性。
需要注意的是,加密Pak文件会增加加载和解密的开销,可能会对游戏的性能产生一定影响。因此,在使用这些参数时,需要权衡安全性和性能之间的平衡,并根据项目的需求进行选择。
在UnrealPak.exe的介绍中,可以看到只要指定了 “-enginedir=” 和 “-projectdir=” ,就会使用 DefaultCrypto.ini 中的Key来作为密钥,参考 https://www.cnblogs.com/shiroe/p/14803859.html,如下:
unrealpak PAK_NAME.pak -create=ResponseFile -compress -encrypt -encryptindex -aes=32bit_AESKey
// 在\Config\DefaultEncryption.ini文件中查找AES的密钥,4.26 提示用 -cryptokeys
-encryptindex -encryptionini -enginedir="<EngineDir>" -projectdir="<GameDir>" -platform=Windows// 新版使用,可将项目设置中的 Encryption Key 填入到json中
//json 格式为 { "EncryptionKey": {"Key": "tmAjE6/depliVQgG3XgI60bwrQE2iGgg5n8VRPXrGm0="} }
-encrypt -encryptindex -compress -cryptokeys=<Crypto.json>
接下来我尝试找到【指定了 “-enginedir=” 和 “-projectdir=” 就会使用 DefaultCrypto.ini 中的Key来作为密钥】所对应的源代码:
Engine\UE4\Source\Developer\PakFileUtilities\Private\PakFileUtilities.cpp 中的 void LoadKeyChain(const TCHAR* CmdLine, FKeyChain& OutCryptoSettings) 中的
void LoadKeyChain(const TCHAR* CmdLine, FKeyChain& OutCryptoSettings)
{
// ……if (FParse::Value(CmdLine, TEXT("projectdir="), ProjectDir, false)&& FParse::Value(CmdLine, TEXT("enginedir="), EngineDir, false)&& FParse::Value(CmdLine, TEXT("platform="), Platform, false)){UE_LOG(LogPakFile, Warning, TEXT("A legacy command line syntax is being used for crypto config. Please update to using the -cryptokey parameter as soon as possible as this mode is deprecated"));FConfigFile EngineConfig;FConfigCacheIni::LoadExternalIniFile(EngineConfig, TEXT("Engine"), *FPaths::Combine(EngineDir, TEXT("Config\\")), *FPaths::Combine(ProjectDir, TEXT("Config/")), true, *Platform);bool bDataCryptoRequired = false;EngineConfig.GetBool(TEXT("PlatformCrypto"), TEXT("PlatformRequiresDataCrypto"), bDataCryptoRequired);if (!bDataCryptoRequired) // 【MarkA】{return;}FConfigFile ConfigFile;FConfigCacheIni::LoadExternalIniFile(ConfigFile, TEXT("Crypto"), *FPaths::Combine(EngineDir, TEXT("Config\\")), *FPaths::Combine(ProjectDir, TEXT("Config/")), true, *Platform);bool bSignPak = false;// ……
(代码块1)
调试“加密生成Pak的过程”
"G:\TheTestingPak2.pak" -Create="{我的项目中的某个Pak命令}_PakCommands.txt" -AlignForMemoryMapping=0 -compress -encrypt -encryptindex -compressionformats=Oodle -encryptionini -projectdir="{我的项目目录}" -enginedir="G:\St\EngineSource" -platform=Android
如果不明白其中的PakCommands.txt的含义,请自行了解,它配置了“需要打包的资源”。
选择 UnrealPak 进行调试,并将 Programe Arguments改为上述指令。
一个小坎
断点断在“代码块1”处,发现在 MarkA 处就进不来了,bDataCryptoRequired 始终是 false,也就是 以下这个ini字段始终是false(或没配置)。
TEXT("PlatformCrypto"), TEXT("PlatformRequiresDataCrypto")
(图:一处阻碍,其中的 bDataCryptoRequired 始终是 false,暂不知道在哪里配置)
解决方法:根据 http://t.csdnimg.cn/RHylE 的介绍,如果我希望快速设置这个值,我需要得知其“Ini层级”,断点查看 EngineConfig.SourceIniHierarchy 得知,只需要创建一个 UserEngine.ini 如下图红框所示,并配置上该字段,如图2,再次运行,就能通过了 ( :D ) 。
[PlatformCrypto]
PlatformRequiresDataCrypto=true
(图2)
最后如愿以偿进入到了我们的目标代码,并能看到密钥:
走完整个过程,看到了日志输出:
LogPakFile: Display: Creating pak G:\TheTestingPak4.pak.
LogPakFile: Display: Using encryption key 'Default' [00000000000000000000000000000000]
LogPakFile: Display: CompressionFormats in priority order: Oodle, Zlib
LogDerivedDataCache: Warning: bShared: 0, data cache Path: ../../../Engine/DerivedDataCache
LogDerivedDataCache: Warning: CreateFileSystemDerivedDataBackend
LogPakFile: Display: CompressionFormat 0 [Oodle] : 0 files, 0 -> 0 bytes
LogPakFile: Display: CompressionFormat 1 [Zlib] : 0 files, 0 -> 0 bytes
LogPakFile: Display: CompressionFormat 2 [None] : 4 files, 27297821 -> 27297821 bytes
LogPakFile: Display: Added 4 files, 27298849 bytes total, time 0.19s.
LogPakFile: Display: PrimaryIndex size: 176 bytes
LogPakFile: Display: PathHashIndex size: 64 bytes
LogPakFile: Display: FullDirectoryIndex size: 320 bytes
LogPakFile: Display: Encryption - ENABLED
LogPakFile: Display: Files: 4
LogPakFile: Display: Index: Encrypted (176 bytes, 0.00MB)
LogPakFile: Display: Total: 27297856 bytes (26.03MB)
LogPakFile: Display: Unreal pak executed in 600.237102 seconds
尝试用UnrealPak.exe去验证一下这个新产生的包,发现List不出来,到这里,就能说明产生的Pak是加密的。
G:\>G:\St\{我的项目}\Engine\Binaries\Win64\UnrealPak.exe -List TheTestingPak.pak
LogTemp: Display: FCustomCryptoModule::StartupModule
LogPakFile: Display: Using command line for crypto configuration
LogWindows: Error: === Critical error: ===
LogWindows: Error:
LogWindows: Error: Fatal error: [File:D:/{我的项目}/EngineSource/Engine/Source/Runtime/PakFile/Private/IPlatformFilePak.cpp] [Line: 339]
LogWindows: Error: Failed to find requested encryption key 00000000000000000000000000000000
LogWindows: Error:
LogWindows: Error:
LogWindows: Error:
LogHeavyTaskMonitor: Warning: FHeavyTaskMonitor TaskKey GLog_TearDown ,TaskPath GLog_TearDown , TaskTime 37.61
FHeavyTaskMonitor TaskKey GLog_TearDown ,TaskPath GLog_TearDown , TaskTime 37.61
到这里,我们就知道如何使用AES加密Pak了,后面的内容是关于UnrealPak.exe解密的追加调试研究,非重点。
UnrealPak.exe无法解密出“已加密的Pak”
原本,我以为有了Key,就可以用UnrealPak.exe罗列出(“-List” 指令)或者解析出(“-Extract” 指令)已加密的Pak中的内容,但是我折腾了很久,我始终无法通过 UnrealPak.exe 的指令进行解密。始终会报错:
【命令行】
G:\>G:\St\DFMEditor\Engine\Binaries\Win64\UnrealPak.exe TheTestingPak.pak -Extract G:\{我的解压后目录} -cryptokey=G:\key.json 【具体内容见后文】
【输出】
LogTemp: Display: FCustomCryptoModule::StartupModule
LogPakFile: Display: Using command line for crypto configuration
LogWindows: Error: === Critical error: ===
LogWindows: Error:
LogWindows: Error: Fatal error: [File:D:/StableEditor/EngineSource/Engine/Source/Runtime/PakFile/Private/IPlatformFilePak.cpp] [Line: 339]
LogWindows: Error: Failed to find requested encryption key 00000000000000000000000000000000
LogWindows: Error:
LogWindows: Error:
LogWindows: Error:
LogHeavyTaskMonitor: Warning: FHeavyTaskMonitor TaskKey GLog_TearDown ,TaskPath GLog_TearDown , TaskTime 37.69
FHeavyTaskMonitor TaskKey GLog_TearDown ,TaskPath GLog_TearDown , TaskTime 37.69
G:\key.json的内容是:
{ "EncryptionKey": {
"Name": "null",
"Guid": "null",
"Key": "6A/h1s12rbKctPygBwBcw+Q7FaidXaWzfn/NE4IsjbA="
}
}
最终结论是:没有办法通过UnrealPak.exe直接解压出加密的Pak内容。我查询到的资料有:
1、如何在项目中而非用UnrealPak.exe 来进行解密:
I can't encrypt and load my external .pak at runtime - Programming & Scripting - Epic Developer Community Forums
2、主要EncryptionKey的GUID是 "Guid":"00000000-0000-0000-0000-000000000000",只有 SecondaryEncryptionKeys 才可能非零:
UE工具链配置与开发技巧 | 循迹研究室
3、 在UnrealPak.exe中主动重载 GUID
-encryptionkeyoverrideguid (override the encryption key guid used for encrypting data in this pak file)
https://www.cnblogs.com/shiroe/p/14803859.html
4、一个成功能够解密出加密内容的帖子,但是内容比较复杂,没有看出是怎么做到的
How I Extracted an Unreal Engine Game’s WWise Audio - Just Still5、"EncryptionKey" 中的 Guid 配置为"null"
https://github.com/allcoolthingsatoneplace/UnrealPakTool/blob/master/Readme.md
6、有人遇到同样问题 But it still using default keys
LogPakFile: Display: Using encryption key 'Default' [00000000000000000000000000000000]
https://gbatemp.net/threads/how-to-unpack-and-repack-unreal-engine-4-files.531784/page-14
相关代码是搜索 FNamedAESKey::Guid 的设值的地方(下图的红色的引用处),都是 Guid() 。
至于为什么游戏内可以成功解密出来用呢?现在我还不了解。