@@ -11,20 +11,35 @@ namespace NLog | |||||
{ | { | ||||
public static class LoggerExtension | 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) | 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) | private void CipherUpdate(Span<byte> i, Span<byte> o) | ||||
{ | { | ||||
// c.Reset(); | |||||
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; | ||||
if (restlen != 0) | if (restlen != 0) | ||||
{ | { | ||||
// may be problem, c is block cipher? | |||||
c.DoFinal(ob, blklen); | c.DoFinal(ob, blklen); | ||||
} | } | ||||
ob.CopyTo(o); | ob.CopyTo(o); | ||||
@@ -1,13 +1,12 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Text; | |||||
using NaCl.Core; | using NaCl.Core; | ||||
namespace Shadowsocks.Encryption.Stream | namespace Shadowsocks.Encryption.Stream | ||||
{ | { | ||||
public class StreamChachaNaClEncryptor : StreamEncryptor | public class StreamChachaNaClEncryptor : StreamEncryptor | ||||
{ | { | ||||
ChaCha20 c; | |||||
readonly ChaCha20 c; | |||||
public StreamChachaNaClEncryptor(string method, string password) : base(method, password) | public StreamChachaNaClEncryptor(string method, string password) : base(method, password) | ||||
{ | { | ||||
c = new ChaCha20(key, 0); | c = new ChaCha20(key, 0); | ||||
@@ -10,49 +10,40 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
public abstract class StreamEncryptor : EncryptorBase | public abstract class StreamEncryptor : EncryptorBase | ||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||||
// for UDP only | // for UDP only | ||||
protected static byte[] _udpTmpBuf = new byte[65536]; | |||||
protected static byte[] udpBuffer = new byte[65536]; | |||||
// every connection should create its own buffer | // 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 | // Is first packet | ||||
protected bool ivReady; | protected bool ivReady; | ||||
protected string _method; | |||||
protected CipherFamily _cipher; | |||||
protected CipherFamily cipherFamily; | |||||
protected CipherInfo CipherInfo; | protected CipherInfo CipherInfo; | ||||
// long-time master key | // 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 keyLen; | ||||
protected int ivLen; | protected int ivLen; | ||||
public StreamEncryptor(string method, string password) | public StreamEncryptor(string method, string password) | ||||
: base(method, 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; | var parameter = (StreamCipherParameter)CipherInfo.CipherParameter; | ||||
keyLen = parameter.KeySize; | keyLen = parameter.KeySize; | ||||
ivLen = parameter.IvSize; | ivLen = parameter.IvSize; | ||||
InitKey(password); | |||||
logger.Dump($"key {instanceId}", key, keyLen); | |||||
} | } | ||||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||||
private void InitKey(string password) | private void InitKey(string password) | ||||
{ | { | ||||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | byte[] passbuf = Encoding.UTF8.GetBytes(password); | ||||
@@ -65,7 +56,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
byte[] result = new byte[password.Length + MD5_LEN]; | byte[] result = new byte[password.Length + MD5_LEN]; | ||||
int i = 0; | int i = 0; | ||||
byte[] md5sum = null; | |||||
byte[] md5sum = Array.Empty<byte>(); | |||||
while (i < keylen) | while (i < keylen) | ||||
{ | { | ||||
if (i == 0) | if (i == 0) | ||||
@@ -85,6 +76,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
protected virtual void initCipher(byte[] iv, bool isEncrypt) | protected virtual void initCipher(byte[] iv, bool isEncrypt) | ||||
{ | { | ||||
if (ivLen == 0) return; | |||||
this.iv = new byte[ivLen]; | this.iv = new byte[ivLen]; | ||||
Array.Copy(iv, this.iv, ivLen); | Array.Copy(iv, this.iv, ivLen); | ||||
} | } | ||||
@@ -103,6 +95,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
int cipherOffset = 0; | int cipherOffset = 0; | ||||
Debug.Assert(buffer != null, "_encCircularBuffer != null"); | Debug.Assert(buffer != null, "_encCircularBuffer != null"); | ||||
buffer.Put(buf, 0, length); | buffer.Put(buf, 0, length); | ||||
logger.Trace($"{instanceId} encrypt TCP, generate iv: {!ivReady}"); | |||||
if (!ivReady) | if (!ivReady) | ||||
{ | { | ||||
// Generate IV | // Generate IV | ||||
@@ -117,7 +110,9 @@ namespace Shadowsocks.Encryption.Stream | |||||
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); | 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); | Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); | ||||
outlength = size + cipherOffset; | outlength = size + cipherOffset; | ||||
} | } | ||||
@@ -126,6 +121,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
Debug.Assert(buffer != null, "_circularBuffer != null"); | Debug.Assert(buffer != null, "_circularBuffer != null"); | ||||
buffer.Put(buf, 0, length); | buffer.Put(buf, 0, length); | ||||
logger.Trace($"{instanceId} decrypt TCP, read iv: {!ivReady}"); | |||||
if (!ivReady) | if (!ivReady) | ||||
{ | { | ||||
if (buffer.Size <= ivLen) | if (buffer.Size <= ivLen) | ||||
@@ -145,6 +141,10 @@ namespace Shadowsocks.Encryption.Stream | |||||
} | } | ||||
byte[] cipher = buffer.ToArray(); | byte[] cipher = buffer.ToArray(); | ||||
cipherUpdate(false, cipher.Length, cipher, outbuf); | 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 | // move pointer only | ||||
buffer.Skip(buffer.Size); | buffer.Skip(buffer.Size); | ||||
outlength = cipher.Length; | outlength = cipher.Length; | ||||
@@ -160,11 +160,11 @@ namespace Shadowsocks.Encryption.Stream | |||||
// Generate IV | // Generate IV | ||||
RNG.GetBytes(outbuf, ivLen); | RNG.GetBytes(outbuf, ivLen); | ||||
initCipher(outbuf, true); | initCipher(outbuf, true); | ||||
lock (_udpTmpBuf) | |||||
lock (udpBuffer) | |||||
{ | { | ||||
cipherUpdate(true, length, buf, _udpTmpBuf); | |||||
cipherUpdate(true, length, buf, udpBuffer); | |||||
outlength = length + ivLen; | 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 | // Get IV from first pos | ||||
initCipher(buf, false); | initCipher(buf, false); | ||||
outlength = length - ivLen; | outlength = length - ivLen; | ||||
lock (_udpTmpBuf) | |||||
lock (udpBuffer) | |||||
{ | { | ||||
// C# could be multi-threaded | // 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) | protected override void initCipher(byte[] iv, bool isEncrypt) | ||||
{ | { | ||||
base.initCipher(iv, isEncrypt); | base.initCipher(iv, isEncrypt); | ||||
if (_cipher == CipherFamily.Rc4Md5) | |||||
if (cipherFamily == CipherFamily.Rc4Md5) | |||||
{ | { | ||||
byte[] temp = new byte[keyLen + ivLen]; | byte[] temp = new byte[keyLen + ivLen]; | ||||
Array.Copy(key, 0, temp, 0, keyLen); | Array.Copy(key, 0, temp, 0, keyLen); | ||||
@@ -8,7 +8,8 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
public class StreamTableNativeEncryptor : StreamEncryptor | 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) | 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) | 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) | 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; | var table = isEncrypt ? _encryptTable : _decryptTable; | ||||
for (int i = 0; i < length; i++) | for (int i = 0; i < length; i++) | ||||
@@ -46,7 +46,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
outbuf[i] = table[buf[i]]; | outbuf[i] = table[buf[i]]; | ||||
} | } | ||||
} | } | ||||
else if (_cipher == CipherFamily.Plain) | |||||
else if (cipherFamily == CipherFamily.Plain) | |||||
{ | { | ||||
Array.Copy(buf, outbuf, length); | Array.Copy(buf, outbuf, length); | ||||
} | } | ||||
@@ -54,7 +54,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
if (_cipher == CipherFamily.Plain) | |||||
if (cipherFamily == CipherFamily.Plain) | |||||
{ | { | ||||
cipher.CopyTo(plain); | cipher.CopyTo(plain); | ||||
return cipher.Length; | return cipher.Length; | ||||
@@ -69,7 +69,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | ||||
{ | { | ||||
if (_cipher == CipherFamily.Plain) | |||||
if (cipherFamily == CipherFamily.Plain) | |||||
{ | { | ||||
plain.CopyTo(cipher); | plain.CopyTo(cipher); | ||||
return plain.Length; | return plain.Length; | ||||
@@ -132,6 +132,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
int leftptr = 0; | int leftptr = 0; | ||||
int rightptr = 0; | int rightptr = 0; | ||||
// why a new array? | |||||
byte[] sorted = new byte[array.Length]; | byte[] sorted = new byte[array.Length]; | ||||
for (int k = 0; k < array.Length; k++) | for (int k = 0; k < array.Length; k++) | ||||
{ | { | ||||