Sep 20, 2021.Knowledge
Included with the Unreal Engine is the PacketHandler, a class which manages and maintains an array of packet HandlerComponents. The packet handler forwards incoming and outgoing packets to each of these handler components, with each component handling and modifying packets in their Incoming and Outgoing functions. These handler components can be used for a variety of purposes, such as implementing a stateless connection handshake, but one of the most common uses for these components is applying encryption to an Unreal project’s network traffic. This article will explain how to enable an encryption handler component, as well as how to set up a project to utilize encryption.
The encryption handler component is treated somewhat differently to other packet handler components, as the PacketHandler will keep a direct reference to the EncryptionHandler, which can be accessed using PacketHandler::GetEncryptionComponent. The engine currently includes several types of encryption components, including handlers for AES, AES-GCM, and DTLS encryption. It’s worth noting that the AES-GCM handler has seen the most use in production by us, and the DTLS handler is currently experimental.
To enable encryption, you’ll first need to enable the desired encryption handler plugin in your project as well as set the EncryptionComponent in the project’s *Engine.ini file(s). For example:
When travelling to a session, you’ll want to ensure that your client includes the "?EncryptionToken=” option on the travel URL. When the server receives this token, it will start the process of enabling encryption for the connection.
Next, UGameInstance contains two virtual functions that will need to be overridden in your project’s GameInstance class:
- This is called on the server after it receives a NMT_Hello control message with an EncryptionToken and is used for setting up the encryption key on the server, such as by retrieving a key from a secure external source like a web service.
- This is called on the client after it receives the EncryptionAck control message from the server and is used for retrieving the key on the client before the encrypted connection is finished being set up.
Both of these functions are passed a FOnEncryptionKeyResponse delegate as an argument, and any overrides must call these delegates as the last step after the key is known (even if the key is being retrieved asynchronously).
It’s worth noting that until these extra control messages are processed and UNetConnection::EnableEncryption is called, the connection flow’s control messages will be plaintext. For more information on how the networking system enables encryption during the connection process, you can look into UWorld::NotifyControlMessage and UPendingNetGame::NotifyControlMessage to see where the encryption control messages are handled on the client and server respectively. You can also check out UNetConnection::SendChallengeControlMessage(const FEncryptionKeyResponse& Response), the delegate passed into ReceivedNetworkEncryptionToken, and UPendingNetGame::FinalizeEncryptedConnection, the delegate passed into ReceivedNetworkEncryptionAck.
For an implementation example, the ShooterGame sample project provides examples for how to set the EncryptionToken URL option as well for overriding the GameInstance encryption functions. This code can be found in ShooterGameInstance.h/.cpp. To enable encryption in this project, the desired handler’s plugin should be enabled, and the EncryptionComponent should be set in DefaultEngine.ini as shown above. The “ShooterGame.TestEncryption” Cvar will then need to be enabled. It’s worth noting that the implementation in ShooterGame was created for the older AES handler. However, the interface for this handler and the DTLS and AES-GCM handlers are the same, and as of 4.27, the example code in ShooterGameInstance.cpp works with either component if using pre-shared keys.
However, one notable difference for the DTLS handler is that this component requires the third party cryptography library OpenSSL. At the time of writing this article, DTLS requires at least OpenSSL version 1.1.1, and some of the engine’s releases do not include version 1.1.1 for all platforms. Epic has an ongoing task to keep the version of OpenSSL included with the engine up to date, so if the engine version you’re working with does not have the needed OpenSSL version for the desired platform, you can retrieve the latest included versions of OpenSSL in Engine\Source\ThirdParty\OpenSSL from either the UE4 or UE5 Main streams. If you need a platform-specific version, these can be found in Engine\Platforms<Platform>\Source\ThirdParty\OpenSSL.