From f9ec0db18da70e4037b8b804bce162b213858f00 Mon Sep 17 00:00:00 2001 From: canbingzt Date: Thu, 11 Sep 2014 12:28:40 +0800 Subject: [PATCH 1/2] refactoring encryptor --- shadowsocks-csharp/Encrypt/EncryptorBase.cs | 29 ++++ .../Encrypt/EncryptorFactory.cs | 21 +++ shadowsocks-csharp/Encrypt/IEncryptor.cs | 12 ++ .../Encrypt/OpensslEncryptor.cs | 156 ++++++++++++++++++ shadowsocks-csharp/Encrypt/RC4Encryptor.cs | 81 +++++++++ shadowsocks-csharp/Encrypt/TableEncryptor.cs | 96 +++++++++++ shadowsocks-csharp/Local.cs | 24 ++- shadowsocks-csharp/shadowsocks-csharp.csproj | 7 + 8 files changed, 417 insertions(+), 9 deletions(-) create mode 100644 shadowsocks-csharp/Encrypt/EncryptorBase.cs create mode 100644 shadowsocks-csharp/Encrypt/EncryptorFactory.cs create mode 100644 shadowsocks-csharp/Encrypt/IEncryptor.cs create mode 100644 shadowsocks-csharp/Encrypt/OpensslEncryptor.cs create mode 100644 shadowsocks-csharp/Encrypt/RC4Encryptor.cs create mode 100644 shadowsocks-csharp/Encrypt/TableEncryptor.cs diff --git a/shadowsocks-csharp/Encrypt/EncryptorBase.cs b/shadowsocks-csharp/Encrypt/EncryptorBase.cs new file mode 100644 index 00000000..82ceeca8 --- /dev/null +++ b/shadowsocks-csharp/Encrypt/EncryptorBase.cs @@ -0,0 +1,29 @@ +using System.Security.Cryptography; +using System.Text; + +namespace shadowsocks_csharp.Encrypt +{ + public abstract class EncryptorBase + : IEncryptor + { + protected EncryptorBase(string method, string password) + { + Method = method; + Password = password; + } + + protected string Method; + protected string Password; + + protected byte[] GetPasswordHash() + { + byte[] inputBytes = Encoding.UTF8.GetBytes(Password); + byte[] hash = MD5.Create().ComputeHash(inputBytes); + return hash; + } + + public abstract byte[] Encrypt(byte[] buf, int length); + + public abstract byte[] Decrypt(byte[] buf, int length); + } +} diff --git a/shadowsocks-csharp/Encrypt/EncryptorFactory.cs b/shadowsocks-csharp/Encrypt/EncryptorFactory.cs new file mode 100644 index 00000000..430ce56b --- /dev/null +++ b/shadowsocks-csharp/Encrypt/EncryptorFactory.cs @@ -0,0 +1,21 @@ + +namespace shadowsocks_csharp.Encrypt +{ + public static class EncryptorFactory + { + public static IEncryptor GetEncryptor(string method, string password) + { + if (string.IsNullOrEmpty(method) || method.ToLowerInvariant() == "table") + { + return new TableEncryptor(method, password); + } + + if (method.ToLowerInvariant() == "rc4") + { + return new Rc4Encryptor(method, password); + } + + return new OpensslEncryptor(method, password); + } + } +} diff --git a/shadowsocks-csharp/Encrypt/IEncryptor.cs b/shadowsocks-csharp/Encrypt/IEncryptor.cs new file mode 100644 index 00000000..7675657a --- /dev/null +++ b/shadowsocks-csharp/Encrypt/IEncryptor.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace shadowsocks_csharp.Encrypt +{ + public interface IEncryptor + { + byte[] Encrypt(byte[] buf, int length); + byte[] Decrypt(byte[] buf, int length); + } +} diff --git a/shadowsocks-csharp/Encrypt/OpensslEncryptor.cs b/shadowsocks-csharp/Encrypt/OpensslEncryptor.cs new file mode 100644 index 00000000..a6d2c2e7 --- /dev/null +++ b/shadowsocks-csharp/Encrypt/OpensslEncryptor.cs @@ -0,0 +1,156 @@ +using OpenSSL.Core; +using OpenSSL.Crypto; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace shadowsocks_csharp.Encrypt +{ + public class OpensslEncryptor + : EncryptorBase, IDisposable + { + public OpensslEncryptor(string method, string password) + : base(method, password) + { + InitKey(Method, Password); + } + + public override byte[] Encrypt(byte[] buf, int length) + { + if (_encryptCtx == IntPtr.Zero) + { + int ivLen = _cipher.IVLength; + byte[] iv = new byte[ivLen]; + Native.RAND_bytes(iv, iv.Length); + InitCipher(ref _encryptCtx, iv, true); + int outLen = length + _cipher.BlockSize; + byte[] cipherText = new byte[outLen]; + Native.EVP_CipherUpdate(_encryptCtx, cipherText, out outLen, buf, length); + byte[] result = new byte[outLen + ivLen]; + Buffer.BlockCopy(iv, 0, result, 0, ivLen); + Buffer.BlockCopy(cipherText, 0, result, ivLen, outLen); + return result; + } + else + { + int outLen = length + _cipher.BlockSize; + byte[] cipherText = new byte[outLen]; + Native.EVP_CipherUpdate(_encryptCtx, cipherText, out outLen, buf, length); + byte[] result = new byte[outLen]; + Buffer.BlockCopy(cipherText, 0, result, 0, outLen); + return result; + } + } + + public override byte[] Decrypt(byte[] buf, int length) + { + if (_decryptCtx == IntPtr.Zero) + { + int ivLen = _cipher.IVLength; + byte[] iv = new byte[ivLen]; + Buffer.BlockCopy(buf, 0, iv, 0, ivLen); + InitCipher(ref _decryptCtx, iv, false); + int outLen = length + _cipher.BlockSize; + outLen -= ivLen; + byte[] cipherText = new byte[outLen]; + byte[] subset = new byte[length - ivLen]; + Buffer.BlockCopy(buf, ivLen, subset, 0, length - ivLen); + Native.EVP_CipherUpdate(_decryptCtx, cipherText, out outLen, subset, length - ivLen); + byte[] result = new byte[outLen]; + Buffer.BlockCopy(cipherText, 0, result, 0, outLen); + return result; + } + else + { + int outLen = length + _cipher.BlockSize; + byte[] cipherText = new byte[outLen]; + Native.EVP_CipherUpdate(_decryptCtx, cipherText, out outLen, buf, length); + byte[] result = new byte[outLen]; + Buffer.BlockCopy(cipherText, 0, result, 0, outLen); + return result; + } + } + + private static readonly Dictionary CachedKeys = new Dictionary(); + private static readonly Dictionary CachedCiphers = new Dictionary(); + private byte[] _key; + private Cipher _cipher; + private IntPtr _encryptCtx; + private IntPtr _decryptCtx; + + private void InitKey(string password, string method) + { + string k = method + ":" + password; + if (CachedKeys.ContainsKey(k)) + { + _key = CachedKeys[k]; + _cipher = CachedCiphers[k]; + return; + } + _cipher = Cipher.CreateByName(method); + if (_cipher == null) + { + throw new NullReferenceException(); + } + byte[] passbuf = Encoding.UTF8.GetBytes(password); + _key = new byte[_cipher.KeyLength]; + byte[] iv = new byte[_cipher.IVLength]; + Native.EVP_BytesToKey(_cipher.Handle, MessageDigest.MD5.Handle, null, passbuf, passbuf.Length, 1, _key, iv); + CachedKeys[k] = _key; + CachedCiphers[k] = _cipher; + } + + private void InitCipher(ref IntPtr ctx, byte[] iv, bool isCipher) + { + ctx = Native.OPENSSL_malloc(Marshal.SizeOf(typeof(CipherContext.EVP_CIPHER_CTX))); + int enc = isCipher ? 1 : 0; + Native.EVP_CIPHER_CTX_init(ctx); + Native.ExpectSuccess(Native.EVP_CipherInit_ex(ctx, _cipher.Handle, IntPtr.Zero, null, null, enc)); + Native.ExpectSuccess(Native.EVP_CIPHER_CTX_set_key_length(ctx, _key.Length)); + Native.ExpectSuccess(Native.EVP_CIPHER_CTX_set_padding(ctx, 1)); + Native.ExpectSuccess(Native.EVP_CipherInit_ex(ctx, _cipher.Handle, IntPtr.Zero, _key, iv, enc)); + } + + #region IDisposable + private bool _disposed; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~OpensslEncryptor() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + + } + + if (_encryptCtx != IntPtr.Zero) + { + Native.EVP_CIPHER_CTX_cleanup(_encryptCtx); + Native.OPENSSL_free(_encryptCtx); + _encryptCtx = IntPtr.Zero; + } + if (_decryptCtx != IntPtr.Zero) + { + Native.EVP_CIPHER_CTX_cleanup(_decryptCtx); + Native.OPENSSL_free(_decryptCtx); + _decryptCtx = IntPtr.Zero; + } + + _disposed = true; + } + } + #endregion + } +} diff --git a/shadowsocks-csharp/Encrypt/RC4Encryptor.cs b/shadowsocks-csharp/Encrypt/RC4Encryptor.cs new file mode 100644 index 00000000..03d41f20 --- /dev/null +++ b/shadowsocks-csharp/Encrypt/RC4Encryptor.cs @@ -0,0 +1,81 @@ + +namespace shadowsocks_csharp.Encrypt +{ + public class Rc4Encryptor + : EncryptorBase + { + public Rc4Encryptor(string method, string password) + : base(method, password) + { + byte[] hash = GetPasswordHash(); + _encryptTable = EncryptInitalize(hash); + _decryptTable = EncryptInitalize(hash); + } + + public override byte[] Encrypt(byte[] buf, int length) + { + return EncryptOutput(enc_ctx, _encryptTable, buf, length); + } + + public override byte[] Decrypt(byte[] buf, int length) + { + return EncryptOutput(dec_ctx, _decryptTable, buf, length); + } + + private readonly byte[] _encryptTable = new byte[256]; + private readonly byte[] _decryptTable = new byte[256]; + + private Context enc_ctx = new Context(); + private Context dec_ctx = new Context(); + + private byte[] EncryptOutput(Context ctx, byte[] s, byte[] data, int length) + { + byte[] result = new byte[length]; + for (int n = 0; n < length; n++) + { + byte b = data[n]; + + ctx.Index1 = (ctx.Index1 + 1) & 255; + ctx.Index2 = (ctx.Index2 + s[ctx.Index1]) & 255; + + Swap(s, ctx.Index1, ctx.Index2); + + result[n] = (byte)(b ^ s[(s[ctx.Index1] + s[ctx.Index2]) & 255]); + } + return result; + } + + private byte[] EncryptInitalize(byte[] key) + { + var s = new byte[256]; + + for (int i = 0; i < 256; i++) + { + s[i] = (byte)i; + } + + for (int i = 0, j = 0; i < 256; i++) + { + j = (j + key[i % key.Length] + s[i]) & 255; + + Swap(s, i, j); + } + + return s; + } + + private static void Swap(byte[] s, int i, int j) + { + byte c = s[i]; + + s[i] = s[j]; + s[j] = c; + } + + class Context + { + public int Index1; + public int Index2; + } + } +} diff --git a/shadowsocks-csharp/Encrypt/TableEncryptor.cs b/shadowsocks-csharp/Encrypt/TableEncryptor.cs new file mode 100644 index 00000000..47c2643b --- /dev/null +++ b/shadowsocks-csharp/Encrypt/TableEncryptor.cs @@ -0,0 +1,96 @@ +using System; + +namespace shadowsocks_csharp.Encrypt +{ + public class TableEncryptor + : EncryptorBase + { + public TableEncryptor(string method, string password) + : base(method, password) + { + byte[] hash = GetPasswordHash(); + // TODO endian + ulong a = BitConverter.ToUInt64(hash, 0); + for (int i = 0; i < 256; i++) + { + _encryptTable[i] = (byte)i; + } + for (int i = 1; i < 1024; i++) + { + _encryptTable = MergeSort(_encryptTable, a, i); + } + for (int i = 0; i < 256; i++) + { + _decryptTable[_encryptTable[i]] = (byte)i; + } + } + + public override byte[] Encrypt(byte[] buf, int length) + { + byte[] result = new byte[length]; + for (int i = 0; i < length; i++) + { + result[i] = _encryptTable[buf[i]]; + } + return result; + } + + public override byte[] Decrypt(byte[] buf, int length) + { + byte[] result = new byte[length]; + for (int i = 0; i < length; i++) + { + result[i] = _decryptTable[buf[i]]; + } + return result; + } + + private readonly byte[] _encryptTable = new byte[256]; + private readonly byte[] _decryptTable = new byte[256]; + + private static long Compare(byte x, byte y, ulong a, int i) + { + return (long)(a % (ulong)(x + i)) - (long)(a % (ulong)(y + i)); + } + + private byte[] MergeSort(byte[] array, ulong a, int j) + { + if (array.Length == 1) + { + return array; + } + int middle = array.Length / 2; + byte[] left = new byte[middle]; + for (int i = 0; i < middle; i++) + { + left[i] = array[i]; + } + byte[] right = new byte[array.Length - middle]; + for (int i = 0; i < array.Length - middle; i++) + { + right[i] = array[i + middle]; + } + left = MergeSort(left, a, j); + right = MergeSort(right, a, j); + + int leftptr = 0; + int rightptr = 0; + + byte[] sorted = new byte[array.Length]; + for (int k = 0; k < array.Length; k++) + { + if (rightptr == right.Length || ((leftptr < left.Length) && (Compare(left[leftptr], right[rightptr], a, j) <= 0))) + { + sorted[k] = left[leftptr]; + leftptr++; + } + else if (leftptr == left.Length || ((rightptr < right.Length) && (Compare(right[rightptr], left[leftptr], a, j)) <= 0)) + { + sorted[k] = right[rightptr]; + rightptr++; + } + } + return sorted; + } + } +} diff --git a/shadowsocks-csharp/Local.cs b/shadowsocks-csharp/Local.cs index 4af521b4..01d79ffd 100755 --- a/shadowsocks-csharp/Local.cs +++ b/shadowsocks-csharp/Local.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text; using System.Net.Sockets; using System.Net; +using shadowsocks_csharp.Encrypt; namespace shadowsocks_csharp { @@ -11,12 +12,12 @@ namespace shadowsocks_csharp class Local { private Config config; - private Encryptor encryptor; + //private Encryptor encryptor; Socket listener; public Local(Config config) { this.config = config; - this.encryptor = new Encryptor(config.method, config.password); + //this.encryptor = new Encryptor(config.method, config.password); } public void Start() @@ -62,11 +63,15 @@ namespace shadowsocks_csharp // Create the state object. Handler handler = new Handler(); handler.connection = conn; - if (encryptor.method == "table") { - handler.encryptor = encryptor; - } else { - handler.encryptor = new Encryptor(config.method, config.password); - } + //if (encryptor.method == "table") + //{ + // handler.encryptor = encryptor; + //} + //else + //{ + // handler.encryptor = new Encryptor(config.method, config.password); + //} + handler.encryptor = EncryptorFactory.GetEncryptor(config.method, config.password); handler.config = config; handler.Start(); @@ -83,7 +88,8 @@ namespace shadowsocks_csharp class Handler { - public Encryptor encryptor; + //public Encryptor encryptor; + public IEncryptor encryptor; public Config config; // Client socket. public Socket remote; @@ -140,7 +146,7 @@ namespace shadowsocks_csharp Console.WriteLine(e.ToString()); } } - encryptor.Dispose(); + //encryptor.Dispose(); } private void connectCallback(IAsyncResult ar) diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 1578c51a..93b1190f 100755 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -46,6 +46,7 @@ prompt 4 false + true pdbonly @@ -83,6 +84,12 @@ + + + + + + From 055424cc2e79763ea73bc9dab9946a6095503510 Mon Sep 17 00:00:00 2001 From: canbingzt Date: Thu, 11 Sep 2014 16:16:45 +0800 Subject: [PATCH 2/2] issues #11 --- shadowsocks-csharp/Local.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shadowsocks-csharp/Local.cs b/shadowsocks-csharp/Local.cs index 01d79ffd..1aa6ff90 100755 --- a/shadowsocks-csharp/Local.cs +++ b/shadowsocks-csharp/Local.cs @@ -54,6 +54,11 @@ namespace shadowsocks_csharp // Get the socket that handles the client request. Socket listener = (Socket)ar.AsyncState; + if (!listener.Connected) + { + return; + } + listener.BeginAccept( new AsyncCallback(AcceptCallback), listener);