diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADNativeEncryptor.cs b/shadowsocks-csharp/Encryption/AEAD/AEADNativeEncryptor.cs new file mode 100644 index 00000000..ea4533a8 --- /dev/null +++ b/shadowsocks-csharp/Encryption/AEAD/AEADNativeEncryptor.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.Encryption.AEAD +{ + public class AEADNativeEncryptor : AEADEncryptor + { + public AEADNativeEncryptor(string method, string password) + : base(method, password) + { + } + + public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) + { + Array.Copy(ciphertext, plaintext, 0); + plen = clen; + + } + + public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) + { + Array.Copy(plaintext, ciphertext, 0); + clen = plen; + } + + public override void Dispose() + { + return; + } + + private static Dictionary _ciphers = new Dictionary() + { + {"plain-aeadfmt",new EncryptorInfo("PLAIN",0,0,0,0,0) } + }; + + + protected override Dictionary getCiphers() + { + return _ciphers; + } + + public static IEnumerable SupportedCiphers() + { + return _ciphers.Keys; + } + + } +} diff --git a/shadowsocks-csharp/Encryption/EncryptorFactory.cs b/shadowsocks-csharp/Encryption/EncryptorFactory.cs index fcae221a..92ed8008 100644 --- a/shadowsocks-csharp/Encryption/EncryptorFactory.cs +++ b/shadowsocks-csharp/Encryption/EncryptorFactory.cs @@ -28,6 +28,12 @@ namespace Shadowsocks.Encryption } // XXX: sequence matters, OpenSSL > Sodium > MbedTLS + foreach (string method in StreamNativeEncryptor.SupportedCiphers()) + { + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(StreamNativeEncryptor)); + } + foreach (string method in StreamOpenSSLEncryptor.SupportedCiphers()) { if (!_registeredEncryptors.ContainsKey(method)) @@ -46,6 +52,11 @@ namespace Shadowsocks.Encryption _registeredEncryptors.Add(method, typeof(StreamMbedTLSEncryptor)); } + foreach (string method in AEADNativeEncryptor.SupportedCiphers()) + { + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(AEADNativeEncryptor)); + } foreach (string method in AEADOpenSSLEncryptor.SupportedCiphers()) { diff --git a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs new file mode 100644 index 00000000..c51450ad --- /dev/null +++ b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.Encryption.Stream +{ + public class StreamNativeEncryptor : StreamEncryptor + { + const int Plain = 0; + const int Table = 1; + const int Rc4 = 2; + const int Rc4Md5 = 3; + + string _password; + + byte[] realkey; + byte[] sbox; + public StreamNativeEncryptor(string method, string password) : base(method, password) + { + _password = password; + } + + public override void Dispose() + { + return; + } + + protected override void initCipher(byte[] iv, bool isEncrypt) + { + base.initCipher(iv, isEncrypt); + if (_cipher >= Rc4) + { + if (_cipher == Rc4Md5) + { + byte[] temp = new byte[keyLen + ivLen]; + Array.Copy(_key, 0, temp, 0, keyLen); + Array.Copy(iv, 0, temp, keyLen, ivLen); + realkey = MbedTLS.MD5(temp); + } + else + { + realkey = _key; + } + sbox = SBox(realkey); + } + else if (_cipher == Table) + { + ulong a = BitConverter.ToUInt64(MbedTLS.MD5(Encoding.UTF8.GetBytes(_password)), 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; + } + } + } + + protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) + { + if (_cipher == Table) + { + var table = isEncrypt ? _encryptTable : _decryptTable; + for (int i = 0; i < length; i++) + { + outbuf[i] = table[buf[i]]; + } + } + else if (_cipher == Plain) + { + Array.Copy(buf, outbuf, length); + } + else + { + var ctx = isEncrypt ? enc_ctx : dec_ctx; + + byte[] t = new byte[length]; + Array.Copy(buf, t, length); + + RC4(ctx, sbox, t, length); + Array.Copy(t, outbuf, length); + } + } + + private static readonly Dictionary _ciphers = new Dictionary + { + {"plain", new EncryptorInfo("PLAIN", 0, 0, Plain) }, + {"table", new EncryptorInfo("TABLE", 0, 0, Table) }, + { "rc4", new EncryptorInfo("RC4", 16, 0, Rc4) }, // original RC4 doesn't use IV + { "rc4-md5", new EncryptorInfo("RC4", 16, 16, Rc4Md5) }, + }; + + public static IEnumerable SupportedCiphers() + { + return _ciphers.Keys; + } + + protected override Dictionary getCiphers() + { + return _ciphers; + } + + #region Table + private byte[] _encryptTable = new byte[256]; + private 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; + } + #endregion + + #region RC4 + class Context + { + public int index1 = 0; + public int index2 = 0; + } + + private Context enc_ctx = new Context(); + private Context dec_ctx = new Context(); + + private byte[] SBox(byte[] key) + { + byte[] 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 void RC4(Context ctx, byte[] s, byte[] data, int 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); + + data[n] = (byte)(b ^ s[(s[ctx.index1] + s[ctx.index2]) & 255]); + } + } + + private static void Swap(byte[] s, int i, int j) + { + byte c = s[i]; + + s[i] = s[j]; + s[j] = c; + } + #endregion + } +} diff --git a/shadowsocks-csharp/View/ConfigForm.cs b/shadowsocks-csharp/View/ConfigForm.cs index caab2f6d..6e9e27f0 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -31,6 +31,18 @@ namespace Shadowsocks.View "salsa20", "chacha20", "bf-cfb", + + "rc4", + "plain", + "table", + }; + private static string[] inuseMethod = new string[] + { + "aes-256-gcm", + "aes-192-gcm", + "aes-128-gcm", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", "chacha20-ietf", "aes-256-cfb", "aes-192-cfb", @@ -42,14 +54,7 @@ namespace Shadowsocks.View "camellia-192-cfb", "camellia-128-cfb", }; - private static string[] inuseMethod = new string[] - { - "aes-256-gcm", - "aes-192-gcm", - "aes-128-gcm", - "chacha20-ietf-poly1305", - "xchacha20-ietf-poly1305", - }; + public static EncryptionMethod[] AllMethods { get diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 31095fd6..4c5bbb56 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -120,6 +120,7 @@ + @@ -133,6 +134,7 @@ + diff --git a/test/CryptographyTest.cs b/test/CryptographyTest.cs index 1ad1cebe..64a5ea5e 100644 --- a/test/CryptographyTest.cs +++ b/test/CryptographyTest.cs @@ -239,7 +239,27 @@ namespace Shadowsocks.Test List threads = new List(); for (int i = 0; i < 10; i++) { - Thread t = new Thread(new ThreadStart(RunSingleBouncyCastleAEADEncryptionThread)); + Thread t = new Thread(new ThreadStart(RunSingleBouncyCastleAEADEncryptionThread));threads.Add(t); + t.Start(); + } + foreach (Thread t in threads) + { + t.Join(); + } + RNG.Close(); + Assert.IsFalse(encryptionFailed); + } + + [TestMethod] + public void TestNativeEncryption() + { + encryptionFailed = false; + // run it once before the multi-threading test to initialize global tables + RunSingleNativeEncryptionThread(); + List threads = new List(); + for (int i = 0; i < 10; i++) + { + Thread t = new Thread(new ThreadStart(RunSingleNativeEncryptionThread)); threads.Add(t); t.Start(); } @@ -251,6 +271,32 @@ namespace Shadowsocks.Test Assert.IsFalse(encryptionFailed); } + private void RunSingleNativeEncryptionThread() + { + try + { + for (int i = 0; i < 100; i++) + { + IEncryptor encryptorO, encryptorN, encryptorN2; + IEncryptor decryptorO, decryptorN, decryptorN2; + encryptorO = new StreamOpenSSLEncryptor("rc4-md5", "barfoo!"); + decryptorO = new StreamOpenSSLEncryptor("rc4-md5", "barfoo!"); + encryptorN = new StreamNativeEncryptor("rc4-md5", "barfoo!"); + encryptorN2 = new StreamNativeEncryptor("rc4-md5", "barfoo!"); + decryptorN = new StreamNativeEncryptor("rc4-md5", "barfoo!"); + decryptorN2 = new StreamNativeEncryptor("rc4-md5", "barfoo!"); + RunEncryptionRound(encryptorN, decryptorN); + RunEncryptionRound(encryptorO, decryptorN2); + RunEncryptionRound(encryptorN2, decryptorO); + } + } + catch + { + encryptionFailed = true; + throw; + } + } + private void RunSingleBouncyCastleAEADEncryptionThread() { try @@ -362,4 +408,4 @@ namespace Shadowsocks.Test } } } -} +} \ No newline at end of file