Skip to content

Commit 5e43261

Browse files
authored
Merge pull request #350 from yacuzo/encryption-layer
AES Encryption sample
2 parents 0ab6ed8 + 14d0fb0 commit 5e43261

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

LibSample/AesEncryptLayer.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using LiteNetLib.Layers;
2+
using System;
3+
using System.Security.Cryptography;
4+
5+
namespace LibSample
6+
{
7+
/// <summary>
8+
/// Uses AES encryption in CBC mode. Make sure you handle your key properly.
9+
/// GCHandle.Alloc(key, GCHandleType.Pinned) to avoid your key being moved around different memory segments.
10+
/// ZeroMemory(gch.AddrOfPinnedObject(), key.Length); to erase it when you are done.
11+
/// Speed varies greatly depending on hardware encryption support.
12+
/// Why encrypt: http://ithare.com/udp-for-games-security-encryption-and-ddos-protection/
13+
/// </summary>
14+
public class AesEncryptLayer : PacketLayerBase
15+
{
16+
public const int KeySize = 256;
17+
public const int BlockSize = 128;
18+
public const int KeySizeInBytes = KeySize / 8;
19+
public const int BlockSizeInBytes = BlockSize / 8;
20+
21+
private readonly AesCryptoServiceProvider _aes;
22+
private ICryptoTransform _encryptor;
23+
private byte[] cipherBuffer = new byte[1500]; //Max possible UDP packet size
24+
private ICryptoTransform _decryptor;
25+
private byte[] ivBuffer = new byte[BlockSizeInBytes];
26+
27+
/// <summary>
28+
/// Should be safe against eavesdropping, but is vulnerable to tampering
29+
/// Needs a HMAC on top of the encrypted content to be fully safe
30+
/// </summary>
31+
/// <param name="key"></param>
32+
/// <param name="initializationVector"></param>
33+
public AesEncryptLayer(byte[] key) : base(BlockSizeInBytes * 2)
34+
{
35+
if (key.Length != KeySizeInBytes) throw new NotSupportedException("EncryptLayer only supports keysize " + KeySize);
36+
37+
//Switch this with AesGCM for better performance, requires .NET Core 3.0 or Standard 2.1
38+
_aes = new AesCryptoServiceProvider();
39+
_aes.KeySize = KeySize;
40+
_aes.BlockSize = BlockSize;
41+
_aes.Key = key;
42+
_aes.Mode = CipherMode.CBC;
43+
_aes.Padding = PaddingMode.PKCS7;
44+
45+
_encryptor = _aes.CreateEncryptor();
46+
_decryptor = _aes.CreateDecryptor();
47+
}
48+
49+
public override void ProcessInboundPacket(ref byte[] data, ref int length)
50+
{
51+
//Can't copy directly to _aes.IV. It won't work for some reason.
52+
Buffer.BlockCopy(data, 0, ivBuffer, 0, ivBuffer.Length);
53+
//_aes.IV = ivBuffer;
54+
_decryptor = _aes.CreateDecryptor(_aes.Key, ivBuffer);
55+
56+
//int currentRead = ivBuffer.Length;
57+
//int currentWrite = 0;
58+
59+
//TransformBlocks(_decryptor, data, length, ref currentRead, ref currentWrite);
60+
byte[] lastBytes = _decryptor.TransformFinalBlock(data, BlockSizeInBytes, length - BlockSizeInBytes);
61+
62+
data = lastBytes;
63+
length = lastBytes.Length;
64+
}
65+
66+
public override void ProcessOutBoundPacket(ref byte[] data, ref int offset, ref int length)
67+
{
68+
//Some Unity platforms may need these (and will be slower + generate garbage)
69+
if (!_encryptor.CanReuseTransform)
70+
{
71+
_aes.GenerateIV();
72+
_encryptor = _aes.CreateEncryptor();
73+
}
74+
75+
//Copy IV in plaintext to output, this is standard practice
76+
_aes.IV.CopyTo(cipherBuffer, 0);
77+
78+
int currentRead = offset;
79+
int currentWrite = _aes.IV.Length;
80+
byte[] lastBytes = _encryptor.TransformFinalBlock(data, currentRead, length - offset);
81+
lastBytes.CopyTo(cipherBuffer, currentWrite);
82+
//TransformBlocks(_encryptor, data, length, ref currentRead, ref currentWrite);
83+
84+
data = cipherBuffer;
85+
offset = 0;
86+
length = lastBytes.Length + BlockSizeInBytes;
87+
}
88+
89+
private void TransformBlocks(ICryptoTransform transform, byte[] input, int inputLength, ref int currentRead, ref int currentWrite)
90+
{
91+
//This loop produces a invalid padding exception
92+
//I'm leaving it here as a start point in case others need support for
93+
//Platforms wheere !transfom.CanTransformMultipleBlocks
94+
if (!transform.CanTransformMultipleBlocks)
95+
{
96+
while (inputLength - currentRead > BlockSizeInBytes)
97+
{
98+
int encryptedCount = transform.TransformBlock(input, currentRead, BlockSizeInBytes, cipherBuffer, currentWrite);
99+
currentRead += encryptedCount;
100+
currentWrite += encryptedCount;
101+
}
102+
}
103+
104+
byte[] lastBytes = transform.TransformFinalBlock(input, currentRead, inputLength - currentRead);
105+
lastBytes.CopyTo(cipherBuffer, currentWrite);
106+
currentWrite += lastBytes.Length;
107+
}
108+
}
109+
}

