@@ -133,10 +133,10 @@ namespace Shadowsocks.Controller | |||||
public const int RecvSize = 2048; | public const int RecvSize = 2048; | ||||
// overhead of one chunk, reserved for AEAD ciphers | // overhead of one chunk, reserved for AEAD ciphers | ||||
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.CHUNK_LEN_BYTES; | |||||
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes; | |||||
// max chunk size | // max chunk size | ||||
public const uint MaxChunkSize = AEADEncryptor.CHUNK_LEN_MASK + AEADEncryptor.CHUNK_LEN_BYTES + 16 * 2; | |||||
public const uint MaxChunkSize = AEADEncryptor.ChunkLengthMask + AEADEncryptor.ChunkLengthBytes + 16 * 2; | |||||
// In general, the ciphertext length, we should take overhead into account | // In general, the ciphertext length, we should take overhead into account | ||||
public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */; | public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */; | ||||
@@ -22,11 +22,10 @@ namespace Shadowsocks.Encryption.AEAD | |||||
protected static byte[] _udpTmpBuf = new byte[65536]; | protected static byte[] _udpTmpBuf = new byte[65536]; | ||||
// every connection should create its own buffer | // every connection should create its own buffer | ||||
private ByteCircularBuffer buffer = new ByteCircularBuffer(MAX_INPUT_SIZE * 2); | |||||
// private ByteCircularBuffer buffer = new ByteCircularBuffer(MAX_INPUT_SIZE * 2); | |||||
private ByteCircularBuffer buffer = new ByteCircularBuffer(MaxInputSize * 2); | |||||
public const int CHUNK_LEN_BYTES = 2; | |||||
public const uint CHUNK_LEN_MASK = 0x3FFFu; | |||||
public const int ChunkLengthBytes = 2; | |||||
public const uint ChunkLengthMask = 0x3FFFu; | |||||
protected Dictionary<string, CipherInfo> ciphers; | protected Dictionary<string, CipherInfo> ciphers; | ||||
@@ -180,11 +179,11 @@ namespace Shadowsocks.Encryption.AEAD | |||||
{ | { | ||||
tcpRequestSent = true; | tcpRequestSent = true; | ||||
// The first TCP request | // The first TCP request | ||||
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||||
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + ChunkLengthBytes]; | |||||
byte[] addrBytes = buffer.Get(AddressBufferLength); | byte[] addrBytes = buffer.Get(AddressBufferLength); | ||||
int encAddrBufLength = ChunkEncrypt(addrBytes, encAddrBufBytes); | int encAddrBufLength = ChunkEncrypt(addrBytes, encAddrBufBytes); | ||||
// ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength); | // ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength); | ||||
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES); | |||||
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + ChunkLengthBytes); | |||||
Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength); | Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength); | ||||
outlength += encAddrBufLength; | outlength += encAddrBufLength; | ||||
logger.Debug($"_tcpRequestSent outlength {outlength}"); | logger.Debug($"_tcpRequestSent outlength {outlength}"); | ||||
@@ -195,12 +194,12 @@ namespace Shadowsocks.Encryption.AEAD | |||||
{ | { | ||||
uint bufSize = (uint)buffer.Size; | uint bufSize = (uint)buffer.Size; | ||||
if (bufSize <= 0) return; | if (bufSize <= 0) return; | ||||
var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK); | |||||
var chunklength = (int)Math.Min(bufSize, ChunkLengthMask); | |||||
byte[] chunkBytes = buffer.Get(chunklength); | byte[] chunkBytes = buffer.Get(chunklength); | ||||
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||||
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + ChunkLengthBytes]; | |||||
int encChunkLength = ChunkEncrypt(chunkBytes, encChunkBytes); | int encChunkLength = ChunkEncrypt(chunkBytes, encChunkBytes); | ||||
// ChunkEncrypt(chunkBytes, chunklength, encChunkBytes, out encChunkLength); | // ChunkEncrypt(chunkBytes, chunklength, encChunkBytes, out encChunkLength); | ||||
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + CHUNK_LEN_BYTES); | |||||
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + ChunkLengthBytes); | |||||
Buffer.BlockCopy(encChunkBytes, 0, outbuf, outlength, encChunkLength); | Buffer.BlockCopy(encChunkBytes, 0, outbuf, outlength, encChunkLength); | ||||
outlength += encChunkLength; | outlength += encChunkLength; | ||||
logger.Debug("chunks enc outlength " + outlength); | logger.Debug("chunks enc outlength " + outlength); | ||||
@@ -256,7 +255,7 @@ namespace Shadowsocks.Encryption.AEAD | |||||
} | } | ||||
// first get chunk length | // first get chunk length | ||||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) | |||||
if (bufSize <= ChunkLengthBytes + tagLen) | |||||
{ | { | ||||
// so we only have chunk length and its tag? | // so we only have chunk length and its tag? | ||||
return; | return; | ||||
@@ -264,12 +263,12 @@ namespace Shadowsocks.Encryption.AEAD | |||||
#region Chunk Decryption | #region Chunk Decryption | ||||
byte[] encLenBytes = buffer.Peek(CHUNK_LEN_BYTES + tagLen); | |||||
byte[] encLenBytes = buffer.Peek(ChunkLengthBytes + tagLen); | |||||
// try to dec chunk len | // try to dec chunk len | ||||
byte[] decChunkLenBytes = CipherDecrypt2(encLenBytes); | byte[] decChunkLenBytes = CipherDecrypt2(encLenBytes); | ||||
ushort chunkLen = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0)); | ushort chunkLen = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0)); | ||||
if (chunkLen > CHUNK_LEN_MASK) | |||||
if (chunkLen > ChunkLengthMask) | |||||
{ | { | ||||
// we get invalid chunk | // we get invalid chunk | ||||
logger.Error($"Invalid chunk length: {chunkLen}"); | logger.Error($"Invalid chunk length: {chunkLen}"); | ||||
@@ -277,7 +276,7 @@ namespace Shadowsocks.Encryption.AEAD | |||||
} | } | ||||
logger.Debug("Get the real chunk len:" + chunkLen); | logger.Debug("Get the real chunk len:" + chunkLen); | ||||
bufSize = buffer.Size; | bufSize = buffer.Size; | ||||
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen) | |||||
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLen + tagLen) | |||||
{ | { | ||||
logger.Debug("No more data to decrypt one chunk"); | logger.Debug("No more data to decrypt one chunk"); | ||||
return; | return; | ||||
@@ -286,7 +285,7 @@ namespace Shadowsocks.Encryption.AEAD | |||||
// we have enough data to decrypt one chunk | // we have enough data to decrypt one chunk | ||||
// drop chunk len and its tag from buffer | // drop chunk len and its tag from buffer | ||||
buffer.Skip(CHUNK_LEN_BYTES + tagLen); | |||||
buffer.Skip(ChunkLengthBytes + tagLen); | |||||
byte[] encChunkBytes = buffer.Get(chunkLen + tagLen); | byte[] encChunkBytes = buffer.Get(chunkLen + tagLen); | ||||
byte[] decChunkBytes = CipherDecrypt2(encChunkBytes); | byte[] decChunkBytes = CipherDecrypt2(encChunkBytes); | ||||
IncrementNonce(); | IncrementNonce(); | ||||
@@ -363,7 +362,7 @@ namespace Shadowsocks.Encryption.AEAD | |||||
private int ChunkEncrypt(Span<byte> plain, Span<byte> cipher) | private int ChunkEncrypt(Span<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
if (plain.Length > CHUNK_LEN_MASK) | |||||
if (plain.Length > ChunkLengthMask) | |||||
{ | { | ||||
logger.Error("enc chunk too big"); | logger.Error("enc chunk too big"); | ||||
throw new CryptoErrorException(); | throw new CryptoErrorException(); | ||||
@@ -4,7 +4,7 @@ | |||||
{ | { | ||||
private static int _currentId = 0; | private static int _currentId = 0; | ||||
public const int MAX_INPUT_SIZE = 32768; | |||||
public const int MaxInputSize = 32768; | |||||
public const int MAX_DOMAIN_LEN = 255; | public const int MAX_DOMAIN_LEN = 255; | ||||
public const int ADDR_PORT_LEN = 2; | public const int ADDR_PORT_LEN = 2; | ||||
@@ -14,7 +14,7 @@ | |||||
public const int ATYP_DOMAIN = 0x03; | public const int ATYP_DOMAIN = 0x03; | ||||
public const int ATYP_IPv6 = 0x04; | public const int ATYP_IPv6 = 0x04; | ||||
public const int MD5_LEN = 16; | |||||
public const int MD5Length = 16; | |||||
// for debugging only, give it a number to trace data stream | // for debugging only, give it a number to trace data stream | ||||
public readonly int instanceId; | public readonly int instanceId; | ||||
@@ -17,12 +17,12 @@ namespace Shadowsocks.Encryption | |||||
static EncryptorFactory() | static EncryptorFactory() | ||||
{ | { | ||||
foreach (var method in StreamTableNativeEncryptor.SupportedCiphers()) | |||||
foreach (var method in StreamPlainNativeEncryptor.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | if (!_registeredEncryptors.ContainsKey(method.Key)) | ||||
{ | { | ||||
ciphers.Add(method.Key, method.Value); | ciphers.Add(method.Key, method.Value); | ||||
_registeredEncryptors.Add(method.Key, typeof(StreamTableNativeEncryptor)); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeEncryptor)); | |||||
} | } | ||||
} | } | ||||
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | ||||
@@ -14,7 +14,6 @@ namespace Shadowsocks.Encryption.Stream | |||||
public StreamAesBouncyCastleEncryptor(string method, string password) : base(method, password) | public StreamAesBouncyCastleEncryptor(string method, string password) : base(method, password) | ||||
{ | { | ||||
c = new BufferedBlockCipher(new CfbBlockCipher(new AesEngine(), 128)); | c = new BufferedBlockCipher(new CfbBlockCipher(new AesEngine(), 128)); | ||||
// c = CipherUtilities.GetCipher("AES/CFB/NoPadding"); | |||||
} | } | ||||
protected override void initCipher(byte[] iv, bool isEncrypt) | protected override void initCipher(byte[] iv, bool isEncrypt) | ||||
@@ -23,13 +22,6 @@ namespace Shadowsocks.Encryption.Stream | |||||
c.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); | 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(0, length); | |||||
if (isEncrypt) CipherEncrypt(i, outbuf); | |||||
else CipherDecrypt(outbuf, i); | |||||
} | |||||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
CipherUpdate(plain, cipher); | CipherUpdate(plain, cipher); | ||||
@@ -42,9 +34,9 @@ namespace Shadowsocks.Encryption.Stream | |||||
return cipher.Length; | return cipher.Length; | ||||
} | } | ||||
private void CipherUpdate(Span<byte> i, Span<byte> o) | private void CipherUpdate(Span<byte> i, Span<byte> o) | ||||
{ | { | ||||
// there's some secret in OpenSSL's EVP context. | |||||
var ob = new byte[o.Length]; | var ob = new byte[o.Length]; | ||||
int blklen = c.ProcessBytes(i.ToArray(), 0, i.Length, ob, 0); | int blklen = c.ProcessBytes(i.ToArray(), 0, i.Length, ob, 0); | ||||
int restlen = i.Length - blklen; | int restlen = i.Length - blklen; | ||||
@@ -55,9 +47,11 @@ namespace Shadowsocks.Encryption.Stream | |||||
ob.CopyTo(o); | ob.CopyTo(o); | ||||
} | } | ||||
#region Ciphers | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | ||||
{ | { | ||||
{"aes-128-cfb",new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||||
{"aes-192-cfb",new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||||
{"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | {"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | ||||
}; | }; | ||||
@@ -7,11 +7,15 @@ namespace Shadowsocks.Encryption.Stream | |||||
public class StreamChachaNaClEncryptor : StreamEncryptor | public class StreamChachaNaClEncryptor : StreamEncryptor | ||||
{ | { | ||||
const int BlockSize = 64; | const int BlockSize = 64; | ||||
// when new data arrive, put it in correct offset of chunk | |||||
// tcp is stream, which can split into chunks at unexpected position... | |||||
// so we need some special handling, as we can't read all data before encrypt | |||||
// when new data arrive, put it on correct offset | |||||
// and update it, ignore other data, get it in correct offset... | // and update it, ignore other data, get it in correct offset... | ||||
byte[] chachaBuf = new byte[32768 + BlockSize]; | |||||
byte[] chachaBuf = new byte[MaxInputSize + BlockSize]; | |||||
// the 'correct offset', always in 0~BlockSize range, so input data always fit into buffer | |||||
int remain = 0; | int remain = 0; | ||||
// increase counter only when a chunk fully recieved | |||||
// increase counter manually... | |||||
int ic = 0; | int ic = 0; | ||||
public StreamChachaNaClEncryptor(string method, string password) : base(method, password) | public StreamChachaNaClEncryptor(string method, string password) : base(method, password) | ||||
{ | { | ||||
@@ -41,20 +45,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
return len; | return len; | ||||
} | } | ||||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||||
{ | |||||
var i = buf.AsSpan(0, length); | |||||
if (isEncrypt) | |||||
{ | |||||
CipherEncrypt(i, outbuf); | |||||
} | |||||
else | |||||
{ | |||||
CipherDecrypt(outbuf, i); | |||||
} | |||||
} | |||||
#region Ciphers | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | ||||
{ | { | ||||
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, | { "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, | ||||
@@ -1,10 +1,10 @@ | |||||
using System; | |||||
using NLog; | |||||
using Shadowsocks.Controller; | |||||
using Shadowsocks.Encryption.CircularBuffer; | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Text; | using System.Text; | ||||
using Shadowsocks.Encryption.CircularBuffer; | |||||
using Shadowsocks.Controller; | |||||
using NLog; | |||||
namespace Shadowsocks.Encryption.Stream | namespace Shadowsocks.Encryption.Stream | ||||
{ | { | ||||
@@ -54,7 +54,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen) | public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen) | ||||
{ | { | ||||
byte[] result = new byte[password.Length + MD5_LEN]; | |||||
byte[] result = new byte[password.Length + MD5Length]; | |||||
int i = 0; | int i = 0; | ||||
byte[] md5sum = Array.Empty<byte>(); | byte[] md5sum = Array.Empty<byte>(); | ||||
while (i < keylen) | while (i < keylen) | ||||
@@ -65,12 +65,12 @@ namespace Shadowsocks.Encryption.Stream | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
Array.Copy(md5sum, 0, result, 0, MD5_LEN); | |||||
Array.Copy(password, 0, result, MD5_LEN, password.Length); | |||||
Array.Copy(md5sum, 0, result, 0, MD5Length); | |||||
Array.Copy(password, 0, result, MD5Length, password.Length); | |||||
md5sum = CryptoUtils.MD5(result); | md5sum = CryptoUtils.MD5(result); | ||||
} | } | ||||
Array.Copy(md5sum, 0, key, i, Math.Min(MD5_LEN, keylen - i)); | |||||
i += MD5_LEN; | |||||
Array.Copy(md5sum, 0, key, i, Math.Min(MD5Length, keylen - i)); | |||||
i += MD5Length; | |||||
} | } | ||||
} | } | ||||
@@ -81,13 +81,9 @@ namespace Shadowsocks.Encryption.Stream | |||||
Array.Copy(iv, this.iv, ivLen); | Array.Copy(iv, this.iv, ivLen); | ||||
} | } | ||||
protected abstract void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf); | |||||
protected abstract int CipherEncrypt(Span<byte> plain, Span<byte> cipher); | protected abstract int CipherEncrypt(Span<byte> plain, Span<byte> cipher); | ||||
protected abstract int CipherDecrypt(Span<byte> plain, Span<byte> cipher); | protected abstract int CipherDecrypt(Span<byte> plain, Span<byte> cipher); | ||||
//protected static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } | |||||
#region TCP | #region TCP | ||||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | ||||
@@ -109,7 +105,8 @@ namespace Shadowsocks.Encryption.Stream | |||||
int size = buffer.Size; | int size = buffer.Size; | ||||
byte[] plain = buffer.Get(size); | byte[] plain = buffer.Get(size); | ||||
byte[] cipher = new byte[size]; | byte[] cipher = new byte[size]; | ||||
cipherUpdate(true, size, plain, cipher); | |||||
CipherEncrypt(plain, cipher); | |||||
logger.DumpBase64($"plain {instanceId}", plain, size); | logger.DumpBase64($"plain {instanceId}", plain, size); | ||||
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | ||||
logger.Dump($"iv {instanceId}", iv, ivLen); | logger.Dump($"iv {instanceId}", iv, ivLen); | ||||
@@ -140,7 +137,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
else initCipher(Array.Empty<byte>(), false); | else initCipher(Array.Empty<byte>(), false); | ||||
} | } | ||||
byte[] cipher = buffer.ToArray(); | byte[] cipher = buffer.ToArray(); | ||||
cipherUpdate(false, cipher.Length, cipher, outbuf); | |||||
CipherDecrypt(outbuf, cipher); | |||||
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | ||||
logger.DumpBase64($"plain {instanceId}", outbuf, cipher.Length); | logger.DumpBase64($"plain {instanceId}", outbuf, cipher.Length); | ||||
logger.Dump($"iv {instanceId}", iv, ivLen); | logger.Dump($"iv {instanceId}", iv, ivLen); | ||||
@@ -162,7 +159,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
initCipher(outbuf, true); | initCipher(outbuf, true); | ||||
lock (udpBuffer) | lock (udpBuffer) | ||||
{ | { | ||||
cipherUpdate(true, length, buf, udpBuffer); | |||||
CipherEncrypt(buf, udpBuffer); | |||||
outlength = length + ivLen; | outlength = length + ivLen; | ||||
Buffer.BlockCopy(udpBuffer, 0, outbuf, ivLen, length); | Buffer.BlockCopy(udpBuffer, 0, outbuf, ivLen, length); | ||||
} | } | ||||
@@ -177,7 +174,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
// C# could be multi-threaded | // C# could be multi-threaded | ||||
Buffer.BlockCopy(buf, ivLen, udpBuffer, 0, length - ivLen); | Buffer.BlockCopy(buf, ivLen, udpBuffer, 0, length - ivLen); | ||||
cipherUpdate(false, length - ivLen, udpBuffer, outbuf); | |||||
CipherDecrypt(outbuf, udpBuffer); | |||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,44 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Runtime.CompilerServices; | |||||
using System.Text; | |||||
namespace Shadowsocks.Encryption.Stream | |||||
{ | |||||
public class StreamPlainNativeEncryptor : StreamEncryptor | |||||
{ | |||||
public StreamPlainNativeEncryptor(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||||
{ | |||||
cipher.CopyTo(plain); | |||||
return cipher.Length; | |||||
} | |||||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||||
{ | |||||
plain.CopyTo(cipher); | |||||
return plain.Length; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) }, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -14,6 +14,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
protected override void initCipher(byte[] iv, bool isEncrypt) | protected override void initCipher(byte[] iv, bool isEncrypt) | ||||
{ | { | ||||
base.initCipher(iv, isEncrypt); | base.initCipher(iv, isEncrypt); | ||||
// rc4-md5 is rc4 with md5 based session key | |||||
if (cipherFamily == CipherFamily.Rc4Md5) | if (cipherFamily == CipherFamily.Rc4Md5) | ||||
{ | { | ||||
byte[] temp = new byte[keyLen + ivLen]; | byte[] temp = new byte[keyLen + ivLen]; | ||||
@@ -28,33 +29,27 @@ namespace Shadowsocks.Encryption.Stream | |||||
sbox = SBox(realkey); | sbox = SBox(realkey); | ||||
} | } | ||||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||||
{ | |||||
byte[] t = new byte[length]; | |||||
Array.Copy(buf, t, length); | |||||
RC4(ctx, sbox, t, length); | |||||
Array.Copy(t, outbuf, length); | |||||
} | |||||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
return DoRC4(plain, cipher); | |||||
return CipherUpdate(plain, cipher); | |||||
} | } | ||||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
return DoRC4(cipher, plain); | |||||
return CipherUpdate(cipher, plain); | |||||
} | } | ||||
private int DoRC4(Span<byte> i, Span<byte> o) | |||||
private int CipherUpdate(Span<byte> i, Span<byte> o) | |||||
{ | { | ||||
i.CopyTo(o); | |||||
RC4(ctx, sbox, o, o.Length); | |||||
return o.Length; | |||||
// don't know why we need third array, but it works... | |||||
Span<byte> t = new byte[i.Length]; | |||||
i.CopyTo(t); | |||||
RC4(ctx, sbox, t, t.Length); | |||||
t.CopyTo(o); | |||||
return t.Length; | |||||
} | } | ||||
#region Ciphers | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | ||||
{ | { | ||||
// original RC4 doesn't use IV | // original RC4 doesn't use IV | ||||
@@ -1,152 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Runtime.CompilerServices; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Shadowsocks.Encryption.Stream | |||||
{ | |||||
public class StreamTableNativeEncryptor : StreamEncryptor | |||||
{ | |||||
// table mode use special way to generate key | |||||
readonly string _password; | |||||
public StreamTableNativeEncryptor(string method, string password) : base(method, password) | |||||
{ | |||||
_password = password; | |||||
} | |||||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
// another cipher is plain, needn't a table | |||||
if (cipherFamily != CipherFamily.Table) return; | |||||
ulong a = BitConverter.ToUInt64(CryptoUtils.MD5(Encoding.UTF8.GetBytes(_password)), 0); | |||||
for (int i = 0; i < 256; i++) | |||||
{ | |||||
_encryptTable[i] = (byte)i; | |||||
} | |||||
Span<byte> t = _encryptTable; | |||||
// copy array 1024 times? excuse me? | |||||
for (int i = 1; i < 1024; i++) | |||||
{ | |||||
t = MergeSort(t, a, i); | |||||
} | |||||
_encryptTable = t.ToArray(); | |||||
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 (cipherFamily == CipherFamily.Table) | |||||
{ | |||||
var table = isEncrypt ? _encryptTable : _decryptTable; | |||||
for (int i = 0; i < length; i++) | |||||
{ | |||||
outbuf[i] = table[buf[i]]; | |||||
} | |||||
} | |||||
else if (cipherFamily == CipherFamily.Plain) | |||||
{ | |||||
Array.Copy(buf, outbuf, length); | |||||
} | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||||
{ | |||||
if (cipherFamily == CipherFamily.Plain) | |||||
{ | |||||
cipher.CopyTo(plain); | |||||
return cipher.Length; | |||||
} | |||||
for (int i = 0; i < cipher.Length; i++) | |||||
{ | |||||
plain[i] = _decryptTable[cipher[i]]; | |||||
} | |||||
return cipher.Length; | |||||
} | |||||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||||
{ | |||||
if (cipherFamily == CipherFamily.Plain) | |||||
{ | |||||
plain.CopyTo(cipher); | |||||
return plain.Length; | |||||
} | |||||
for (int i = 0; i < plain.Length; i++) | |||||
{ | |||||
cipher[i] = _decryptTable[plain[i]]; | |||||
} | |||||
return plain.Length; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) }, | |||||
{"table", new CipherInfo("table", 0, 0, CipherFamily.Table) }, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
#region Table | |||||
private byte[] _encryptTable = new byte[256]; | |||||
private byte[] _decryptTable = new byte[256]; | |||||
private byte[] _tmp = new byte[256]; | |||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
private static long Compare(byte x, byte y, ulong a, int i) | |||||
{ | |||||
return (long)(a % (ulong)(x + i)) - (long)(a % (ulong)(y + i)); | |||||
} | |||||
byte[] buf = new byte[1024]; | |||||
private Span<byte> MergeSort(Span<byte> array, ulong a, int j) | |||||
{ | |||||
if (array.Length == 1) | |||||
{ | |||||
return array; | |||||
} | |||||
int middle = array.Length / 2; | |||||
Span<byte> left = MergeSort(array.Slice(0, middle), a, j);; | |||||
Span<byte> right = MergeSort(array.Slice(middle), a, j); | |||||
int leftptr = 0; | |||||
int rightptr = 0; | |||||
// why a new array? | |||||
Span<byte> sorted = new byte[array.Length];// buf.AsSpan().Slice(0,array.Length); // // _tmp; | |||||
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 | |||||
} | |||||
} |
@@ -147,16 +147,11 @@ namespace Shadowsocks.Test | |||||
[TestMethod] | [TestMethod] | ||||
public void TestNativeEncryption() | public void TestNativeEncryption() | ||||
{ | { | ||||
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "plain"); | |||||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4"); | |||||
TestEncryptionMethod(typeof(StreamPlainNativeEncryptor), "plain"); | |||||
// TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4"); | |||||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5"); | TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5"); | ||||
} | } | ||||
[TestMethod] | |||||
public void TestNativeTableEncryption() | |||||
{ | |||||
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "table"); | |||||
} | |||||
[TestMethod] | [TestMethod] | ||||
public void TestStreamAesBouncyCastleEncryption() | public void TestStreamAesBouncyCastleEncryption() | ||||
{ | { | ||||