@@ -11,20 +11,35 @@ namespace NLog | |||
{ | |||
public static class LoggerExtension | |||
{ | |||
public static void Dump(this Logger logger, string tag, byte[] arr, int length) | |||
// for key, iv, etc... | |||
public static void Dump(this Logger logger, string tag, byte[] arr, int length = -1) | |||
{ | |||
if (logger.IsTraceEnabled) | |||
{ | |||
var sb = new StringBuilder($"{Environment.NewLine}{tag}: "); | |||
for (int i = 0; i < length - 1; i++) | |||
{ | |||
sb.Append($"0x{arr[i]:X2}, "); | |||
} | |||
sb.Append($"0x{arr[length - 1]:X2}"); | |||
sb.Append(Environment.NewLine); | |||
logger.Trace(sb.ToString()); | |||
} | |||
if (length == -1) length = arr.Length; | |||
if (!logger.IsTraceEnabled) return; | |||
string hex = BitConverter.ToString(arr.AsSpan().Slice(0, length).ToArray()).Replace("-", ""); | |||
string content = $@" | |||
{tag}: | |||
{hex} | |||
"; | |||
logger.Trace(content); | |||
} | |||
// for cipher and plain text, so we can use openssl to test | |||
public static void DumpBase64(this Logger logger, string tag, byte[] arr, int length = -1) | |||
{ | |||
if (length == -1) length = arr.Length; | |||
if (!logger.IsTraceEnabled) return; | |||
string hex =Convert.ToBase64String(arr.AsSpan().Slice(0, length).ToArray()); | |||
string content = $@" | |||
{tag}: | |||
{hex} | |||
"; | |||
logger.Trace(content); | |||
} | |||
public static void Debug(this Logger logger, EndPoint local, EndPoint remote, int len, string header = null, string tailer = null) | |||
{ | |||
@@ -45,13 +45,11 @@ namespace Shadowsocks.Encryption.Stream | |||
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); | |||
@@ -1,13 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using NaCl.Core; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class StreamChachaNaClEncryptor : StreamEncryptor | |||
{ | |||
ChaCha20 c; | |||
readonly ChaCha20 c; | |||
public StreamChachaNaClEncryptor(string method, string password) : base(method, password) | |||
{ | |||
c = new ChaCha20(key, 0); | |||
@@ -10,49 +10,40 @@ namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public abstract class StreamEncryptor : EncryptorBase | |||
{ | |||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||
// for UDP only | |||
protected static byte[] _udpTmpBuf = new byte[65536]; | |||
protected static byte[] udpBuffer = new byte[65536]; | |||
// every connection should create its own buffer | |||
private ByteCircularBuffer buffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | |||
protected Dictionary<string, CipherInfo> ciphers; | |||
private readonly ByteCircularBuffer buffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | |||
// Is first packet | |||
protected bool ivReady; | |||
protected string _method; | |||
protected CipherFamily _cipher; | |||
protected CipherFamily cipherFamily; | |||
protected CipherInfo CipherInfo; | |||
// long-time master key | |||
protected static byte[] key = null; | |||
protected byte[] iv; | |||
protected static byte[] key = Array.Empty<byte>(); | |||
protected byte[] iv = Array.Empty<byte>(); | |||
protected int keyLen; | |||
protected int ivLen; | |||
public StreamEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
InitEncryptorInfo(method); | |||
InitKey(password); | |||
} | |||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||
private void InitEncryptorInfo(string method) | |||
{ | |||
method = method.ToLower(); | |||
_method = method; | |||
ciphers = getCiphers(); | |||
CipherInfo = ciphers[_method]; | |||
_cipher = CipherInfo.Type; | |||
CipherInfo = getCiphers()[method.ToLower()]; | |||
cipherFamily = CipherInfo.Type; | |||
var parameter = (StreamCipherParameter)CipherInfo.CipherParameter; | |||
keyLen = parameter.KeySize; | |||
ivLen = parameter.IvSize; | |||
InitKey(password); | |||
logger.Dump($"key {instanceId}", key, keyLen); | |||
} | |||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||
private void InitKey(string password) | |||
{ | |||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | |||
@@ -65,7 +56,7 @@ namespace Shadowsocks.Encryption.Stream | |||
{ | |||
byte[] result = new byte[password.Length + MD5_LEN]; | |||
int i = 0; | |||
byte[] md5sum = null; | |||
byte[] md5sum = Array.Empty<byte>(); | |||
while (i < keylen) | |||
{ | |||
if (i == 0) | |||
@@ -85,6 +76,7 @@ namespace Shadowsocks.Encryption.Stream | |||
protected virtual void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
if (ivLen == 0) return; | |||
this.iv = new byte[ivLen]; | |||
Array.Copy(iv, this.iv, ivLen); | |||
} | |||
@@ -103,6 +95,7 @@ namespace Shadowsocks.Encryption.Stream | |||
int cipherOffset = 0; | |||
Debug.Assert(buffer != null, "_encCircularBuffer != null"); | |||
buffer.Put(buf, 0, length); | |||
logger.Trace($"{instanceId} encrypt TCP, generate iv: {!ivReady}"); | |||
if (!ivReady) | |||
{ | |||
// Generate IV | |||
@@ -117,7 +110,9 @@ namespace Shadowsocks.Encryption.Stream | |||
byte[] plain = buffer.Get(size); | |||
byte[] cipher = new byte[size]; | |||
cipherUpdate(true, size, plain, cipher); | |||
logger.DumpBase64($"plain {instanceId}", plain, size); | |||
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | |||
logger.Dump($"iv {instanceId}", iv, ivLen); | |||
Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); | |||
outlength = size + cipherOffset; | |||
} | |||
@@ -126,6 +121,7 @@ namespace Shadowsocks.Encryption.Stream | |||
{ | |||
Debug.Assert(buffer != null, "_circularBuffer != null"); | |||
buffer.Put(buf, 0, length); | |||
logger.Trace($"{instanceId} decrypt TCP, read iv: {!ivReady}"); | |||
if (!ivReady) | |||
{ | |||
if (buffer.Size <= ivLen) | |||
@@ -145,6 +141,10 @@ namespace Shadowsocks.Encryption.Stream | |||
} | |||
byte[] cipher = buffer.ToArray(); | |||
cipherUpdate(false, cipher.Length, cipher, outbuf); | |||
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length); | |||
logger.DumpBase64($"plain {instanceId}", outbuf, cipher.Length); | |||
logger.Dump($"iv {instanceId}", iv, ivLen); | |||
// move pointer only | |||
buffer.Skip(buffer.Size); | |||
outlength = cipher.Length; | |||
@@ -160,11 +160,11 @@ namespace Shadowsocks.Encryption.Stream | |||
// Generate IV | |||
RNG.GetBytes(outbuf, ivLen); | |||
initCipher(outbuf, true); | |||
lock (_udpTmpBuf) | |||
lock (udpBuffer) | |||
{ | |||
cipherUpdate(true, length, buf, _udpTmpBuf); | |||
cipherUpdate(true, length, buf, udpBuffer); | |||
outlength = length + ivLen; | |||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, ivLen, length); | |||
Buffer.BlockCopy(udpBuffer, 0, outbuf, ivLen, length); | |||
} | |||
} | |||
@@ -173,11 +173,11 @@ namespace Shadowsocks.Encryption.Stream | |||
// Get IV from first pos | |||
initCipher(buf, false); | |||
outlength = length - ivLen; | |||
lock (_udpTmpBuf) | |||
lock (udpBuffer) | |||
{ | |||
// C# could be multi-threaded | |||
Buffer.BlockCopy(buf, ivLen, _udpTmpBuf, 0, length - ivLen); | |||
cipherUpdate(false, length - ivLen, _udpTmpBuf, outbuf); | |||
Buffer.BlockCopy(buf, ivLen, udpBuffer, 0, length - ivLen); | |||
cipherUpdate(false, length - ivLen, udpBuffer, outbuf); | |||
} | |||
} | |||
@@ -14,7 +14,7 @@ namespace Shadowsocks.Encryption.Stream | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
base.initCipher(iv, isEncrypt); | |||
if (_cipher == CipherFamily.Rc4Md5) | |||
if (cipherFamily == CipherFamily.Rc4Md5) | |||
{ | |||
byte[] temp = new byte[keyLen + ivLen]; | |||
Array.Copy(key, 0, temp, 0, keyLen); | |||
@@ -8,7 +8,8 @@ namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class StreamTableNativeEncryptor : StreamEncryptor | |||
{ | |||
string _password; | |||
// table mode use special way to generate key | |||
readonly string _password; | |||
public StreamTableNativeEncryptor(string method, string password) : base(method, password) | |||
{ | |||
@@ -17,28 +18,27 @@ namespace Shadowsocks.Encryption.Stream | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
base.initCipher(iv, isEncrypt); | |||
if (_cipher == CipherFamily.Table) | |||
// 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++) | |||
{ | |||
ulong a = BitConverter.ToUInt64(CryptoUtils.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; | |||
} | |||
_encryptTable[i] = (byte)i; | |||
} | |||
// copy array 1024 times? excuse me? | |||
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 == CipherFamily.Table) | |||
if (cipherFamily == CipherFamily.Table) | |||
{ | |||
var table = isEncrypt ? _encryptTable : _decryptTable; | |||
for (int i = 0; i < length; i++) | |||
@@ -46,7 +46,7 @@ namespace Shadowsocks.Encryption.Stream | |||
outbuf[i] = table[buf[i]]; | |||
} | |||
} | |||
else if (_cipher == CipherFamily.Plain) | |||
else if (cipherFamily == CipherFamily.Plain) | |||
{ | |||
Array.Copy(buf, outbuf, length); | |||
} | |||
@@ -54,7 +54,7 @@ namespace Shadowsocks.Encryption.Stream | |||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
if (_cipher == CipherFamily.Plain) | |||
if (cipherFamily == CipherFamily.Plain) | |||
{ | |||
cipher.CopyTo(plain); | |||
return cipher.Length; | |||
@@ -69,7 +69,7 @@ namespace Shadowsocks.Encryption.Stream | |||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||
{ | |||
if (_cipher == CipherFamily.Plain) | |||
if (cipherFamily == CipherFamily.Plain) | |||
{ | |||
plain.CopyTo(cipher); | |||
return plain.Length; | |||
@@ -132,6 +132,7 @@ namespace Shadowsocks.Encryption.Stream | |||
int leftptr = 0; | |||
int rightptr = 0; | |||
// why a new array? | |||
byte[] sorted = new byte[array.Length]; | |||
for (int k = 0; k < array.Length; k++) | |||
{ | |||