首页 > 其他分享 >UE如何存储PAK文件的AES密码

UE如何存储PAK文件的AES密码

时间:2024-04-28 17:22:20浏览次数:27  
标签:&& AES string CryptoSettings CmdLine KEY UE IMPLEMENT PAK

Intro

UE的大部分content资产都放在了.PAK文件中,为了避免资产被破解,最好对文件进行加密。由于pak文件在运行时需要解密,所以运行时必然需要知道明文密码。或许是出于效率考虑,Unreal使用的是AES这种对称加密,也就是加密和解密使用的是相同的key。

如果把密码以明文的形式存储在文件系统中,那么既然能获得pak文件,也可以从安装包中获得密码文件,所以这个密码应该如何存储就是一个比较棘手的现实问题。

打包

在配置project时,可以在Project Settings>>Project>>Encryption中设置加密和签名配置,其中就包括了“Encryption Key”配置。

在执行工程打包的时候,编辑器会读取这里的配置,并根据配置的不同来选择设置打包命令(UnrealPak)的参数。

例如,下面的代码中可以看到对cryptokeys命令行参数的设置。

@file: Engine\Source\Programs\AutomationTool\Scripts\CopyBuildToStagingDirectory.Automation.cs
namespace AutomationScripts
{
	/// <summary>
	/// Helper command used for cooking.
	/// </summary>
	/// <remarks>
	/// Command line parameters used by this command:
	/// -clean
	/// </remarks>
	public partial class Project : CommandUtils
	{
///...
		/// <summary>
		/// Writes a pak response file to disk
		/// </summary>
		/// <param name="Filename"></param>
		/// <param name="ResponseFile"></param>
		private static void WritePakResponseFile(string Filename, Dictionary<string, string> ResponseFile, bool Compressed, bool RehydrateAssets, EncryptionAndSigning.CryptoSettings CryptoSettings, bool bForceFullEncryption)
		{
			using (var Writer = new StreamWriter(Filename, false, new System.Text.UTF8Encoding(true)))
			{
				foreach (var Entry in ResponseFile)
				{
					string Extension = Path.GetExtension(Entry.Key);
					string Line = String.Format("\"{0}\" \"{1}\"", Entry.Key, Entry.Value);

					// explicitly exclude some file types from compression
					if (Compressed && !Path.GetExtension(Entry.Key).Contains(".mp4") && !Extension.Contains("ushaderbytecode") && !Path.GetExtension(Entry.Key).Contains("upipelinecache"))
					{
						Line += " -compress";
					}

					// todo: Ideally we would know if the package is virtualized and only opt to rehydrate those packages, but we'd need to be able
					// to pipe that info this far.
					if(RehydrateAssets && (Extension.Contains(".uasset") || Extension.Contains(".umap")))
					{
						Line += " -rehydrate";
					}

					if (CryptoSettings != null)
					{
						bool bEncryptFile = bForceFullEncryption || CryptoSettings.bEnablePakFullAssetEncryption;
						bEncryptFile = bEncryptFile || (CryptoSettings.bEnablePakUAssetEncryption && Extension.Contains(".uasset"));
						bEncryptFile = bEncryptFile || (CryptoSettings.bEnablePakIniEncryption && Extension.Contains(".ini"));

						if (bEncryptFile)
						{
							Line += " -encrypt";
						}
					}

					Writer.WriteLine(Line);
				}
			}
		}

		/// <summary>
		/// Loads streaming install chunk manifest file from disk
		/// </summary>
		/// <param name="Filename"></param>
		/// <returns></returns>
		private static HashSet<string> ReadPakChunkManifest(string Filename)
		{
			var ResponseFile = ReadAllLines(Filename);
			var Result = new HashSet<string>(ResponseFile, StringComparer.InvariantCultureIgnoreCase);
			return Result;
		}

