From a7e5fcde5147546b4840baf601cff958b245a781 Mon Sep 17 00:00:00 2001 From: Student Main Date: Thu, 26 Mar 2020 01:58:02 +0800 Subject: [PATCH] buggy aes-cfb with bouncycastle --- .../Controller/Service/UpdateChecker.cs | 7 ++ shadowsocks-csharp/Encryption/CipherInfo.cs | 16 ++-- .../Encryption/EncryptorBase.cs | 15 +++- .../Encryption/EncryptorFactory.cs | 12 ++- .../Stream/StreamAesBouncyCastleEncryptor.cs | 77 +++++++++++++++++++ .../Encryption/Stream/StreamEncryptor.cs | 3 + test/CryptographyTest.cs | 72 +++++++++++------ 7 files changed, 170 insertions(+), 32 deletions(-) create mode 100644 shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index fb1aa331..6ae51eaf 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs @@ -39,6 +39,9 @@ namespace Shadowsocks.Controller public void CheckUpdate(Configuration config, int delay) { +#if DEBUG + return; +#endif CheckUpdateTimer timer = new CheckUpdateTimer(delay); timer.AutoReset = false; timer.Elapsed += Timer_Elapsed; @@ -58,6 +61,10 @@ namespace Shadowsocks.Controller public void CheckUpdate(Configuration config) { +#if DEBUG + return; +#endif + this.config = config; try diff --git a/shadowsocks-csharp/Encryption/CipherInfo.cs b/shadowsocks-csharp/Encryption/CipherInfo.cs index 1f3b8939..8f4c2ebe 100644 --- a/shadowsocks-csharp/Encryption/CipherInfo.cs +++ b/shadowsocks-csharp/Encryption/CipherInfo.cs @@ -24,8 +24,10 @@ namespace Shadowsocks.Encryption public enum CipherStandardState { InUse, - Deprecated, - Hidden, + Deprecated, // popup warning when updated + Hidden, // enabled by hidden flag in config file + + Unstable, // not in standard list or wip, only gui info } public class CipherParameter @@ -55,11 +57,12 @@ namespace Shadowsocks.Encryption public CipherStandardState StandardState = CipherStandardState.InUse; #region Stream ciphers - public CipherInfo(string name, int keySize, int ivSize, CipherFamily type) + public CipherInfo(string name, int keySize, int ivSize, CipherFamily type, CipherStandardState state = CipherStandardState.Hidden) { Type = type; Name = name; - StandardState = CipherStandardState.Hidden; + StandardState = state; + CipherParameter = new StreamCipherParameter { KeySize = keySize, @@ -70,10 +73,11 @@ namespace Shadowsocks.Encryption #endregion #region AEAD ciphers - public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type) + public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type, CipherStandardState state = CipherStandardState.InUse) { Type = type; Name = name; + StandardState = state; CipherParameter = new AEADCipherParameter { @@ -87,7 +91,7 @@ namespace Shadowsocks.Encryption public override string ToString() { - return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString("deprecated")})"; + return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString(StandardState.ToString().ToLower())})"; } public string ToString(bool verbose) { diff --git a/shadowsocks-csharp/Encryption/EncryptorBase.cs b/shadowsocks-csharp/Encryption/EncryptorBase.cs index 7f358a5c..6431bfba 100644 --- a/shadowsocks-csharp/Encryption/EncryptorBase.cs +++ b/shadowsocks-csharp/Encryption/EncryptorBase.cs @@ -2,6 +2,8 @@ { public abstract class EncryptorBase : IEncryptor { + private static int _currentId = 0; + public const int MAX_INPUT_SIZE = 32768; public const int MAX_DOMAIN_LEN = 255; @@ -14,15 +16,21 @@ public const int MD5_LEN = 16; + // for debugging only, give it a number to trace data stream + public readonly int instanceId; + protected EncryptorBase(string method, string password) { + instanceId = _currentId; + _currentId++; + Method = method; Password = password; } protected string Method; protected string Password; - + public abstract void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); public abstract void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); @@ -31,6 +39,11 @@ public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); + public override string ToString() + { + return $"{instanceId}({Method},{Password})"; + } + public int AddressBufferLength { get; set; } = -1; } } \ No newline at end of file diff --git a/shadowsocks-csharp/Encryption/EncryptorFactory.cs b/shadowsocks-csharp/Encryption/EncryptorFactory.cs index a2c239a2..534c66d8 100644 --- a/shadowsocks-csharp/Encryption/EncryptorFactory.cs +++ b/shadowsocks-csharp/Encryption/EncryptorFactory.cs @@ -33,6 +33,15 @@ namespace Shadowsocks.Encryption _registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); } } + foreach (var method in StreamAesBouncyCastleEncryptor.SupportedCiphers()) + { + if (!_registeredEncryptors.ContainsKey(method.Key)) + { + ciphers.Add(method.Key, method.Value); + _registeredEncryptors.Add(method.Key, typeof(StreamAesBouncyCastleEncryptor)); + } + } + foreach (var method in AEADAesGcmNativeEncryptor.SupportedCiphers()) { @@ -66,7 +75,8 @@ namespace Shadowsocks.Encryption t = _registeredEncryptors[DefaultCipher]; } - ConstructorInfo c = t.GetConstructor(ConstructorTypes); + ConstructorInfo c = t?.GetConstructor(ConstructorTypes) ?? + throw new TypeLoadException("can't load constructor"); if (c == null) throw new System.Exception("Invalid ctor"); IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password }); return result; diff --git a/shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs new file mode 100644 index 00000000..5500f2f9 --- /dev/null +++ b/shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs @@ -0,0 +1,77 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using System; +using System.Collections.Generic; + +namespace Shadowsocks.Encryption.Stream +{ + public class StreamAesBouncyCastleEncryptor : StreamEncryptor + { + IBufferedCipher c; + public StreamAesBouncyCastleEncryptor(string method, string password) : base(method, password) + { + c = new BufferedBlockCipher(new CfbBlockCipher(new AesEngine(), 128)); + // c = CipherUtilities.GetCipher("AES/CFB/NoPadding"); + } + + protected override void initCipher(byte[] iv, bool isEncrypt) + { + base.initCipher(iv, isEncrypt); + c.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); + } + + protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) + { + var i = buf.AsSpan().Slice(0, length); + if (isEncrypt) CipherEncrypt(i, outbuf); + else CipherDecrypt(outbuf, i); + } + + protected override int CipherEncrypt(Span plain, Span cipher) + { + CipherUpdate(plain, cipher); + return plain.Length; + } + + protected override int CipherDecrypt(Span plain, Span cipher) + { + CipherUpdate(cipher, plain); + return cipher.Length; + } + + + private void CipherUpdate(Span i, Span o) + { + // c.Reset(); + var ob = new byte[o.Length]; + int blklen = c.ProcessBytes(i.ToArray(), 0, i.Length, ob, 0); + int restlen = i.Length - blklen; + if (restlen != 0) + { + // may be problem, c is block cipher? + c.DoFinal(ob, blklen); + } + ob.CopyTo(o); + } + + #region Ciphers + private static readonly Dictionary _ciphers = new Dictionary + { + {"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, + }; + + public static Dictionary SupportedCiphers() + { + return _ciphers; + } + + protected override Dictionary getCiphers() + { + return _ciphers; + } + #endregion + } +} diff --git a/shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs index 18486ca4..1a993ee7 100644 --- a/shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs +++ b/shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs @@ -4,11 +4,13 @@ using System.Diagnostics; using System.Text; using Shadowsocks.Encryption.CircularBuffer; using Shadowsocks.Controller; +using NLog; namespace Shadowsocks.Encryption.Stream { public abstract class StreamEncryptor : EncryptorBase { + private static Logger logger = LogManager.GetCurrentClassLogger(); // for UDP only protected static byte[] _udpTmpBuf = new byte[65536]; @@ -115,6 +117,7 @@ namespace Shadowsocks.Encryption.Stream byte[] plain = buffer.Get(size); byte[] cipher = new byte[size]; cipherUpdate(true, size, plain, cipher); + Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); outlength = size + cipherOffset; } diff --git a/test/CryptographyTest.cs b/test/CryptographyTest.cs index 3294ab09..ea68995c 100644 --- a/test/CryptographyTest.cs +++ b/test/CryptographyTest.cs @@ -12,6 +12,36 @@ namespace Shadowsocks.Test public class CryptographyTest { Random random = new Random(); + + private void ArrayEqual(IEnumerable expected, IEnumerable actual, string msg = "") + { + var e1 = expected.GetEnumerator(); + var e2 = actual.GetEnumerator(); + int ctr = 0; + while (true) + { + var e1next = e1.MoveNext(); + var e2next = e2.MoveNext(); + + if (e1next && e2next) + { + Assert.AreEqual(e1.Current, e2.Current, "at " + ctr); + } + else if (!e1next && !e2next) + { + return; + } + else if (!e1next) + { + Assert.Fail($"actual longer than expected ({ctr}) {msg}"); + } + else + { + Assert.Fail($"actual shorter than expected ({ctr}) {msg}"); + } + } + } + [TestMethod] public void TestMD5() { @@ -26,37 +56,26 @@ namespace Shadowsocks.Test } } - private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor) + private void SingleEncryptionTestCase(IEncryptor encryptor, IEncryptor decryptor, int length) { RNG.Reload(); - byte[] plain = new byte[16384]; + byte[] plain = new byte[length]; byte[] cipher = new byte[plain.Length + 100];// AEAD with IPv4 address type needs +100 byte[] plain2 = new byte[plain.Length + 16]; - //random = new Random(); - random.NextBytes(plain); - encryptor.Encrypt(plain, plain.Length, cipher, out int outLen); + encryptor.Encrypt(plain, length, cipher, out int outLen); decryptor.Decrypt(cipher, outLen, plain2, out int outLen2); - Assert.AreEqual(plain.Length, outLen2); - for (int j = 0; j < plain.Length; j++) - { - Assert.AreEqual(plain[j], plain2[j]); - } - encryptor.Encrypt(plain, 1000, cipher, out outLen); - decryptor.Decrypt(cipher, outLen, plain2, out outLen2); - Assert.AreEqual(1000, outLen2); - for (int j = 0; j < outLen2; j++) - { - Assert.AreEqual(plain[j], plain2[j]); - } - encryptor.Encrypt(plain, 12333, cipher, out outLen); - decryptor.Decrypt(cipher, outLen, plain2, out outLen2); - Assert.AreEqual(12333, outLen2); - for (int j = 0; j < outLen2; j++) - { - Assert.AreEqual(plain[j], plain2[j]); - } + Assert.AreEqual(length, outLen2); + ArrayEqual(plain.AsSpan().Slice(0, length).ToArray(), plain2.AsSpan().Slice(0, length).ToArray()); + } + + private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor) + { + SingleEncryptionTestCase(encryptor, decryptor, 16384); + SingleEncryptionTestCase(encryptor, decryptor, 7); + SingleEncryptionTestCase(encryptor, decryptor, 1000); + SingleEncryptionTestCase(encryptor, decryptor, 12333); } const string password = "barfoo!"; @@ -141,5 +160,10 @@ namespace Shadowsocks.Test var dec = new StreamTableNativeEncryptor("table", "barfoo!"); RunEncryptionRound(enc, dec); } + [TestMethod] + public void TestStreamAesBouncyCastleEncryption() + { + TestEncryptionMethod(typeof(StreamAesBouncyCastleEncryptor), "aes-256-cfb"); + } } } \ No newline at end of file