From a4503d22e1cc7bd7af512515dc4fabe622f9c2a0 Mon Sep 17 00:00:00 2001 From: lidgren Date: Sat, 16 Apr 2011 22:37:15 +0000 Subject: [PATCH] Net encryption generalized to support more algorithms --- Lidgren.Network/Encryption/INetEncryption.cs | 14 ++ .../Encryption/NetBlockEncryptionBase.cs | 78 ++++++++++ .../Encryption/NetXorEncryption.cs | 46 ++++++ .../Encryption/NetXteaEncryption.cs | 137 ++++++++++++++++++ Lidgren.Network/Lidgren.Network.csproj | 5 +- Lidgren.Network/NetBigInteger.cs | 2 +- Lidgren.Network/NetIncomingMessage.cs | 12 +- Lidgren.Network/NetOutgoingMessage.cs | 17 +-- Lidgren.Network/NetSRP.cs | 2 +- .../DurableSample/DurableSample.suo | Bin 38400 -> 41472 bytes UnitTests/EncryptionTests.cs | 67 ++++----- 11 files changed, 317 insertions(+), 63 deletions(-) create mode 100644 Lidgren.Network/Encryption/INetEncryption.cs create mode 100644 Lidgren.Network/Encryption/NetBlockEncryptionBase.cs create mode 100644 Lidgren.Network/Encryption/NetXorEncryption.cs create mode 100644 Lidgren.Network/Encryption/NetXteaEncryption.cs diff --git a/Lidgren.Network/Encryption/INetEncryption.cs b/Lidgren.Network/Encryption/INetEncryption.cs new file mode 100644 index 0000000..099cde0 --- /dev/null +++ b/Lidgren.Network/Encryption/INetEncryption.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + public interface INetEncryption + { + bool Encrypt(NetOutgoingMessage msg); + bool Decrypt(NetIncomingMessage msg); + } + + + +} diff --git a/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs b/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs new file mode 100644 index 0000000..625f73c --- /dev/null +++ b/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + /// + /// Base for a non-threadsafe encryption class + /// + public abstract class NetBlockEncryptionBase : INetEncryption + { + // temporary space for one block to avoid reallocating every time + private byte[] m_tmp; + + /// + /// Block size in bytes for this cipher + /// + public abstract int BlockSize { get; } + + public NetBlockEncryptionBase() + { + m_tmp = new byte[BlockSize]; + } + + /// + /// Encrypt am outgoing message with this algorithm; no writing can be done to the message after encryption, or message will be corrupted + /// + public bool Encrypt(NetOutgoingMessage msg) + { + int payloadBitLength = msg.LengthBits; + int numBytes = msg.LengthBytes; + int blockSize = BlockSize; + int numBlocks = (int)Math.Ceiling((double)numBytes / (double)blockSize); + int dstSize = numBlocks * blockSize; + + msg.EnsureBufferSize(dstSize * 8 + 2); // add 2 bytes for payload length at end + msg.LengthBits = dstSize * 8; // length will automatically adjust +4 bytes when payload length is written + + for(int i=0;i + /// Decrypt an incoming message encrypted with corresponding Encrypt + /// + /// message to decrypt + /// true if successful; false if failed + public bool Decrypt(NetIncomingMessage msg) + { + int numEncryptedBytes = msg.LengthBytes - 2; // last 2 bytes is true bit length + int blockSize = BlockSize; + int numBlocks = numEncryptedBytes / blockSize; + if (numBlocks * blockSize != numEncryptedBytes) + return false; + + for (int i = 0; i < numBlocks; i++) + { + DecryptBlock(msg.m_data, (i * blockSize), m_tmp); + Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length); + } + + // read 16 bits of true payload length + uint realSize = NetBitWriter.ReadUInt32(msg.m_data, 16, (numEncryptedBytes * 8)); + msg.m_bitLength = (int)realSize; + return true; + } + + protected abstract void EncryptBlock(byte[] source, int sourceOffset, byte[] destination); + protected abstract void DecryptBlock(byte[] source, int sourceOffset, byte[] destination); + } +} diff --git a/Lidgren.Network/Encryption/NetXorEncryption.cs b/Lidgren.Network/Encryption/NetXorEncryption.cs new file mode 100644 index 0000000..77d02b0 --- /dev/null +++ b/Lidgren.Network/Encryption/NetXorEncryption.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Example class; not very good encryption + /// + public class NetXorEncryption : INetEncryption + { + private byte[] m_key; + + public NetXorEncryption(byte[] key) + { + m_key = key; + } + + public NetXorEncryption(string key) + { + m_key = Encoding.ASCII.GetBytes(key); + } + + public bool Encrypt(NetOutgoingMessage msg) + { + int numBytes = msg.LengthBytes; + for (int i = 0; i < numBytes; i++) + { + int offset = i % m_key.Length; + msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + } + return true; + } + + public bool Decrypt(NetIncomingMessage msg) + { + int numBytes = msg.LengthBytes; + for (int i = 0; i < numBytes; i++) + { + int offset = i % m_key.Length; + msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + } + return true; + } + } +} diff --git a/Lidgren.Network/Encryption/NetXteaEncryption.cs b/Lidgren.Network/Encryption/NetXteaEncryption.cs new file mode 100644 index 0000000..8133202 --- /dev/null +++ b/Lidgren.Network/Encryption/NetXteaEncryption.cs @@ -0,0 +1,137 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Security; + +namespace Lidgren.Network +{ + /// + /// Methods to encrypt and decrypt data using the XTEA algorithm + /// + public sealed class NetXtea : NetBlockEncryptionBase + { + private const int c_blockSize = 8; + private const int c_keySize = 16; + private const int c_delta = unchecked((int)0x9E3779B9); + + private readonly int m_numRounds; + private readonly uint[] m_sum0; + private readonly uint[] m_sum1; + + public override int BlockSize { get { return c_blockSize; } } + + /// + /// 16 byte key + /// + public NetXtea(byte[] key, int rounds) + { + if (key.Length < c_keySize) + throw new NetException("Key too short!"); + + m_numRounds = rounds; + m_sum0 = new uint[m_numRounds]; + m_sum1 = new uint[m_numRounds]; + uint[] tmp = new uint[8]; + + int num2; + int index = num2 = 0; + while (index < 4) + { + tmp[index] = BitConverter.ToUInt32(key, num2); + index++; + num2 += 4; + } + for (index = num2 = 0; index < 32; index++) + { + m_sum0[index] = ((uint)num2) + tmp[num2 & 3]; + num2 += -1640531527; + m_sum1[index] = ((uint)num2) + tmp[(num2 >> 11) & 3]; + } + } + + /// + /// 16 byte key + /// + public NetXtea(byte[] key) + : this(key, 32) + { + } + + /// + /// String to hash for key + /// + public NetXtea(string key) + : this(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key)), 32) + { + } + + protected override void EncryptBlock(byte[] source, int sourceOffset, byte[] destination) + { + uint v0 = BytesToUInt(source, sourceOffset); + uint v1 = BytesToUInt(source, sourceOffset + 4); + + for (int i = 0; i != m_numRounds; i++) + { + v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i]; + v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i]; + } + + UIntToBytes(v0, destination, 0); + UIntToBytes(v1, destination, 0 + 4); + + return; + } + + protected override void DecryptBlock(byte[] source, int sourceOffset, byte[] destination) + { + // Pack bytes into integers + uint v0 = BytesToUInt(source, sourceOffset); + uint v1 = BytesToUInt(source, sourceOffset + 4); + + for (int i = m_numRounds - 1; i >= 0; i--) + { + v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i]; + v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i]; + } + + UIntToBytes(v0, destination, 0); + UIntToBytes(v1, destination, 0 + 4); + + return; + } + + private static uint BytesToUInt(byte[] bytes, int offset) + { + uint retval = (uint)(bytes[offset] << 24); + retval |= (uint)(bytes[++offset] << 16); + retval |= (uint)(bytes[++offset] << 8); + return (retval | bytes[++offset]); + } + + private static void UIntToBytes(uint value, byte[] destination, int destinationOffset) + { + destination[destinationOffset++] = (byte)(value >> 24); + destination[destinationOffset++] = (byte)(value >> 16); + destination[destinationOffset++] = (byte)(value >> 8); + destination[destinationOffset++] = (byte)value; + } + } +} diff --git a/Lidgren.Network/Lidgren.Network.csproj b/Lidgren.Network/Lidgren.Network.csproj index 1bccc09..c16f353 100644 --- a/Lidgren.Network/Lidgren.Network.csproj +++ b/Lidgren.Network/Lidgren.Network.csproj @@ -54,6 +54,10 @@ + + + + Code @@ -68,7 +72,6 @@ - diff --git a/Lidgren.Network/NetBigInteger.cs b/Lidgren.Network/NetBigInteger.cs index 6c7cfb8..241c883 100644 --- a/Lidgren.Network/NetBigInteger.cs +++ b/Lidgren.Network/NetBigInteger.cs @@ -1,8 +1,8 @@ using System; +using System.Text; using System.Collections; using System.Diagnostics; using System.Globalization; -using System.Text; namespace Lidgren.Network { diff --git a/Lidgren.Network/NetIncomingMessage.cs b/Lidgren.Network/NetIncomingMessage.cs index f10f326..de1e550 100644 --- a/Lidgren.Network/NetIncomingMessage.cs +++ b/Lidgren.Network/NetIncomingMessage.cs @@ -104,17 +104,9 @@ namespace Lidgren.Network m_isFragment = false; } - public void Decrypt(NetXtea tea) + public bool Decrypt(INetEncryption encryption) { - // requires blocks of 8 bytes - int blocks = m_bitLength / 64; - if (blocks * 64 != m_bitLength) - throw new NetException("Wrong message length for XTEA decrypt! Length is " + m_bitLength + " bits"); - - byte[] result = new byte[m_data.Length]; - for (int i = 0; i < blocks; i++) - tea.DecryptBlock(m_data, (i * 8), result, (i * 8)); - m_data = result; + return encryption.Decrypt(this); } /// diff --git a/Lidgren.Network/NetOutgoingMessage.cs b/Lidgren.Network/NetOutgoingMessage.cs index 2ed5a5b..b173a35 100644 --- a/Lidgren.Network/NetOutgoingMessage.cs +++ b/Lidgren.Network/NetOutgoingMessage.cs @@ -115,22 +115,11 @@ namespace Lidgren.Network } /// - /// Encrypt this message using the XTEA algorithm; no more writing can be done before sending it + /// Encrypt this message using the provided algorithm; no more writing can be done before sending it or the message will be corrupt! /// - public void Encrypt(NetXtea tea) + public bool Encrypt(INetEncryption encryption) { - // need blocks of 8 bytes - WritePadBits(); - int blocksNeeded = (m_bitLength + 63) / 64; - int missingBits = (blocksNeeded * 64) - m_bitLength; - int missingBytes = NetUtility.BytesToHoldBits(missingBits); - for (int i = 0; i < missingBytes; i++) - Write((byte)0); - - byte[] result = new byte[m_data.Length]; - for (int i = 0; i < blocksNeeded; i++) - tea.EncryptBlock(m_data, (i * 8), result, (i * 8)); - m_data = result; + return encryption.Encrypt(this); } /// diff --git a/Lidgren.Network/NetSRP.cs b/Lidgren.Network/NetSRP.cs index 01ba853..769663a 100644 --- a/Lidgren.Network/NetSRP.cs +++ b/Lidgren.Network/NetSRP.cs @@ -12,7 +12,7 @@ namespace Lidgren.Network public static class NetSRP { private static readonly NetBigInteger N = new NetBigInteger("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16); - private static readonly NetBigInteger g = new NetBigInteger("2"); + private static readonly NetBigInteger g = NetBigInteger.Two; private static readonly NetBigInteger k = ComputeMultiplier(); private static HashAlgorithm GetHashAlgorithm() diff --git a/Samples/LibraryTestSamples/DurableSample/DurableSample.suo b/Samples/LibraryTestSamples/DurableSample/DurableSample.suo index 0c1a75b0612f78b5c842ce9aff0dcd5bffb0f6dc..02f2f7d73631edd881be81dd6de2f7f783b71638 100644 GIT binary patch delta 1255 zcma)*TWAwO6ozM}v*{+;wCUB@q&3x8YcJp>QmZe$2+}s%ja38-B^qi4Q4|;NwNdM( z7Nz$1AW}gT#fM_G^DM>-iYRKe4>#w_C*gIX3ls1GiUbfZeo-r z9?IZ~d z#INi&rp~fg8ZKRP%qRlW)$XAb>6~d&H&PdpTpJapdy=WyHUehOOL)i^gPGg8;;=Ht zuq2hLW<(w+ZF%n{d5UGh!`A+9OdNz0*}7f~c<)hlHmWku0!F^BF$9_&n)&TkH=_V= z=f+IVv}}Q{l_4mwL!+?WL*$T)?9?pOwY)UQv=m0sOXi_6%ox6?5EwpemLYPrsOht= zB8=Ibvhgi&u%uP<%{URv5?zxO`S#w1U)O>&l*04}HRsN1LRL3*G_Qs7n%!`&vc^$% zBQl{RMY6If3vSnQcMoYHJBdNMNsl;G-N$GnO7|%pWF67P*V;M<{H)y%iIon>344__ zWIfpc_ru5G$^w_T86H_U9(zbD|MxwK&znd7SIt~vEdHEk;o0Iy_hUBaAK!q8Z@Wz4 z_Ssf3)Z3(rSG$bz@e2)<_QT?;zT!C9g9Cb>Tsm4|A5@-Nq{Jq1 z_hax89Sx4|Xn|PQTybl-gF;Mgu04wM82WK^`In!-^(6YKbUc9T8T3IJ--(XV^RW

