Browse Source

buggy aes-cfb with bouncycastle

pull/2865/head
Student Main 5 years ago
parent
commit
a7e5fcde51
7 changed files with 170 additions and 32 deletions
  1. +7
    -0
      shadowsocks-csharp/Controller/Service/UpdateChecker.cs
  2. +10
    -6
      shadowsocks-csharp/Encryption/CipherInfo.cs
  3. +14
    -1
      shadowsocks-csharp/Encryption/EncryptorBase.cs
  4. +11
    -1
      shadowsocks-csharp/Encryption/EncryptorFactory.cs
  5. +77
    -0
      shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs
  6. +3
    -0
      shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs
  7. +48
    -24
      test/CryptographyTest.cs

+ 7
- 0
shadowsocks-csharp/Controller/Service/UpdateChecker.cs View File

@@ -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


+ 10
- 6
shadowsocks-csharp/Encryption/CipherInfo.cs View File

@@ -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)
{


+ 14
- 1
shadowsocks-csharp/Encryption/EncryptorBase.cs View File

@@ -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;
}
}

+ 11
- 1
shadowsocks-csharp/Encryption/EncryptorFactory.cs View File

@@ -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;


+ 77
- 0
shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs View File

@@ -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<byte> plain, Span<byte> cipher)
{
CipherUpdate(plain, cipher);
return plain.Length;
}

protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher)
{
CipherUpdate(cipher, plain);
return cipher.Length;
}


private void CipherUpdate(Span<byte> i, Span<byte> 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<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)},
};

public static Dictionary<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}

protected override Dictionary<string, CipherInfo> getCiphers()
{
return _ciphers;
}
#endregion
}
}

+ 3
- 0
shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs View File

@@ -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;
}


+ 48
- 24
test/CryptographyTest.cs View File

@@ -12,6 +12,36 @@ namespace Shadowsocks.Test
public class CryptographyTest
{
Random random = new Random();
private void ArrayEqual<T>(IEnumerable<T> expected, IEnumerable<T> 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<byte>(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");
}
}
}

Loading…
Cancel
Save