		private static string GetCommonUnrealPakArguments(List<OrderFile> PakOrderFileLocations, string AdditionalOptions, EncryptionAndSigning.CryptoSettings CryptoSettings, FileReference CryptoKeysCacheFilename, List<OrderFile> SecondaryPakOrderFileLocations, bool bUnattended)
		{
			StringBuilder CmdLine = new StringBuilder();
			if (CryptoKeysCacheFilename != null)
			{
				CmdLine.AppendFormat(" -cryptokeys={0}", CommandUtils.MakePathSafeToUseWithCommandLine(CryptoKeysCacheFilename.FullName));
			}
			if (PakOrderFileLocations != null && PakOrderFileLocations.Count() > 0)
			{
				CmdLine.AppendFormat(" -order={0}", CommandUtils.MakePathSafeToUseWithCommandLine(string.Join(",", PakOrderFileLocations.Select(u => u.File.FullName).ToArray())));
			}
			if (SecondaryPakOrderFileLocations != null && SecondaryPakOrderFileLocations.Count() > 0)
			{
				CmdLine.AppendFormat(" -secondaryOrder={0}", CommandUtils.MakePathSafeToUseWithCommandLine(string.Join(",", SecondaryPakOrderFileLocations.Select(u => u.File.FullName).ToArray())));
			}

			if (CryptoSettings != null && CryptoSettings.bDataCryptoRequired)
			{
				if (CryptoSettings.bEnablePakIndexEncryption)
				{
					CmdLine.AppendFormat(" -encryptindex");
				}
				if (CryptoSettings.bDataCryptoRequired && CryptoSettings.bEnablePakSigning && CryptoSettings.SigningKey.IsValid())
				{
					CmdLine.AppendFormat(" -sign");
				}
			}

			if (bUnattended)
			{
				// We don't want unrealpak popping up interactive dialogs while we're running a build
				CmdLine.AppendFormat(" -unattended");
			}

			CmdLine.Append(AdditionalOptions);

			return CmdLine.ToString();
		}

		static private string GetPakFileSpecificUnrealPakArguments(Dictionary<string, string> UnrealPakResponseFile, FileReference OutputLocation, string AdditionalOptions, bool Compressed, bool RehydrateAssets,  EncryptionAndSigning.CryptoSettings CryptoSettings, String PatchSourceContentPath, string EncryptionKeyGuid)
		{
			StringBuilder CmdLine = new StringBuilder(MakePathSafeToUseWithCommandLine(OutputLocation.FullName));

			// Force encryption of ALL files if we're using specific encryption key. This should be made an option per encryption key in the settings, but for our initial
			// implementation we will just assume that we require maximum security for this data.
			bool bForceEncryption = !string.IsNullOrEmpty(EncryptionKeyGuid);
			string PakName = Path.GetFileNameWithoutExtension(OutputLocation.FullName);
			string ResponseFilesPath = CombinePaths(CmdEnv.EngineSavedFolder, "ResponseFiles");
			InternalUtils.SafeCreateDirectory(ResponseFilesPath);
			string UnrealPakResponseFileName = CombinePaths(ResponseFilesPath, "PakList_" + PakName + ".txt");
			WritePakResponseFile(UnrealPakResponseFileName, UnrealPakResponseFile, Compressed, RehydrateAssets, CryptoSettings, bForceEncryption);
			CmdLine.AppendFormat(" -create={0}", CommandUtils.MakePathSafeToUseWithCommandLine(UnrealPakResponseFileName));

			if (!String.IsNullOrEmpty(PatchSourceContentPath))
			{
				CmdLine.AppendFormat(" -generatepatch={0} -tempfiles={1}", CommandUtils.MakePathSafeToUseWithCommandLine(PatchSourceContentPath), CommandUtils.MakePathSafeToUseWithCommandLine(CommandUtils.CombinePaths(CommandUtils.CmdEnv.LocalRoot, "TempFiles" + Path.GetFileNameWithoutExtension(OutputLocation.FullName))));
			}
			if (CryptoSettings != null && CryptoSettings.bDataCryptoRequired)
			{
				if (!string.IsNullOrEmpty(EncryptionKeyGuid))
				{
					CmdLine.AppendFormat(" -EncryptionKeyOverrideGuid={0}", EncryptionKeyGuid);
				}
			}

			return CmdLine.ToString();
		}

对应地,在打包程序中也包含了对于这些选项(例如cryptokeys)的解析。


void LoadKeyChain(const TCHAR* CmdLine, FKeyChain& OutCryptoSettings)
{
	OutCryptoSettings.SetSigningKey( InvalidRSAKeyHandle );
	OutCryptoSettings.GetEncryptionKeys().Empty();

	// First, try and parse the keys from a supplied crypto key cache file
	FString CryptoKeysCacheFilename;
	if (FParse::Value(CmdLine, TEXT("cryptokeys="), CryptoKeysCacheFilename))
	{
		UE_LOG(LogPakFile, Display, TEXT("Parsing crypto keys from a crypto key cache file"));
		KeyChainUtilities::LoadKeyChainFromFile(CryptoKeysCacheFilename, OutCryptoSettings);
	}
	else if (FParse::Param(CmdLine, TEXT("encryptionini")))
	{
		///...
	}
	else
	{
		UE_LOG(LogPakFile, Display, TEXT("Using command line for crypto configuration"));

		FString EncryptionKeyString;
		FParse::Value(CmdLine, TEXT("aes="), EncryptionKeyString, false);
	}

	FString EncryptionKeyOverrideGuidString;
	FGuid EncryptionKeyOverrideGuid;
	if (FParse::Value(CmdLine, TEXT("EncryptionKeyOverrideGuid="), EncryptionKeyOverrideGuidString))
	{
		FGuid::Parse(EncryptionKeyOverrideGuidString, EncryptionKeyOverrideGuid);
	}
	OutCryptoSettings.SetPrincipalEncryptionKey(OutCryptoSettings.GetEncryptionKeys().Find(EncryptionKeyOverrideGuid));
}

编译

这个可能相对比较隐晦,因为这些是通过宏定义实现的。

同样是在构建脚本中,读取Editor中的配置,然后定义了构建target时的定制化C++宏。这里我们关心的是