M_p844*F{{T^S5wNY3-ofkHQPtMt8pYzE8u$%LIE0@Z_rT=pWphq$;&WEIV0OE=8g9oyAWWE6-Q{DU#C*^Je}j5I=mb@ zt7Jo6`rR_AsB>BiP9y85-IwEARuFa7&O7p;XBs*Bs0ddf=1^pXD{e<&RFLV8BrW3y z^01Fu(S03yRMyv3`WCLd3GSgnYq$#LJt(1ZttJ*Ov#UKa#w@B;h8xZxDk*oI3IC0i$wu^cp0TOP+0qQ< z3is%#S|QEMzLZ%;JyRId!l;YTfKFMbB|wCj_9 zmYS!fQI~I*ze7OvmJjcI#gP-fMlRod4OFLL`K#8} algos = new List(); - byte[] original = new byte[16]; - NetRandom.Instance.NextBytes(original); + algos.Add(new NetXorEncryption("TopSecret")); + algos.Add(new NetXtea("TopSecret")); - byte[] encrypted = new byte[original.Length]; - xtea.EncryptBlock(original, 0, encrypted, 0); - xtea.EncryptBlock(original, 8, encrypted, 8); + foreach (var algo in algos) + { + NetOutgoingMessage om = peer.CreateMessage(); + om.Write("Hallon"); + om.Write(42); + om.Write(5, 5); + om.Write(true); + om.Write("kokos"); + int trueLen = om.LengthBits; + om.Encrypt(algo); - byte[] decrypted = new byte[original.Length]; - xtea.DecryptBlock(encrypted, 0, decrypted, 0); - xtea.DecryptBlock(encrypted, 8, decrypted, 8); + // convert to incoming message + NetIncomingMessage im = Program.CreateIncomingMessage(om.PeekDataBuffer(), om.LengthBits); + im.Decrypt(algo); - // compare! - for (int i = 0; i < original.Length; i++) - if (original[i] != decrypted[i]) - throw new NetException("XTEA fail!"); + if (im.LengthBits != trueLen) + throw new NetException("Length fail"); - Console.WriteLine("XTEA OK"); + if (im.ReadString() != "Hallon") + throw new NetException("fail"); + if (im.ReadInt32() != 42) + throw new NetException("fail"); + if (im.ReadInt32(5) != 5) + throw new NetException("fail"); + if (im.ReadBoolean() != true) + throw new NetException("fail"); + if (im.ReadString() != "kokos") + throw new NetException("fail"); - NetOutgoingMessage om = peer.CreateMessage(); - om.Write("Hallon"); - om.Write(42); - om.Write(5, 5); - om.Write(true); - om.Write("kokos"); - om.Encrypt(xtea); - - // convert to incoming message - NetIncomingMessage im = Program.CreateIncomingMessage(om.PeekDataBuffer(), om.LengthBits); - im.Decrypt(xtea); - - if (im.ReadString() != "Hallon") - throw new NetException("fail"); - if (im.ReadInt32() != 42) - throw new NetException("fail"); - if (im.ReadInt32(5) != 5) - throw new NetException("fail"); - if (im.ReadBoolean() != true) - throw new NetException("fail"); - if (im.ReadString() != "kokos") - throw new NetException("fail"); + Console.WriteLine(algo.GetType().Name + " encryption verified"); + } for (int i = 0; i < 100; i++) {