LibSample/AesEncryptionTest.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Linq;
3+
using System.Security.Cryptography;
4+
using System.Text;
5+
6+
namespace LibSample
7+
{
8+
class AesEncryptionTest
9+
{
10+
public void Run() => AesLayerEncryptDecrypt();
11+
12+
private void AesLayerEncryptDecrypt()
13+
{
14+
var keyGen = RandomNumberGenerator.Create();
15+
byte[] key = new byte[AesEncryptLayer.KeySizeInBytes];
16+
keyGen.GetBytes(key);
17+
const string testData = "This text is long enough to need multiple blocks to encrypt";
18+
19+
var outboudLayer = new AesEncryptLayer(key);
20+
byte[] outbound = Encoding.ASCII.GetBytes(testData);
21+
int lengthOfPacket = outbound.Length;
22+
int start = 0;
23+
int length = outbound.Length;
24+
outboudLayer.ProcessOutBoundPacket(ref outbound, ref start, ref length);
25+
26+
int minLenth = lengthOfPacket + AesEncryptLayer.BlockSizeInBytes;
27+
int maxLength = lengthOfPacket + outboudLayer.ExtraPacketSizeForLayer;
28+
if (length < minLenth || length > maxLength)
29+
{
30+
throw new Exception("Packet length out of bounds");
31+
}
32+
33+
var inboundLayer = new AesEncryptLayer(key);
34+
//Copy array so we dont read and write to same array
35+
byte[] inboundData = new byte[outbound.Length];
36+
outbound.CopyTo(inboundData, 0);
37+
inboundLayer.ProcessInboundPacket(ref inboundData, ref length);
38+
39+
Console.WriteLine(Encoding.ASCII.GetString(inboundData, 0, length));
40+
byte[] expectedPlaintext = Encoding.ASCII.GetBytes(testData);
41+
42+
var isEqualLength = expectedPlaintext.Length == length;
43+
var areContentEqual = expectedPlaintext.SequenceEqual(inboundData);
44+
if (isEqualLength && areContentEqual)
45+
{
46+
Console.WriteLine("Test complete");
47+
}
48+
else
49+
{
50+
throw new Exception("Test failed, decrypted data not equal to original");
51+
}
52+
}
53+
}
54+
}

LibSample/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ static void Main(string[] args)
2626
new SerializerBenchmark().Run();
2727
//new SpeedBench().Run();
2828
//new PacketProcessorExample().Run();
29+
//new AesEncryptionTest().Run();
2930
}
3031
}
3132
}

LiteNetLib/Layers/XorEncryptLayer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void SetKey(string key)
2929

3030
public void SetKey(byte[] key)
3131
{
32-
if(_byteKey.Length != key.Length)
32+
if (_byteKey == null || _byteKey.Length != key.Length)
3333
_byteKey = new byte[key.Length];
3434
Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length);
3535
}

0 commit comments

Comments
 (0)