 ProjectDefinitions.Add(String.Format("IMPLEMENT_ENCRYPTION_KEY_REGISTRATION()=UE_REGISTER_ENCRYPTION_KEY({0})", FormatHexBytes(CryptoSettings.EncryptionKey!.Key!)));

这个宏定义。下面是相对完整的宏定义生成代码

///@file:Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs
          /// <summary>                                                           
          /// Constructor.                                                        
          /// </summary>                                                          
          /// <param name="Target">Information about the target being built</param>
          public TargetRules(TargetInfo Target)                                   
          {   
///...
              // Setup macros for signing and encryption keys                         
              EncryptionAndSigning.CryptoSettings CryptoSettings = EncryptionAndSigning.ParseCryptoSettings(CryptoSettingsDir, Platform, Logger);
              if (CryptoSettings.IsAnyEncryptionEnabled())                            
              {                                                                       
                ProjectDefinitions.Add(String.Format("IMPLEMENT_ENCRYPTION_KEY_REGISTRATION()=UE_REGISTER_ENCRYPTION_KEY({0})", FormatHexBytes(CryptoSettings.EncryptionKey!.Key!)));
              }                                                                   
              else                                                                    
              {                                                                       
                  ProjectDefinitions.Add("IMPLEMENT_ENCRYPTION_KEY_REGISTRATION()="); 
              }                                                                   
                                                                                      
              if (CryptoSettings.IsPakSigningEnabled())                               
              {                                                                                                                                                                            
>>                ProjectDefinitions.Add(String.Format("IMPLEMENT_SIGNING_KEY_REGISTRATION()=UE_REGISTER_SIGNING_KEY(UE_LIST_ARGUMENT({0}), UE_LIST_ARGUMENT({1}))", FormatHexBytes(CryptoS  ettings.SigningKey!.PublicKey.Exponent!), FormatHexBytes(CryptoSettings.SigningKey.PublicKey.Modulus!)));
              }                                                                   
              else                                                                    
              {                                                                       
                  ProjectDefinitions.Add("IMPLEMENT_SIGNING_KEY_REGISTRATION()=");    
              }                                                                   
          }                        

在ModuleManager.h中包含了IMPLEMENT_ENCRYPTION_KEY_REGISTRATION和UE_REGISTER_ENCRYPTION_KEY的宏定义。

主要是UE_REGISTER_ENCRYPTION_KEY函数内通过

const unsigned char Key[32] = { VA_ARGS };

将构建中宏定义的__VA_ARGS__将key保存在了局部变量Key[32] 中,并且可以通过Callback返回。

这意味着:在构建生成的可执行文件中包含了密码的明文

///@file:Engine\Source\Runtime\Core\Public\Modules\ModuleManager.h
#if IS_PROGRAM
/**
 * Macro for registering encryption key for a project.
 */
#define UE_REGISTER_ENCRYPTION_KEY(...) \
	struct FEncryptionKeyRegistration \
	{ \
		FEncryptionKeyRegistration() \
		{ \
			extern CORE_API void RegisterEncryptionKeyCallback(void (*)(unsigned char OutKey[32])); \
			RegisterEncryptionKeyCallback(&Callback); \
		} \
		static void Callback(unsigned char OutKey[32]) \
		{ \
			const unsigned char Key[32] = { __VA_ARGS__ }; \
			for(int ByteIdx = 0; ByteIdx < 32; ByteIdx++) \
			{ \
				OutKey[ByteIdx] = Key[ByteIdx]; \
			} \
		} \
	} GEncryptionKeyRegistration;
	
	
	#if IS_MONOLITHIC
		#define IMPLEMENT_APPLICATION( ModuleName, GameName ) \
			/* For monolithic builds, we must statically define the game's name string (See Core.h) */ \
			TCHAR GInternalProjectName[64] = TEXT( GameName ); \
			IMPLEMENT_FOREIGN_ENGINE_DIR() \
			IMPLEMENT_LIVE_CODING_ENGINE_DIR() \
			IMPLEMENT_LIVE_CODING_PROJECT() \
			IMPLEMENT_SIGNING_KEY_REGISTRATION() \
			IMPLEMENT_ENCRYPTION_KEY_REGISTRATION() \
			IMPLEMENT_GAME_MODULE(FDefaultGameModuleImpl, ModuleName) \
			PER_MODULE_BOILERPLATE \
			FEngineLoop GEngineLoop;

解包

前面提到在

///@file:Engine\Source\Runtime\Core\Private\Misc\CoreDelegates.cpp
CORE_API void RegisterEncryptionKeyCallback(TEncryptionKeyFunc InCallback)
{
	FCoreDelegates::GetPakEncryptionKeyDelegate().BindLambda([InCallback](uint8 OutKey[32])
	{
		InCallback(OutKey);
	});
}

在挂载pak文件时,会获得key的内容并用来解密。注意下面的FCoreDelegates::GetPakEncryptionKeyDelegate()和前面宏定义操作的是相同变量。

///@file:Engine\Source\Runtime\PakFile\Private\IPlatformFilePak.cpp
bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/, bool bLoadIndex /*= true*/)
{
///...
		if (bPakSuccess && IoDispatcherFileBackend.IsValid())
		{
			FGuid EncryptionKeyGuid = Pak->GetInfo().EncryptionKeyGuid;
			FAES::FAESKey EncryptionKey;

			if (!GetRegisteredEncryptionKeys().GetKey(EncryptionKeyGuid, EncryptionKey))
			{
				if (!EncryptionKeyGuid.IsValid() && FCoreDelegates::GetPakEncryptionKeyDelegate().IsBound())
				{
					FCoreDelegates::GetPakEncryptionKeyDelegate().Execute(EncryptionKey.Key);
				}
			}

			FString UtocPath = FPaths::ChangeExtension(InPakFilename, TEXT(".utoc"));
///...
}

Outro

尽管加密PAK文件可以提升资产的安全性,但是在能够反汇编可执行文件的前提下,这种安全的提升其实并不高。例如这篇How to extract decryption key for Unreal Engine 4 *.pak files文件就是简单的搜索特定指令,并获得保存在程序中的密码。

标签:&&,AES,string,CryptoSettings,CmdLine,KEY,UE,IMPLEMENT,PAK
From: https://www.cnblogs.com/tsecer/p/18164136

相关文章

  • [转]<a>标签超链接跳转到第三方系统提示:The Http request is not acceptable for the
    原文地址:TheHttprequestisnotacceptablefortherequestedresource.-CSDN博客1.问题描述在做一个点击本系统的一个按钮打开第三方链接并跳转新页面,跳转过去的第三方链接由https://ip地址组成,报以下错:TheHttprequestisnotacceptablefortherequestedresource.2.......
  • ue4.26 通过材质开关控制mesh pass的blend function
    一,meshpass中blendfunction的设置方法在meshpass中设置blendfunction有如下几种方式:1,在CreateXXXProcessor(返回FXXXProcessor)中: 2,FXXXProcessor::AddMeshBatch中: 3,FXXXProcessor::Process中: 4,RenderXXX中: 二,材质开关访问途径我们知道,访问材质开关有以下几种......
  • vue3 引入workers 大量优化业务代码和复杂的计算的代码
    前沿vite页面引入worker在src新建一个 worker.d.ts文件declaremodule'*.worker.ts'{classWebpackWorkerextendsWorker{constructor();}exportdefaultWebpackWorker;}在 tsconfig.json页面引入"lib":["esnext",......
  • 『手撕Vue-CLI』添加自定义指令
    前言经上篇『手撕Vue-CLI』添加帮助和版本号的介绍之后,已经可以在控制台中输入nue--help来查看帮助信息了,但是在帮助信息中只有--version,--help这两个指令,而vue-cli中还有很多指令,例如create,serve,build等等,所以本章将继续添加自定义指令,例如create指令。添加create......
  • [Paper Reading] DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D
    名称DETR3D:3DObjectDetectionfromMulti-viewImagesvia3D-to-2DQueries时间:21.10机构:mit/CMU/StanfordTL;DR一种利用Transformer做E2E的3D目标检测方法,在nuScenes自动驾驶数据集上取得很好效果。Method主要创新点在于2D-to-3DFeatureTransforms模块,细节如图描......
  • CF1966D Missing Subsequence Sum 题解
    题意:给定\(n(n\le10^6)\)和\(k(k\len)\)。构造一个长度小于等于\(25\)的序列\(a\)满足:1.不存在一个子序列的和为\(k\)。2.对于\(1\lei\len,i\nek\),存在一个子序列的和为\(i\)。看到长度为\(25\),首先肯定会想到二进制。那么我们先构造出一个序列\([2^......
  • Vue系列---【如何关闭eslint校验?】
    如何关闭eslint校验?1.如果你的项目集成了eslint,但校验太严格,导致项目启动不了,你没时间排错,你可以找到vue.config.js,没有就创建,配上下面的内容。module.exports={//关闭eslint,因为校验太严格,例如:在main.js里定义了一个变量leta=100;,但未使用,就会导致项目启动不了......
  • 响应式原理(Vue3、Vue2)
    1.Vue3副作用函数(onMounted、watchEffect)帮助管理组件中的副作用逻辑,并自动追踪其依赖关系,以确保在数据变化时能够自动触发副作用函数的更新。会自动追踪被其内部函数引用的响应式数据。当这些数据发生变化时,Vue3会自动重新运行副作用函数,确保副作用与数据的状态保持同步。......
  • ant design pro vue项目搭建-运行项目
    1、克隆代码gitclone--depth=1https://github.com/vueComponent/ant-design-vue-pro.git2、依赖安装npminstall提示eslint版本报错  去除eslint,将package.json中eslint相关配置删除3、重新安装依赖完成,没有报错 npminstall 4、启动项目 npmrun......
  • SpringMVC(1)-@RequestMapping的简单使用
    本文核心内容来自于韩顺平老师的课程@RequestMapping注解可以用来指定控制器或者处理器的某个方法的请求url@ControllerpublicclassUserServlet{@RequestMapping("/login")publicStringlogin(){return"login";}}1@RequestMappi......