Version: 2020.2
NetworkServerSimple
UnityWebRequest

Multiplayer 加密插件

Unity Multiplayer 可以使用加密插件,以便通过网络发送的所有数据先通过加密插件,然后再进行发送。这样可以防止通过数据包操纵和攻击专用游戏服务器的方式进行游戏作弊。

Unity Multiplayer 没有内置的加密插件,因此您必须提供自己的插件来实现加密算法并实现下文进一步列出的必需功能。

下图说明了 Unity Multiplayer 如何使用您的加密插件(如果您提供了该插件)。

将加密插件与 Unity Multiplayer 配合使用时的数据流
将加密插件与 Unity Multiplayer 配合使用时的数据流

如何使用加密插件

为了指示游戏或应用程序使用加密插件,必须调用 UnityEngine.Networking.NetworkTransport.LoadEncryptionLibrary(path),其中 path 是所编译插件的路径。在 Windows 中,此路径一般为:string.Format("{0}/Plugins/UnetEncryption.dll", Application.dataPath)

调用此函数时,Unity 会检查该文件是否存在以及它是否实现了所有必需功能(在下文列出)。这些是 Unity Multiplayer 系统本身将调用的函数。如果您创建自己的加密插件,可能需要添加从 C# 代码调用的更多函数。例如,需要将算法初始化或为插件提供密钥值。可以按照通常的方式对可从 C# 调用的原生插件执行此操作。

注意:在游戏的构建版本中,插件的位置不一定与 Assets 文件夹中的位置相同,并且该位置在目标平台之间可能不同。您可能需要编写代码来检测当前的运行时环境,并根据检测结果选择正确路径。

可从 Unity GitHub 获得示例加密插件和示例 Unity 项目。提供这些示例是为了展示如何实现您自己的插件。

必需函数

您创建或使用的任何加密插件都必须提供以下函数。如果 Unity 未定义这些函数,它将无法加载插件。这些是 Unity 运行时本身将调用的函数。插件通常会提供_额外_函数,以便从用户的 C# 代码调用这些函数来实现多种目的,比如注册密钥。

对数据进行加密

int UNetEncryptionLib_Encrypt(
    void * payload,
    int payload_len,
    void * dest,
    int & dest_len,
    int connection_id,
    bool isConnect);

此函数执行加密任务。每当通过网络发送数据包时,Unity 的网络功能都将调用此函数。

参数

  • payload 是要加密的数据。
  • payload_len 是 payload 缓冲区的长度(以字节为单位)。
  • dest 是插件应将加密数据写入到的缓冲区。
  • dest_len 是 dest 缓冲区的容量(以字节为单位)。插件必须将此值替换为_实际写入_ dest 的字节数。
  • connection_id 是该连接的本地标识符。
  • 如果此数据包是连接请求,则 isConnect 为 true。如果此参数为 true,必须提前(通过游戏代码)告知插件要使用哪个密钥。如果此参数为 false,插件应该已经具有从该值到要使用的密钥之间的映射。请参阅示例插件以了解实现方式。

返回值

成功时,Encrypt 必须返回零。如果返回任何其他值,运行时将丢弃数据包而不发送。

对数据进行解密

int UNetEncryptionLib_Decrypt(
    void * payload,
    int payload_len,
    void * dest,
    int & dest_len,
    int & key_id);

此函数执行解密任务。每当从网络接收数据包时,Unity 的网络功能都将调用此函数。

参数

  • payload 是接收的数据包。
  • payload_len 是 payload 缓冲区的长度(以字节为单位)。
  • dest 是插件应将解密数据写入到的缓冲区。
  • dest_len 是 dest 缓冲区的容量(以字节为单位)。插件必须将此值替换为_实际写入_ dest 的字节数。
  • key_id 是一个整数标识符。插件应编写一个值来唯一标识所使用的解密密钥。在服务器上,如果接受新连接,则将该值传回到 ConnectionIdAssigned

返回值

成功时,Decrypt 必须返回零。如果返回任何其他值,将丢弃数据包而不做进一步处理。

SafeMaxPacketSize

unsigned short UNetEncryptionLib_SafeMaxPacketSize(
    unsigned short mtu);

应该从游戏调用此函数以修改 ConnectionConfig.PacketSize(也称为_最大传输单元_,即 MTU),然后调用 NetworkTransport.AddHost

例如,您的游戏可能通常使用 1000 字节的 MTU。如果 ConnectionConfig.PacketSize 设置为 1000 字节,然后将其传递到 NetworkTransport.AddHost(通过 HostConfig.DefaultConfig),则 NetworkTransport 层会通过单个数据包发送不超过 1000 字节的明文。

加密插件通常会由于放置在有效负载之前的标头信息以及将有效负载舍入为加密块大小而增加一些开销。例如,如果您要发送 18 字节的明文,并且插件需要添加 49 字节的标头并使用 AES 来加密块大小为 16 字节的数据,则该算法将产生一个 81 字节的数据包(18 字节的明文字节将舍入为 32 字节的密文,然后再添加 49 字节的标头)。

Unity 会调用此函数来确保即将发送的数据包不会超出允许的发送限制(需要考虑网络 MTU 和加密算法的密文扩展和填充)。

参数

  • mtu 是最大传输单元。这是您希望插件生成的最大数据包大小。

返回值

为了使插件生成不大于 MTU 的数据包,应该向单次 Encrypt 调用提供的最大明文量。

必须在连接配置中设置最大数据包大小,从而告诉 Unity Multiplayer 拆分数据,使数据符合您的加密要求。如果您发现自己的某些消息无法通过网络成功传输,可能是由于这些消息超出最大数据包大小而被丢弃。

ConnectionIdAssigned

void UNetEncryptionLib_ConnectionIdAssigned(
    int key_id,
    unsigned short connection_id);

已接受新连接并向其分配 ID 后将在服务器上调用此函数。

参数

  • key_id 是由该数据包对应的先前 Decrypt 调用写入的密钥标识符。
  • connection_id 是从现在开始将使用的连接 ID。具体而言,在将数据包发送回客户端时,作为后续 Encrypt 调用的参数。
NetworkServerSimple
UnityWebRequest