From 5d658fc8415112e4e6f6f57f2bdeca75cbe50cc7 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 14 Feb 2020 20:51:15 +0800 Subject: [PATCH 1/4] bring native rc4 back, along with rc4-md5 partially revert 421554f8ecda46026aa0a083eee582cfbdbfb787 --- .../Stream/StreamNativeEncryptor.cs | 111 ++++++++++++++++++ shadowsocks-csharp/shadowsocks-csharp.csproj | 1 + test/CryptographyTest.cs | 50 +++++++- 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs diff --git a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs new file mode 100644 index 00000000..465e316f --- /dev/null +++ b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.Encryption.Stream +{ + public class StreamNativeEncryptor : StreamEncryptor + { + bool md5; + byte[] realkey; + byte[] sbox; + public StreamNativeEncryptor(string method, string password) : base(method, password) + { + md5 = method.ToLowerInvariant().IndexOf("md5") >= 0; + } + + public override void Dispose() + { + return; + } + + protected override void initCipher(byte[] iv, bool isEncrypt) + { + base.initCipher(iv, isEncrypt); + if (md5) + { + 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 = EncryptInitalize(realkey); + } + + protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) + { + var ctx = isEncrypt ? enc_ctx : dec_ctx; + + byte[] t = new byte[length]; + Array.Copy(buf, t, length); + + EncryptOutput(ctx, sbox, t, length); + Array.Copy(t, outbuf, length); + } + + protected override Dictionary getCiphers() + { + return new Dictionary() + { + { "rc4-md5", new EncryptorInfo("rc4", 16, 16, 1) } + }; + } + + class Context + { + public int index1 = 0; + public int index2 = 0; + } + + private Context enc_ctx = new Context(); + private Context dec_ctx = new Context(); + + private byte[] EncryptInitalize(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 EncryptOutput(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; + } + } +} diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index bf562337..5cbc4d78 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -129,6 +129,7 @@ + diff --git a/test/CryptographyTest.cs b/test/CryptographyTest.cs index 68c2e3a8..1b2f98ae 100644 --- a/test/CryptographyTest.cs +++ b/test/CryptographyTest.cs @@ -229,5 +229,53 @@ namespace Shadowsocks.Test throw; } } + + [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(); + } + foreach (Thread t in threads) + { + t.Join(); + } + RNG.Close(); + Assert.IsFalse(encryptionFailed); + } + + private void RunSingleNativeEncryptionThread() + { + try + { + for (int i = 0; i < 100; i++) + { + var random = new Random(); + 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; + } + } } -} +} \ No newline at end of file From 5280c580d02624ad29e6b19032fedcbd843b0b53 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 14 Feb 2020 20:54:54 +0800 Subject: [PATCH 2/4] register native rc4 --- .../Encryption/EncryptorFactory.cs | 6 ++++++ .../Encryption/Stream/StreamNativeEncryptor.cs | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/shadowsocks-csharp/Encryption/EncryptorFactory.cs b/shadowsocks-csharp/Encryption/EncryptorFactory.cs index fcae221a..79715149 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)) diff --git a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs index 465e316f..ecc72535 100644 --- a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs +++ b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs @@ -49,12 +49,22 @@ namespace Shadowsocks.Encryption.Stream Array.Copy(t, outbuf, length); } + private static readonly Dictionary _ciphers = new Dictionary + { + { "rc4", new EncryptorInfo("RC4", 16, 16, 1) }, + { "rc4-md5", new EncryptorInfo("RC4", 16, 16, 1) }, + // it's using ivLen=16, not compatible + //{ "chacha20-ietf", new EncryptorInfo("chacha20", 32, 12, CIPHER_CHACHA20_IETF) } + }; + + public static IEnumerable SupportedCiphers() + { + return _ciphers.Keys; + } + protected override Dictionary getCiphers() { - return new Dictionary() - { - { "rc4-md5", new EncryptorInfo("rc4", 16, 16, 1) } - }; + return _ciphers; } class Context From 695716cd299c970748d0af8f42a07c6c87f655c6 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 14 Feb 2020 22:38:01 +0800 Subject: [PATCH 3/4] bring back table, add plain revert d24548a04f4701dfe9f3e07d6305b5ba3b7a56f8 --- .../Stream/StreamNativeEncryptor.cs | 134 +++++++++++++++--- shadowsocks-csharp/View/ConfigForm.cs | 4 + 2 files changed, 117 insertions(+), 21 deletions(-) diff --git a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs index ecc72535..f30bedd4 100644 --- a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs +++ b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs @@ -8,12 +8,18 @@ namespace Shadowsocks.Encryption.Stream { public class StreamNativeEncryptor : StreamEncryptor { - bool md5; + 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) { - md5 = method.ToLowerInvariant().IndexOf("md5") >= 0; + _password = password; } public override void Dispose() @@ -24,37 +30,71 @@ namespace Shadowsocks.Encryption.Stream protected override void initCipher(byte[] iv, bool isEncrypt) { base.initCipher(iv, isEncrypt); - if (md5) + if (_cipher >= Rc4) { - byte[] temp = new byte[keyLen + ivLen]; - Array.Copy(_key, 0, temp, 0, keyLen); - Array.Copy(iv, 0, temp, keyLen, ivLen); - realkey = MbedTLS.MD5(temp); + 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 + else if (_cipher == Table) { - realkey = _key; + 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; + } } - sbox = EncryptInitalize(realkey); } protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) { - var ctx = isEncrypt ? enc_ctx : dec_ctx; + 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); + byte[] t = new byte[length]; + Array.Copy(buf, t, length); - EncryptOutput(ctx, sbox, t, length); - Array.Copy(t, outbuf, length); + RC4(ctx, sbox, t, length); + Array.Copy(t, outbuf, length); + } } private static readonly Dictionary _ciphers = new Dictionary { - { "rc4", new EncryptorInfo("RC4", 16, 16, 1) }, - { "rc4-md5", new EncryptorInfo("RC4", 16, 16, 1) }, - // it's using ivLen=16, not compatible - //{ "chacha20-ietf", new EncryptorInfo("chacha20", 32, 12, CIPHER_CHACHA20_IETF) } + {"plain", new EncryptorInfo("PLAIN", 0, 0, Plain) }, + {"table", new EncryptorInfo("TABLE", 0, 0, Table) }, + { "rc4", new EncryptorInfo("RC4", 16, 16, Rc4) }, + { "rc4-md5", new EncryptorInfo("RC4", 16, 16, Rc4Md5) }, }; public static IEnumerable SupportedCiphers() @@ -67,6 +107,57 @@ namespace Shadowsocks.Encryption.Stream 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; @@ -76,7 +167,7 @@ namespace Shadowsocks.Encryption.Stream private Context enc_ctx = new Context(); private Context dec_ctx = new Context(); - private byte[] EncryptInitalize(byte[] key) + private byte[] SBox(byte[] key) { byte[] s = new byte[256]; @@ -95,7 +186,7 @@ namespace Shadowsocks.Encryption.Stream return s; } - private void EncryptOutput(Context ctx, byte[] s, byte[] data, int length) + private void RC4(Context ctx, byte[] s, byte[] data, int length) { for (int n = 0; n < length; n++) { @@ -117,5 +208,6 @@ namespace Shadowsocks.Encryption.Stream s[i] = s[j]; s[j] = c; } + #endregion } } diff --git a/shadowsocks-csharp/View/ConfigForm.cs b/shadowsocks-csharp/View/ConfigForm.cs index 1d94ec79..9a681cfb 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -31,6 +31,10 @@ namespace Shadowsocks.View "salsa20", "chacha20", "bf-cfb", + + "rc4", + "plain", + "table", }; private static string[] inuseMethod = new string[] { From edd723de49022fcda6c176702bbf45e1b3ad49ea Mon Sep 17 00:00:00 2001 From: Student Main Date: Sat, 15 Feb 2020 01:14:47 +0800 Subject: [PATCH 4/4] rc4 doesn't use iv and plain cipher for aead for fun --- .../Encryption/AEAD/AEADNativeEncryptor.cs | 51 +++++++++++++++++++ .../Encryption/EncryptorFactory.cs | 5 ++ .../Stream/StreamNativeEncryptor.cs | 2 +- shadowsocks-csharp/shadowsocks-csharp.csproj | 1 + 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 shadowsocks-csharp/Encryption/AEAD/AEADNativeEncryptor.cs 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 79715149..92ed8008 100644 --- a/shadowsocks-csharp/Encryption/EncryptorFactory.cs +++ b/shadowsocks-csharp/Encryption/EncryptorFactory.cs @@ -52,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 index f30bedd4..c51450ad 100644 --- a/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs +++ b/shadowsocks-csharp/Encryption/Stream/StreamNativeEncryptor.cs @@ -93,7 +93,7 @@ namespace Shadowsocks.Encryption.Stream { {"plain", new EncryptorInfo("PLAIN", 0, 0, Plain) }, {"table", new EncryptorInfo("TABLE", 0, 0, Table) }, - { "rc4", new EncryptorInfo("RC4", 16, 16, Rc4) }, + { "rc4", new EncryptorInfo("RC4", 16, 0, Rc4) }, // original RC4 doesn't use IV { "rc4-md5", new EncryptorInfo("RC4", 16, 16, Rc4Md5) }, }; diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 5cbc4d78..64cf5a2d 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -116,6 +116,7 @@ +