@@ -121,14 +121,21 @@ after_build: | |||||
$WorkingFolder = "$env:APPVEYOR_BUILD_FOLDER\working" | $WorkingFolder = "$env:APPVEYOR_BUILD_FOLDER\working" | ||||
$ExeFileName = "Shadowsocks-$env:APPVEYOR_BUILD_VERSION-$env:CONFIGURATION.exe" | $ExeFileName = "Shadowsocks-$env:APPVEYOR_BUILD_VERSION-$env:CONFIGURATION.exe" | ||||
$DllFileName = "Shadowsocks.dll" | |||||
$ExeFile = "$WorkingFolder\$DNVer\$ExeFileName" | $ExeFile = "$WorkingFolder\$DNVer\$ExeFileName" | ||||
$DllFile = "$WorkingFolder\$DNVer\$DllFileName" | |||||
$ExeHashFile = "$Exefile.hash" | $ExeHashFile = "$Exefile.hash" | ||||
$DllHashFile = "$DllFile.hash" | |||||
New-Item $WorkingFolder -ItemType Directory -Force | New-Item $WorkingFolder -ItemType Directory -Force | ||||
Copy-Item $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\net472\Shadowsocks.exe $WorkingFolder\Shadowsocks.exe | |||||
Copy-Item $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\netcoreapp3.1\Shadowsocks.exe $WorkingFolder\Shadowsocks.exe | |||||
Copy-Item $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\netcoreapp3.1\Shadowsocks.dll $WorkingFolder\Shadowsocks.dll | |||||
Copy-Item $WorkingFolder\Shadowsocks.exe $ExeFile | Copy-Item $WorkingFolder\Shadowsocks.exe $ExeFile | ||||
Copy-Item $WorkingFolder\Shadowsocks.dll $DllFile | |||||
CalculateHash -file $Exefile | Out-File -FilePath $ExeHashFile | CalculateHash -file $Exefile | Out-File -FilePath $ExeHashFile | ||||
CalculateHash -file $DllFile | Out-File -FilePath $DllHashFile | |||||
Push-AppveyorArtifact $ExeFile | Push-AppveyorArtifact $ExeFile | ||||
Push-AppveyorArtifact $ExeHashFile | Push-AppveyorArtifact $ExeHashFile | ||||
@@ -137,13 +144,17 @@ after_build: | |||||
if ($env:configuration -eq 'Release') | if ($env:configuration -eq 'Release') | ||||
{ | { | ||||
$ReleaseFile = "$WorkingFolder\Shadowsocks.exe" | $ReleaseFile = "$WorkingFolder\Shadowsocks.exe" | ||||
$ReleaseFile2 = "$WorkingFolder\Shadowsocks.dll" | |||||
$HashFile = "$ReleaseFile.hash" | $HashFile = "$ReleaseFile.hash" | ||||
$HashFile2 = "$ReleaseFile2.hash" | |||||
$ZipFile = "$WorkingFolder\Shadowsocks-$env:APPVEYOR_BUILD_VERSION.zip" | $ZipFile = "$WorkingFolder\Shadowsocks-$env:APPVEYOR_BUILD_VERSION.zip" | ||||
$ZipHashFile = "$ZipFile.hash" | $ZipHashFile = "$ZipFile.hash" | ||||
# Calculate exe Hash and archieve both exe and hash to zip | # Calculate exe Hash and archieve both exe and hash to zip | ||||
CalculateHash -file $ReleaseFile | Out-File -FilePath $hashFile | CalculateHash -file $ReleaseFile | Out-File -FilePath $hashFile | ||||
7z a $ZipFile $ReleaseFile | 7z a $ZipFile $ReleaseFile | ||||
7z a $ZipFile $ReleaseFile2 | |||||
7z a $ZipFile $HashFile | 7z a $ZipFile $HashFile | ||||
7z a $ZipFile $HashFile2 | |||||
Push-AppveyorArtifact $ZipFile | Push-AppveyorArtifact $ZipFile | ||||
# Calculate zip Hash | # Calculate zip Hash | ||||
CalculateHash -file $ZipFile | Out-File -FilePath $ZipHashFile | CalculateHash -file $ZipFile | Out-File -FilePath $ZipHashFile | ||||
@@ -148,7 +148,9 @@ namespace Shadowsocks.Controller | |||||
private TCPRelay _tcprelay; | private TCPRelay _tcprelay; | ||||
private Socket _connection; | private Socket _connection; | ||||
private IEncryptor _encryptor; | |||||
private IEncryptor encryptor; | |||||
// workaround | |||||
private IEncryptor decryptor; | |||||
private Server _server; | private Server _server; | ||||
private AsyncSession _currentRemoteSession; | private AsyncSession _currentRemoteSession; | ||||
@@ -216,13 +218,14 @@ namespace Shadowsocks.Controller | |||||
if (server == null || server.server == "") | if (server == null || server.server == "") | ||||
throw new ArgumentException("No server configured"); | throw new ArgumentException("No server configured"); | ||||
_encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||||
encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||||
decryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||||
this._server = server; | this._server = server; | ||||
/* prepare address buffer length for AEAD */ | /* prepare address buffer length for AEAD */ | ||||
Logger.Debug($"_addrBufLength={_addrBufLength}"); | Logger.Debug($"_addrBufLength={_addrBufLength}"); | ||||
_encryptor.AddrBufLength = _addrBufLength; | |||||
encryptor.AddressBufferLength = _addrBufLength; | |||||
decryptor.AddressBufferLength = _addrBufLength; | |||||
} | } | ||||
public void Start(byte[] firstPacket, int length) | public void Start(byte[] firstPacket, int length) | ||||
@@ -272,14 +275,6 @@ namespace Shadowsocks.Controller | |||||
Logger.LogUsefulException(e); | Logger.LogUsefulException(e); | ||||
} | } | ||||
} | } | ||||
lock (_encryptionLock) | |||||
{ | |||||
lock (_decryptionLock) | |||||
{ | |||||
_encryptor?.Dispose(); | |||||
} | |||||
} | |||||
} | } | ||||
private void HandshakeReceive() | private void HandshakeReceive() | ||||
@@ -825,7 +820,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
_encryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); | |||||
decryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); | |||||
} | } | ||||
catch (CryptoErrorException) | catch (CryptoErrorException) | ||||
{ | { | ||||
@@ -898,7 +893,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
_encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); | |||||
encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); | |||||
} | } | ||||
catch (CryptoErrorException) | catch (CryptoErrorException) | ||||
{ | { | ||||
@@ -0,0 +1,109 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Security.Cryptography; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Shadowsocks.Encryption.AEAD | |||||
{ | |||||
public class AEADAesGcmNativeEncryptor : AEADEncryptor | |||||
{ | |||||
public AEADAesGcmNativeEncryptor(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm)}, | |||||
}; | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||||
{ | |||||
base.InitCipher(salt, isEncrypt, isUdp); | |||||
DeriveSessionKey(isEncrypt ? encryptSalt : decryptSalt, | |||||
_Masterkey, sessionKey); | |||||
} | |||||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||||
{ | |||||
using (var aes = new AesGcm(sessionKey)) | |||||
{ | |||||
byte[] tag = new byte[tagLen]; | |||||
byte[] cipherWithoutTag = new byte[clen]; | |||||
Array.Copy(ciphertext, 0, cipherWithoutTag, 0, clen - tagLen); | |||||
Array.Copy(ciphertext, clen - tagLen, tag, 0, tagLen); | |||||
aes.Decrypt(decNonce, ciphertext, cipherWithoutTag, tag); | |||||
} | |||||
/*var cipher = new GcmBlockCipher(new AesEngine()); | |||||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _decNonce); | |||||
cipher.Init(false, parameters); | |||||
var plaintextBC = new byte[cipher.GetOutputSize((int)clen)]; | |||||
var len = cipher.ProcessBytes(ciphertext, 0, (int)clen, plaintextBC, 0); | |||||
cipher.DoFinal(plaintextBC, len); | |||||
plen = (uint)(plaintextBC.Length); | |||||
Array.Copy(plaintextBC, 0, plaintext, 0, plen);*/ | |||||
} | |||||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||||
{ | |||||
using (var aes = new AesGcm(sessionKey)) | |||||
{ | |||||
byte[] tag = new byte[tagLen]; | |||||
byte[] cipherWithoutTag = new byte[clen]; | |||||
aes.Encrypt(encNonce, plaintext, cipherWithoutTag, tag); | |||||
cipherWithoutTag.CopyTo(ciphertext, 0); | |||||
tag.CopyTo(ciphertext, clen); | |||||
} | |||||
/* | |||||
var cipher = new GcmBlockCipher(new AesEngine()); | |||||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _encNonce); | |||||
cipher.Init(true, parameters); | |||||
var ciphertextBC = new byte[cipher.GetOutputSize((int)plen)]; | |||||
var len = cipher.ProcessBytes(plaintext, 0, (int)plen, ciphertextBC, 0); | |||||
cipher.DoFinal(ciphertextBC, len); | |||||
clen = (uint)(ciphertextBC.Length); | |||||
Array.Copy(ciphertextBC, 0, ciphertext, 0, clen);*/ | |||||
} | |||||
public override byte[] CipherDecrypt2(byte[] cipher) | |||||
{ | |||||
var (cipherMem, tagMem) = GetCipherTextAndTagMem(cipher); | |||||
Span<byte> plainMem = new Span<byte>(new byte[cipherMem.Length]); | |||||
using var aes = new AesGcm(sessionKey); | |||||
aes.Decrypt(decNonce.AsSpan(), cipherMem.Span, tagMem.Span, plainMem); | |||||
return plainMem.ToArray(); | |||||
} | |||||
public override byte[] CipherEncrypt2(byte[] plain) | |||||
{ | |||||
Span<byte> d = new Span<byte>(new byte[plain.Length + tagLen]); | |||||
//Span<byte> cipherMem = new Span<byte>(new byte[plain.Length]); | |||||
//Span<byte> tagMem = new Span<byte>(new byte[tagLen]); | |||||
using var aes = new AesGcm(sessionKey); | |||||
aes.Encrypt(encNonce.AsSpan(), plain.AsSpan(), d.Slice(0, plain.Length), d.Slice(plain.Length)); | |||||
return d.ToArray(); | |||||
} | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
} | |||||
} |
@@ -9,24 +9,21 @@ using System.Threading.Tasks; | |||||
namespace Shadowsocks.Encryption.AEAD | namespace Shadowsocks.Encryption.AEAD | ||||
{ | { | ||||
public class AEADBouncyCastleEncryptor : AEADEncryptor, IDisposable | |||||
public class AEADBouncyCastleEncryptor : AEADEncryptor | |||||
{ | { | ||||
static int CIPHER_AES = 1; // dummy | |||||
public AEADBouncyCastleEncryptor(string method, string password) | |||||
: base(method, password) | |||||
public AEADBouncyCastleEncryptor(string method, string password) : base(method, password) | |||||
{ | { | ||||
} | } | ||||
private static readonly Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo> | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | { | ||||
{"aes-128-gcm", new EncryptorInfo("AES-128-GCM", 16, 16, 12, 16, CIPHER_AES)}, | |||||
{"aes-192-gcm", new EncryptorInfo("AES-192-GCM", 24, 24, 12, 16, CIPHER_AES)}, | |||||
{"aes-256-gcm", new EncryptorInfo("AES-256-GCM", 32, 32, 12, 16, CIPHER_AES)}, | |||||
{"aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm)}, | |||||
}; | }; | ||||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | { | ||||
return _ciphers; | return _ciphers; | ||||
} | } | ||||
@@ -35,27 +32,27 @@ namespace Shadowsocks.Encryption.AEAD | |||||
{ | { | ||||
base.InitCipher(salt, isEncrypt, isUdp); | base.InitCipher(salt, isEncrypt, isUdp); | ||||
DeriveSessionKey(isEncrypt ? _encryptSalt : _decryptSalt, | |||||
_Masterkey, _sessionKey); | |||||
DeriveSessionKey(isEncrypt ? encryptSalt : decryptSalt, | |||||
_Masterkey, sessionKey); | |||||
} | } | ||||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | ||||
{ | { | ||||
var cipher = new GcmBlockCipher(new AesEngine()); | var cipher = new GcmBlockCipher(new AesEngine()); | ||||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _decNonce); | |||||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, decNonce); | |||||
cipher.Init(false, parameters); | cipher.Init(false, parameters); | ||||
var plaintextBC = new byte[cipher.GetOutputSize((int)clen)]; | |||||
var len = cipher.ProcessBytes(ciphertext, 0, (int)clen, plaintextBC, 0); | |||||
cipher.DoFinal(plaintextBC, len); | |||||
plen = (uint)(plaintextBC.Length); | |||||
Array.Copy(plaintextBC, 0, plaintext, 0, plen); | |||||
//var plaintextBC = new byte[cipher.GetOutputSize(ciphertext.Length)]; | |||||
var len = cipher.ProcessBytes(ciphertext, 0, ciphertext.Length, plaintext, 0); | |||||
cipher.DoFinal(plaintext, len); | |||||
//plen = (uint)(plaintext.Length); | |||||
//Array.Copy(plaintextBC, 0, plaintext, 0, plaintext.Length); | |||||
} | } | ||||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | ||||
{ | { | ||||
var cipher = new GcmBlockCipher(new AesEngine()); | var cipher = new GcmBlockCipher(new AesEngine()); | ||||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _encNonce); | |||||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, encNonce); | |||||
cipher.Init(true, parameters); | cipher.Init(true, parameters); | ||||
var ciphertextBC = new byte[cipher.GetOutputSize((int)plen)]; | var ciphertextBC = new byte[cipher.GetOutputSize((int)plen)]; | ||||
@@ -65,13 +62,35 @@ namespace Shadowsocks.Encryption.AEAD | |||||
Array.Copy(ciphertextBC, 0, ciphertext, 0, clen); | Array.Copy(ciphertextBC, 0, ciphertext, 0, clen); | ||||
} | } | ||||
public static List<string> SupportedCiphers() | |||||
public override byte[] CipherDecrypt2(byte[] cipher) | |||||
{ | { | ||||
return new List<string>(_ciphers.Keys); | |||||
var aes = new GcmBlockCipher(new AesEngine()); | |||||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, decNonce); | |||||
aes.Init(false, parameters); | |||||
byte[] plain = new byte[aes.GetOutputSize(cipher.Length)]; | |||||
var len = aes.ProcessBytes(cipher, 0, cipher.Length, plain, 0); | |||||
aes.DoFinal(plain, len); | |||||
return plain; | |||||
} | } | ||||
public override void Dispose() | |||||
public override byte[] CipherEncrypt2(byte[] plain) | |||||
{ | { | ||||
var aes = new GcmBlockCipher(new AesEngine()); | |||||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, encNonce); | |||||
aes.Init(true, parameters); | |||||
var cipher = new byte[aes.GetOutputSize(plain.Length)]; | |||||
var len = aes.ProcessBytes(plain, 0, plain.Length, cipher, 0); | |||||
aes.DoFinal(cipher, len); | |||||
return cipher; | |||||
} | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -11,8 +11,7 @@ using Shadowsocks.Encryption.Stream; | |||||
namespace Shadowsocks.Encryption.AEAD | namespace Shadowsocks.Encryption.AEAD | ||||
{ | { | ||||
public abstract class AEADEncryptor | |||||
: EncryptorBase | |||||
public abstract class AEADEncryptor : EncryptorBase | |||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | private static Logger logger = LogManager.GetCurrentClassLogger(); | ||||
// We are using the same saltLen and keyLen | // We are using the same saltLen and keyLen | ||||
@@ -29,26 +28,27 @@ namespace Shadowsocks.Encryption.AEAD | |||||
public const int CHUNK_LEN_BYTES = 2; | public const int CHUNK_LEN_BYTES = 2; | ||||
public const uint CHUNK_LEN_MASK = 0x3FFFu; | public const uint CHUNK_LEN_MASK = 0x3FFFu; | ||||
protected Dictionary<string, EncryptorInfo> ciphers; | |||||
protected Dictionary<string, CipherInfo> ciphers; | |||||
protected string _method; | protected string _method; | ||||
protected int _cipher; | |||||
protected CipherFamily _cipher; | |||||
// internal name in the crypto library | // internal name in the crypto library | ||||
protected string _innerLibName; | protected string _innerLibName; | ||||
protected EncryptorInfo CipherInfo; | |||||
protected CipherInfo CipherInfo; | |||||
protected static byte[] _Masterkey = null; | protected static byte[] _Masterkey = null; | ||||
protected byte[] _sessionKey; | |||||
protected byte[] sessionKey; | |||||
protected int keyLen; | protected int keyLen; | ||||
protected int saltLen; | protected int saltLen; | ||||
protected int tagLen; | protected int tagLen; | ||||
protected int nonceLen; | protected int nonceLen; | ||||
protected byte[] _encryptSalt; | |||||
protected byte[] _decryptSalt; | |||||
protected byte[] encryptSalt; | |||||
protected byte[] decryptSalt; | |||||
protected object _nonceIncrementLock = new object(); | protected object _nonceIncrementLock = new object(); | ||||
protected byte[] _encNonce; | |||||
protected byte[] _decNonce; | |||||
protected byte[] encNonce; | |||||
protected byte[] decNonce; | |||||
// Is first packet | // Is first packet | ||||
protected bool _decryptSaltReceived; | protected bool _decryptSaltReceived; | ||||
protected bool _encryptSaltSent; | protected bool _encryptSaltSent; | ||||
@@ -62,11 +62,11 @@ namespace Shadowsocks.Encryption.AEAD | |||||
InitEncryptorInfo(method); | InitEncryptorInfo(method); | ||||
InitKey(password); | InitKey(password); | ||||
// Initialize all-zero nonce for each connection | // Initialize all-zero nonce for each connection | ||||
_encNonce = new byte[nonceLen]; | |||||
_decNonce = new byte[nonceLen]; | |||||
encNonce = new byte[nonceLen]; | |||||
decNonce = new byte[nonceLen]; | |||||
} | } | ||||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||||
protected void InitEncryptorInfo(string method) | protected void InitEncryptorInfo(string method) | ||||
{ | { | ||||
@@ -74,15 +74,12 @@ namespace Shadowsocks.Encryption.AEAD | |||||
_method = method; | _method = method; | ||||
ciphers = getCiphers(); | ciphers = getCiphers(); | ||||
CipherInfo = ciphers[_method]; | CipherInfo = ciphers[_method]; | ||||
_innerLibName = CipherInfo.InnerLibName; | |||||
_cipher = CipherInfo.Type; | _cipher = CipherInfo.Type; | ||||
if (_cipher == 0) { | |||||
throw new System.Exception("method not found"); | |||||
} | |||||
keyLen = CipherInfo.KeySize; | |||||
saltLen = CipherInfo.SaltSize; | |||||
tagLen = CipherInfo.TagSize; | |||||
nonceLen = CipherInfo.NonceSize; | |||||
var parameter = (AEADCipherParameter)CipherInfo.CipherParameter; | |||||
keyLen = parameter.KeySize; | |||||
saltLen = parameter.SaltSize; | |||||
tagLen = parameter.TagSize; | |||||
nonceLen = parameter.NonceSize; | |||||
} | } | ||||
protected void InitKey(string password) | protected void InitKey(string password) | ||||
@@ -93,7 +90,7 @@ namespace Shadowsocks.Encryption.AEAD | |||||
if (_Masterkey.Length != keyLen) Array.Resize(ref _Masterkey, keyLen); | if (_Masterkey.Length != keyLen) Array.Resize(ref _Masterkey, keyLen); | ||||
DeriveKey(passbuf, _Masterkey, keyLen); | DeriveKey(passbuf, _Masterkey, keyLen); | ||||
// init session key | // init session key | ||||
if (_sessionKey == null) _sessionKey = new byte[keyLen]; | |||||
if (sessionKey == null) sessionKey = new byte[keyLen]; | |||||
} | } | ||||
public void DeriveKey(byte[] password, byte[] key, int keylen) | public void DeriveKey(byte[] password, byte[] key, int keylen) | ||||
@@ -103,32 +100,69 @@ namespace Shadowsocks.Encryption.AEAD | |||||
public void DeriveSessionKey(byte[] salt, byte[] masterKey, byte[] sessionKey) | public void DeriveSessionKey(byte[] salt, byte[] masterKey, byte[] sessionKey) | ||||
{ | { | ||||
CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey,0); | |||||
CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey, 0); | |||||
} | } | ||||
protected void IncrementNonce(bool isEncrypt) | protected void IncrementNonce(bool isEncrypt) | ||||
{ | { | ||||
lock (_nonceIncrementLock) { | |||||
CryptoUtils.SodiumIncrement(isEncrypt ? _encNonce : _decNonce); | |||||
lock (_nonceIncrementLock) | |||||
{ | |||||
CryptoUtils.SodiumIncrement(isEncrypt ? encNonce : decNonce); | |||||
} | } | ||||
} | } | ||||
public virtual void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | public virtual void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | ||||
{ | { | ||||
if (isEncrypt) { | |||||
_encryptSalt = new byte[saltLen]; | |||||
Array.Copy(salt, _encryptSalt, saltLen); | |||||
} else { | |||||
_decryptSalt = new byte[saltLen]; | |||||
Array.Copy(salt, _decryptSalt, saltLen); | |||||
if (isEncrypt) | |||||
{ | |||||
encryptSalt = new byte[saltLen]; | |||||
Array.Copy(salt, encryptSalt, saltLen); | |||||
} | |||||
else | |||||
{ | |||||
decryptSalt = new byte[saltLen]; | |||||
Array.Copy(salt, decryptSalt, saltLen); | |||||
} | } | ||||
logger.Dump("Salt", salt, saltLen); | logger.Dump("Salt", salt, saltLen); | ||||
} | } | ||||
public static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } | |||||
/// <summary> | |||||
/// | |||||
/// </summary> | |||||
/// <param name="plaintext">Input, plain text</param> | |||||
/// <param name="plen">plaintext.Length</param> | |||||
/// <param name="ciphertext">Output, allocated by caller, tag space included, | |||||
/// length = plaintext.Length + tagLen, [enc][tag] order</param> | |||||
/// <param name="clen">Should be same as ciphertext.Length</param> | |||||
public abstract void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen); | public abstract void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen); | ||||
// plain -> cipher + tag | |||||
public abstract byte[] CipherEncrypt2(byte[] plain); | |||||
// cipher + tag -> plain | |||||
public abstract byte[] CipherDecrypt2(byte[] cipher); | |||||
public (Memory<byte>, Memory<byte>) GetCipherTextAndTagMem(byte[] cipher) | |||||
{ | |||||
var mc = cipher.AsMemory(); | |||||
var clen = mc.Length - tagLen; | |||||
var c = mc.Slice(0, clen); | |||||
var t = mc.Slice(clen); | |||||
return (c, t); | |||||
} | |||||
public (byte[], byte[]) GetCipherTextAndTag(byte[] cipher) | |||||
{ | |||||
var (c, t) = GetCipherTextAndTagMem(cipher); | |||||
return (c.ToArray(), t.ToArray()); | |||||
} | |||||
/// <summary> | |||||
/// | |||||
/// </summary> | |||||
/// <param name="ciphertext">Cipher text in [enc][tag] order</param> | |||||
/// <param name="clen">ciphertext.Length</param> | |||||
/// <param name="plaintext">Output plain text may with additional data allocated by caller</param> | |||||
/// <param name="plen">Output, should be used plain text length</param> | |||||
public abstract void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen); | public abstract void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen); | ||||
#region TCP | #region TCP | ||||
@@ -140,32 +174,35 @@ namespace Shadowsocks.Encryption.AEAD | |||||
_encCircularBuffer.Put(buf, 0, length); | _encCircularBuffer.Put(buf, 0, length); | ||||
outlength = 0; | outlength = 0; | ||||
logger.Debug("---Start Encryption"); | logger.Debug("---Start Encryption"); | ||||
if (! _encryptSaltSent) { | |||||
if (!_encryptSaltSent) | |||||
{ | |||||
_encryptSaltSent = true; | _encryptSaltSent = true; | ||||
// Generate salt | // Generate salt | ||||
byte[] saltBytes = new byte[saltLen]; | byte[] saltBytes = new byte[saltLen]; | ||||
randBytes(saltBytes, saltLen); | |||||
RNG.GetBytes(saltBytes, saltLen); | |||||
InitCipher(saltBytes, true, false); | InitCipher(saltBytes, true, false); | ||||
Array.Copy(saltBytes, 0, outbuf, 0, saltLen); | Array.Copy(saltBytes, 0, outbuf, 0, saltLen); | ||||
outlength = saltLen; | outlength = saltLen; | ||||
logger.Debug($"_encryptSaltSent outlength {outlength}"); | logger.Debug($"_encryptSaltSent outlength {outlength}"); | ||||
} | } | ||||
if (! _tcpRequestSent) { | |||||
if (!_tcpRequestSent) | |||||
{ | |||||
_tcpRequestSent = true; | _tcpRequestSent = true; | ||||
// The first TCP request | // The first TCP request | ||||
int encAddrBufLength; | int encAddrBufLength; | ||||
byte[] encAddrBufBytes = new byte[AddrBufLength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||||
byte[] addrBytes = _encCircularBuffer.Get(AddrBufLength); | |||||
ChunkEncrypt(addrBytes, AddrBufLength, encAddrBufBytes, out encAddrBufLength); | |||||
Debug.Assert(encAddrBufLength == AddrBufLength + tagLen * 2 + CHUNK_LEN_BYTES); | |||||
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||||
byte[] addrBytes = _encCircularBuffer.Get(AddressBufferLength); | |||||
ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength); | |||||
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES); | |||||
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}"); | ||||
} | } | ||||
// handle other chunks | // handle other chunks | ||||
while (true) { | |||||
while (true) | |||||
{ | |||||
uint bufSize = (uint)_encCircularBuffer.Size; | uint bufSize = (uint)_encCircularBuffer.Size; | ||||
if (bufSize <= 0) return; | if (bufSize <= 0) return; | ||||
var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK); | var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK); | ||||
@@ -178,12 +215,14 @@ namespace Shadowsocks.Encryption.AEAD | |||||
outlength += encChunkLength; | outlength += encChunkLength; | ||||
logger.Debug("chunks enc outlength " + outlength); | logger.Debug("chunks enc outlength " + outlength); | ||||
// check if we have enough space for outbuf | // check if we have enough space for outbuf | ||||
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize) { | |||||
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize) | |||||
{ | |||||
logger.Debug("enc outbuf almost full, giving up"); | logger.Debug("enc outbuf almost full, giving up"); | ||||
return; | return; | ||||
} | } | ||||
bufSize = (uint)_encCircularBuffer.Size; | bufSize = (uint)_encCircularBuffer.Size; | ||||
if (bufSize <= 0) { | |||||
if (bufSize <= 0) | |||||
{ | |||||
logger.Debug("No more data to encrypt, leaving"); | logger.Debug("No more data to encrypt, leaving"); | ||||
return; | return; | ||||
} | } | ||||
@@ -200,10 +239,12 @@ namespace Shadowsocks.Encryption.AEAD | |||||
_decCircularBuffer.Put(buf, 0, length); | _decCircularBuffer.Put(buf, 0, length); | ||||
logger.Debug("---Start Decryption"); | logger.Debug("---Start Decryption"); | ||||
if (! _decryptSaltReceived) { | |||||
if (!_decryptSaltReceived) | |||||
{ | |||||
bufSize = _decCircularBuffer.Size; | bufSize = _decCircularBuffer.Size; | ||||
// check if we get the leading salt | // check if we get the leading salt | ||||
if (bufSize <= saltLen) { | |||||
if (bufSize <= saltLen) | |||||
{ | |||||
// need more | // need more | ||||
return; | return; | ||||
} | } | ||||
@@ -214,16 +255,19 @@ namespace Shadowsocks.Encryption.AEAD | |||||
} | } | ||||
// handle chunks | // handle chunks | ||||
while (true) { | |||||
while (true) | |||||
{ | |||||
bufSize = _decCircularBuffer.Size; | bufSize = _decCircularBuffer.Size; | ||||
// check if we have any data | // check if we have any data | ||||
if (bufSize <= 0) { | |||||
if (bufSize <= 0) | |||||
{ | |||||
logger.Debug("No data in _decCircularBuffer"); | logger.Debug("No data in _decCircularBuffer"); | ||||
return; | return; | ||||
} | } | ||||
// first get chunk length | // first get chunk length | ||||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) { | |||||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) | |||||
{ | |||||
// so we only have chunk length and its tag? | // so we only have chunk length and its tag? | ||||
return; | return; | ||||
} | } | ||||
@@ -231,13 +275,16 @@ namespace Shadowsocks.Encryption.AEAD | |||||
#region Chunk Decryption | #region Chunk Decryption | ||||
byte[] encLenBytes = _decCircularBuffer.Peek(CHUNK_LEN_BYTES + tagLen); | byte[] encLenBytes = _decCircularBuffer.Peek(CHUNK_LEN_BYTES + tagLen); | ||||
uint decChunkLenLength = 0; | |||||
byte[] decChunkLenBytes = new byte[CHUNK_LEN_BYTES]; | |||||
//uint decChunkLenLength = 0; | |||||
//byte[] decChunkLenBytes = new byte[CHUNK_LEN_BYTES]; | |||||
// try to dec chunk len | // try to dec chunk len | ||||
cipherDecrypt(encLenBytes, CHUNK_LEN_BYTES + (uint)tagLen, decChunkLenBytes, ref decChunkLenLength); | |||||
Debug.Assert(decChunkLenLength == CHUNK_LEN_BYTES); | |||||
//cipherDecrypt(encLenBytes, CHUNK_LEN_BYTES + (uint)tagLen, decChunkLenBytes, ref decChunkLenLength); | |||||
byte[] decChunkLenBytes = CipherDecrypt2(encLenBytes); | |||||
// Debug.Assert(decChunkLenLength == CHUNK_LEN_BYTES); | |||||
// finally we get the real chunk len | // finally we get the real chunk len | ||||
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 > CHUNK_LEN_MASK) | ||||
{ | { | ||||
// we get invalid chunk | // we get invalid chunk | ||||
@@ -246,7 +293,8 @@ namespace Shadowsocks.Encryption.AEAD | |||||
} | } | ||||
logger.Debug("Get the real chunk len:" + chunkLen); | logger.Debug("Get the real chunk len:" + chunkLen); | ||||
bufSize = _decCircularBuffer.Size; | bufSize = _decCircularBuffer.Size; | ||||
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen) { | |||||
if (bufSize < CHUNK_LEN_BYTES + 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; | ||||
} | } | ||||
@@ -256,17 +304,19 @@ namespace Shadowsocks.Encryption.AEAD | |||||
// drop chunk len and its tag from buffer | // drop chunk len and its tag from buffer | ||||
_decCircularBuffer.Skip(CHUNK_LEN_BYTES + tagLen); | _decCircularBuffer.Skip(CHUNK_LEN_BYTES + tagLen); | ||||
byte[] encChunkBytes = _decCircularBuffer.Get(chunkLen + tagLen); | byte[] encChunkBytes = _decCircularBuffer.Get(chunkLen + tagLen); | ||||
byte[] decChunkBytes = new byte[chunkLen]; | |||||
uint decChunkLen = 0; | |||||
cipherDecrypt(encChunkBytes, chunkLen + (uint)tagLen, decChunkBytes, ref decChunkLen); | |||||
Debug.Assert(decChunkLen == chunkLen); | |||||
//byte[] decChunkBytes = new byte[chunkLen]; | |||||
//uint decChunkLen = 0; | |||||
//cipherDecrypt(encChunkBytes, chunkLen + (uint)tagLen, decChunkBytes, ref decChunkLen); | |||||
byte[] decChunkBytes = CipherDecrypt2(encChunkBytes); | |||||
//Debug.Assert(decChunkLen == chunkLen); | |||||
IncrementNonce(false); | IncrementNonce(false); | ||||
#endregion | #endregion | ||||
// output to outbuf | // output to outbuf | ||||
Buffer.BlockCopy(decChunkBytes, 0, outbuf, outlength, (int) decChunkLen); | |||||
outlength += (int)decChunkLen; | |||||
decChunkBytes.CopyTo(outbuf, outlength); | |||||
// Buffer.BlockCopy(decChunkBytes, 0, outbuf, outlength, (int)decChunkLen); | |||||
outlength += decChunkBytes.Length; | |||||
logger.Debug("aead dec outlength " + outlength); | logger.Debug("aead dec outlength " + outlength); | ||||
if (outlength + 100 > TCPHandler.BufferSize) | if (outlength + 100 > TCPHandler.BufferSize) | ||||
{ | { | ||||
@@ -275,7 +325,8 @@ namespace Shadowsocks.Encryption.AEAD | |||||
} | } | ||||
bufSize = _decCircularBuffer.Size; | bufSize = _decCircularBuffer.Size; | ||||
// check if we already done all of them | // check if we already done all of them | ||||
if (bufSize <= 0) { | |||||
if (bufSize <= 0) | |||||
{ | |||||
logger.Debug("No data in _decCircularBuffer, already all done"); | logger.Debug("No data in _decCircularBuffer, already all done"); | ||||
return; | return; | ||||
} | } | ||||
@@ -289,59 +340,74 @@ namespace Shadowsocks.Encryption.AEAD | |||||
public override void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | public override void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | ||||
{ | { | ||||
// Generate salt | // Generate salt | ||||
randBytes(outbuf, saltLen); | |||||
RNG.GetBytes(outbuf, saltLen); | |||||
InitCipher(outbuf, true, true); | InitCipher(outbuf, true, true); | ||||
uint olen = 0; | |||||
lock (_udpTmpBuf) { | |||||
cipherEncrypt(buf, (uint) length, _udpTmpBuf, ref olen); | |||||
Debug.Assert(olen == length + tagLen); | |||||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, saltLen, (int) olen); | |||||
outlength = (int) (saltLen + olen); | |||||
//uint olen = 0; | |||||
lock (_udpTmpBuf) | |||||
{ | |||||
//cipherEncrypt(buf, (uint)length, _udpTmpBuf, ref olen); | |||||
var plain = buf.AsSpan().Slice(0, length).ToArray(); // mmp | |||||
var cipher = CipherEncrypt2(plain); | |||||
//Debug.Assert(olen == length + tagLen); | |||||
Buffer.BlockCopy(cipher, 0, outbuf, saltLen, length + tagLen); | |||||
//Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, saltLen, (int)olen); | |||||
outlength = (int)(saltLen + cipher.Length); | |||||
} | } | ||||
} | } | ||||
public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | ||||
{ | { | ||||
InitCipher(buf, false, true); | InitCipher(buf, false, true); | ||||
uint olen = 0; | |||||
lock (_udpTmpBuf) { | |||||
//uint olen = 0; | |||||
lock (_udpTmpBuf) | |||||
{ | |||||
// copy remaining data to first pos | // copy remaining data to first pos | ||||
Buffer.BlockCopy(buf, saltLen, buf, 0, length - saltLen); | Buffer.BlockCopy(buf, saltLen, buf, 0, length - saltLen); | ||||
cipherDecrypt(buf, (uint) (length - saltLen), _udpTmpBuf, ref olen); | |||||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, 0, (int) olen); | |||||
outlength = (int) olen; | |||||
byte[] b = buf.AsSpan().Slice(0, length - saltLen).ToArray(); | |||||
byte[] o = CipherDecrypt2(b); | |||||
//cipherDecrypt(buf, (uint)(length - saltLen), _udpTmpBuf, ref olen); | |||||
Buffer.BlockCopy(o, 0, outbuf, 0, o.Length); | |||||
outlength = o.Length; | |||||
} | } | ||||
} | } | ||||
#endregion | #endregion | ||||
// we know the plaintext length before encryption, so we can do it in one operation | // we know the plaintext length before encryption, so we can do it in one operation | ||||
// plain -> [len][data] | |||||
private void ChunkEncrypt(byte[] plaintext, int plainLen, byte[] ciphertext, out int cipherLen) | private void ChunkEncrypt(byte[] plaintext, int plainLen, byte[] ciphertext, out int cipherLen) | ||||
{ | { | ||||
if (plainLen > CHUNK_LEN_MASK) { | |||||
if (plainLen > CHUNK_LEN_MASK) | |||||
{ | |||||
logger.Error("enc chunk too big"); | logger.Error("enc chunk too big"); | ||||
throw new CryptoErrorException(); | throw new CryptoErrorException(); | ||||
} | } | ||||
// encrypt len | // encrypt len | ||||
byte[] encLenBytes = new byte[CHUNK_LEN_BYTES + tagLen]; | |||||
uint encChunkLenLength = 0; | |||||
byte[] lenbuf = BitConverter.GetBytes((ushort) IPAddress.HostToNetworkOrder((short)plainLen)); | |||||
cipherEncrypt(lenbuf, CHUNK_LEN_BYTES, encLenBytes, ref encChunkLenLength); | |||||
Debug.Assert(encChunkLenLength == CHUNK_LEN_BYTES + tagLen); | |||||
//byte[] encLenBytes = new byte[CHUNK_LEN_BYTES + tagLen]; | |||||
//uint encChunkLenLength = 0; | |||||
// always 2 byte | |||||
byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plainLen)); | |||||
//cipherEncrypt(lenbuf, CHUNK_LEN_BYTES, encLenBytes, ref encChunkLenLength); | |||||
byte[] encLenBytes = CipherEncrypt2(lenbuf); | |||||
//Debug.Assert(encChunkLenLength == CHUNK_LEN_BYTES + tagLen); | |||||
IncrementNonce(true); | IncrementNonce(true); | ||||
// encrypt corresponding data | // encrypt corresponding data | ||||
byte[] encBytes = new byte[plainLen + tagLen]; | |||||
uint encBufLength = 0; | |||||
cipherEncrypt(plaintext, (uint) plainLen, encBytes, ref encBufLength); | |||||
Debug.Assert(encBufLength == plainLen + tagLen); | |||||
//byte[] encBytes = new byte[plainLen + tagLen]; | |||||
//uint encBufLength = 0; | |||||
//cipherEncrypt(plaintext, (uint)plainLen, encBytes, ref encBufLength); | |||||
byte[] encBytes = CipherEncrypt2(plaintext); | |||||
//Debug.Assert(encBufLength == plainLen + tagLen); | |||||
IncrementNonce(true); | IncrementNonce(true); | ||||
// construct outbuf | // construct outbuf | ||||
Array.Copy(encLenBytes, 0, ciphertext, 0, (int) encChunkLenLength); | |||||
Buffer.BlockCopy(encBytes, 0, ciphertext, (int) encChunkLenLength, (int) encBufLength); | |||||
cipherLen = (int) (encChunkLenLength + encBufLength); | |||||
encLenBytes.CopyTo(ciphertext, 0); | |||||
encBytes.CopyTo(ciphertext, encLenBytes.Length); | |||||
//Array.Copy(encLenBytes, 0, ciphertext, 0, (int)encChunkLenLength); | |||||
//Buffer.BlockCopy(encBytes, 0, ciphertext, (int)encChunkLenLength, (int)encBufLength); | |||||
cipherLen = encLenBytes.Length + encBytes.Length; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,81 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using NaCl.Core; | |||||
using NaCl.Core.Base; | |||||
namespace Shadowsocks.Encryption.AEAD | |||||
{ | |||||
public class AEADNaClEncryptor : AEADEncryptor | |||||
{ | |||||
SnufflePoly1305 enc; | |||||
SnufflePoly1305 dec; | |||||
public AEADNaClEncryptor(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||||
{ | |||||
base.InitCipher(salt, isEncrypt, isUdp); | |||||
DeriveSessionKey(isEncrypt ? encryptSalt : decryptSalt, | |||||
_Masterkey, sessionKey); | |||||
SnufflePoly1305 tmp; | |||||
switch (_cipher) | |||||
{ | |||||
default: | |||||
case CipherFamily.Chacha20Poly1305: | |||||
tmp = new ChaCha20Poly1305(sessionKey); | |||||
break; | |||||
case CipherFamily.XChacha20Poly1305: | |||||
tmp = new XChaCha20Poly1305(sessionKey); | |||||
break; | |||||
} | |||||
if (isEncrypt) enc = tmp; | |||||
else dec = tmp; | |||||
} | |||||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||||
{ | |||||
var pt = dec.Decrypt(ciphertext, null, decNonce); | |||||
pt.CopyTo(plaintext, 0); | |||||
plen = (uint)pt.Length; | |||||
} | |||||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||||
{ | |||||
var ct = enc.Encrypt(plaintext, null, encNonce); | |||||
ct.CopyTo(ciphertext, 0); | |||||
clen = (uint)ct.Length; | |||||
} | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"chacha20-ietf-poly1305", new CipherInfo("chacha20-ietf-poly1305",32, 32, 12, 16, CipherFamily.Chacha20Poly1305)}, | |||||
{"xchacha20-ietf-poly1305", new CipherInfo("xchacha20-ietf-poly1305",32, 32, 24, 16, CipherFamily.XChacha20Poly1305)}, | |||||
}; | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public override byte[] CipherEncrypt2(byte[] plain) | |||||
{ | |||||
return enc.Encrypt(plain, null, encNonce); | |||||
} | |||||
public override byte[] CipherDecrypt2(byte[] cipher) | |||||
{ | |||||
return dec.Decrypt(cipher, null, decNonce); | |||||
} | |||||
} | |||||
} |
@@ -1,48 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Shadowsocks.Encryption.AEAD | |||||
{ | |||||
public class AEADNativeEncryptor : AEADEncryptor | |||||
{ | |||||
public AEADNativeEncryptor(string method, string password) | |||||
: base(method, password) | |||||
{ | |||||
} | |||||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||||
{ | |||||
Array.Copy(ciphertext, plaintext, 0); | |||||
plen = clen; | |||||
} | |||||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||||
{ | |||||
Array.Copy(plaintext, ciphertext, 0); | |||||
clen = plen; | |||||
} | |||||
public override void Dispose() | |||||
{ | |||||
return; | |||||
} | |||||
private static Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo>() | |||||
{ | |||||
{"plain-fake-aead",new EncryptorInfo("PLAIN_AEAD",0,0,0,0,0) } | |||||
}; | |||||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public static IEnumerable<string> SupportedCiphers() | |||||
{ | |||||
return _ciphers.Keys; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,91 @@ | |||||
using Shadowsocks.Controller; | |||||
using System; | |||||
namespace Shadowsocks.Encryption | |||||
{ | |||||
public enum CipherFamily | |||||
{ | |||||
Plain, | |||||
Table, | |||||
AesGcm, | |||||
AesCfb, | |||||
AesCtr, | |||||
Chacha20, | |||||
Chacha20Poly1305, | |||||
XChacha20Poly1305, | |||||
Rc4, | |||||
Rc4Md5, | |||||
} | |||||
public enum CipherStandardState | |||||
{ | |||||
InUse, | |||||
Deprecated, | |||||
Hidden, | |||||
} | |||||
public class CipherParameter | |||||
{ | |||||
public int KeySize; | |||||
} | |||||
public class StreamCipherParameter : CipherParameter | |||||
{ | |||||
public int IvSize; | |||||
} | |||||
public class AEADCipherParameter : CipherParameter | |||||
{ | |||||
public int SaltSize; | |||||
public int TagSize; | |||||
public int NonceSize; | |||||
} | |||||
public class CipherInfo | |||||
{ | |||||
public string Name; | |||||
public CipherFamily Type; | |||||
public CipherParameter CipherParameter; | |||||
public CipherStandardState StandardState = CipherStandardState.InUse; | |||||
#region Stream ciphers | |||||
public CipherInfo(string name, int keySize, int ivSize, CipherFamily type) | |||||
{ | |||||
Type = type; | |||||
Name = name; | |||||
StandardState = CipherStandardState.Hidden; | |||||
CipherParameter = new StreamCipherParameter | |||||
{ | |||||
KeySize = keySize, | |||||
IvSize = ivSize, | |||||
}; | |||||
} | |||||
#endregion | |||||
#region AEAD ciphers | |||||
public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type) | |||||
{ | |||||
Type = type; | |||||
Name = name; | |||||
CipherParameter = new AEADCipherParameter | |||||
{ | |||||
KeySize = keySize, | |||||
SaltSize = saltSize, | |||||
NonceSize = nonceSize, | |||||
TagSize = tagSize, | |||||
}; | |||||
} | |||||
#endregion | |||||
public override string ToString() | |||||
{ | |||||
return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString("deprecated")})"; | |||||
} | |||||
} | |||||
} |
@@ -1,65 +1,6 @@ | |||||
namespace Shadowsocks.Encryption | namespace Shadowsocks.Encryption | ||||
{ | { | ||||
public class EncryptorInfo | |||||
{ | |||||
public int KeySize; | |||||
public int IvSize; | |||||
public int SaltSize; | |||||
public int TagSize; | |||||
public int NonceSize; | |||||
public int Type; | |||||
public string InnerLibName; | |||||
// For those who make use of internal crypto method name | |||||
// e.g. mbed TLS | |||||
#region Stream ciphers | |||||
public EncryptorInfo(string innerLibName, int keySize, int ivSize, int type) | |||||
{ | |||||
this.KeySize = keySize; | |||||
this.IvSize = ivSize; | |||||
this.Type = type; | |||||
this.InnerLibName = innerLibName; | |||||
} | |||||
public EncryptorInfo(int keySize, int ivSize, int type) | |||||
{ | |||||
this.KeySize = keySize; | |||||
this.IvSize = ivSize; | |||||
this.Type = type; | |||||
this.InnerLibName = string.Empty; | |||||
} | |||||
#endregion | |||||
#region AEAD ciphers | |||||
public EncryptorInfo(string innerLibName, int keySize, int saltSize, int nonceSize, int tagSize, int type) | |||||
{ | |||||
this.KeySize = keySize; | |||||
this.SaltSize = saltSize; | |||||
this.NonceSize = nonceSize; | |||||
this.TagSize = tagSize; | |||||
this.Type = type; | |||||
this.InnerLibName = innerLibName; | |||||
} | |||||
public EncryptorInfo(int keySize, int saltSize, int nonceSize, int tagSize, int type) | |||||
{ | |||||
this.KeySize = keySize; | |||||
this.SaltSize = saltSize; | |||||
this.NonceSize = nonceSize; | |||||
this.TagSize = tagSize; | |||||
this.Type = type; | |||||
this.InnerLibName = string.Empty; | |||||
} | |||||
#endregion | |||||
} | |||||
public abstract class EncryptorBase | |||||
: IEncryptor | |||||
public abstract class EncryptorBase : IEncryptor | |||||
{ | { | ||||
public const int MAX_INPUT_SIZE = 32768; | public const int MAX_INPUT_SIZE = 32768; | ||||
@@ -81,7 +22,7 @@ | |||||
protected string Method; | protected string Method; | ||||
protected string Password; | protected string Password; | ||||
public abstract void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | 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); | public abstract void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
@@ -90,8 +31,6 @@ | |||||
public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
public abstract void Dispose(); | |||||
public int AddrBufLength { get; set; } = - 1; | |||||
public int AddressBufferLength { get; set; } = -1; | |||||
} | } | ||||
} | } |
@@ -10,27 +10,51 @@ namespace Shadowsocks.Encryption | |||||
public static class EncryptorFactory | public static class EncryptorFactory | ||||
{ | { | ||||
private static Dictionary<string, Type> _registeredEncryptors = new Dictionary<string, Type>(); | private static Dictionary<string, Type> _registeredEncryptors = new Dictionary<string, Type>(); | ||||
private static Dictionary<string, CipherInfo> ciphers = new Dictionary<string, CipherInfo>(); | |||||
private static readonly Type[] ConstructorTypes = { typeof(string), typeof(string) }; | private static readonly Type[] ConstructorTypes = { typeof(string), typeof(string) }; | ||||
static EncryptorFactory() | static EncryptorFactory() | ||||
{ | { | ||||
foreach (string method in StreamNativeEncryptor.SupportedCiphers()) | |||||
foreach (var method in StreamTableNativeEncryptor.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method)) | |||||
_registeredEncryptors.Add(method, typeof(StreamNativeEncryptor)); | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamTableNativeEncryptor)); | |||||
} | |||||
} | } | ||||
foreach (string method in AEADNativeEncryptor.SupportedCiphers()) | |||||
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method)) | |||||
_registeredEncryptors.Add(method, typeof(AEADNativeEncryptor)); | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); | |||||
} | |||||
} | } | ||||
foreach (string method in AEADBouncyCastleEncryptor.SupportedCiphers()) | |||||
foreach (var method in AEADAesGcmNativeEncryptor.SupportedCiphers()) | |||||
{ | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeEncryptor)); | |||||
} | |||||
} | |||||
foreach (var method in AEADNaClEncryptor.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method)) | |||||
_registeredEncryptors.Add(method, typeof(AEADBouncyCastleEncryptor)); | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(AEADNaClEncryptor)); | |||||
} | |||||
} | |||||
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | |||||
{ | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -55,15 +79,25 @@ namespace Shadowsocks.Encryption | |||||
{ | { | ||||
var sb = new StringBuilder(); | var sb = new StringBuilder(); | ||||
sb.Append(Environment.NewLine); | sb.Append(Environment.NewLine); | ||||
sb.AppendLine("========================="); | |||||
sb.AppendLine("-------------------------"); | |||||
sb.AppendLine("Registered Encryptor Info"); | sb.AppendLine("Registered Encryptor Info"); | ||||
foreach (var encryptor in _registeredEncryptors) | foreach (var encryptor in _registeredEncryptors) | ||||
{ | { | ||||
sb.AppendLine(String.Format("{0}=>{1}", encryptor.Key, encryptor.Value.Name)); | sb.AppendLine(String.Format("{0}=>{1}", encryptor.Key, encryptor.Value.Name)); | ||||
} | } | ||||
sb.AppendLine("========================="); | |||||
// use ----- instead of =======, so when user paste it to Github, it won't became title | |||||
sb.AppendLine("-------------------------"); | |||||
return sb.ToString(); | return sb.ToString(); | ||||
} | } | ||||
public static CipherInfo GetCipherInfo(string name) | |||||
{ | |||||
return ciphers[name]; | |||||
} | |||||
public static IEnumerable<CipherInfo> ListAvaliableCiphers() | |||||
{ | |||||
return ciphers.Values; | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,11 +1,12 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
namespace Shadowsocks.Encryption | namespace Shadowsocks.Encryption | ||||
{ | { | ||||
public interface IEncryptor : IDisposable | |||||
public interface IEncryptor | |||||
{ | { | ||||
/* length == -1 means not used */ | /* length == -1 means not used */ | ||||
int AddrBufLength { set; get; } | |||||
int AddressBufferLength { set; get; } | |||||
void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
@@ -7,8 +7,7 @@ using Shadowsocks.Controller; | |||||
namespace Shadowsocks.Encryption.Stream | namespace Shadowsocks.Encryption.Stream | ||||
{ | { | ||||
public abstract class StreamEncryptor | |||||
: EncryptorBase | |||||
public abstract class StreamEncryptor : EncryptorBase | |||||
{ | { | ||||
// for UDP only | // for UDP only | ||||
protected static byte[] _udpTmpBuf = new byte[65536]; | protected static byte[] _udpTmpBuf = new byte[65536]; | ||||
@@ -17,7 +16,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
private ByteCircularBuffer _encCircularBuffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | private ByteCircularBuffer _encCircularBuffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | ||||
private ByteCircularBuffer _decCircularBuffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | private ByteCircularBuffer _decCircularBuffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | ||||
protected Dictionary<string, EncryptorInfo> ciphers; | |||||
protected Dictionary<string, CipherInfo> ciphers; | |||||
protected byte[] _encryptIV; | protected byte[] _encryptIV; | ||||
protected byte[] _decryptIV; | protected byte[] _decryptIV; | ||||
@@ -27,10 +26,10 @@ namespace Shadowsocks.Encryption.Stream | |||||
protected bool _encryptIVSent; | protected bool _encryptIVSent; | ||||
protected string _method; | protected string _method; | ||||
protected int _cipher; | |||||
protected CipherFamily _cipher; | |||||
// internal name in the crypto library | // internal name in the crypto library | ||||
protected string _innerLibName; | protected string _innerLibName; | ||||
protected EncryptorInfo CipherInfo; | |||||
protected CipherInfo CipherInfo; | |||||
// long-time master key | // long-time master key | ||||
protected static byte[] _key = null; | protected static byte[] _key = null; | ||||
protected int keyLen; | protected int keyLen; | ||||
@@ -43,7 +42,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
InitKey(password); | InitKey(password); | ||||
} | } | ||||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||||
private void InitEncryptorInfo(string method) | private void InitEncryptorInfo(string method) | ||||
{ | { | ||||
@@ -51,13 +50,10 @@ namespace Shadowsocks.Encryption.Stream | |||||
_method = method; | _method = method; | ||||
ciphers = getCiphers(); | ciphers = getCiphers(); | ||||
CipherInfo = ciphers[_method]; | CipherInfo = ciphers[_method]; | ||||
_innerLibName = CipherInfo.InnerLibName; | |||||
_cipher = CipherInfo.Type; | _cipher = CipherInfo.Type; | ||||
if (_cipher == 0) { | |||||
throw new System.Exception("method not found"); | |||||
} | |||||
keyLen = CipherInfo.KeySize; | |||||
ivLen = CipherInfo.IvSize; | |||||
var parameter = (StreamCipherParameter)CipherInfo.CipherParameter; | |||||
keyLen = parameter.KeySize; | |||||
ivLen = parameter.IvSize; | |||||
} | } | ||||
private void InitKey(string password) | private void InitKey(string password) | ||||
@@ -73,10 +69,14 @@ 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 = null; | ||||
while (i < keylen) { | |||||
if (i == 0) { | |||||
while (i < keylen) | |||||
{ | |||||
if (i == 0) | |||||
{ | |||||
md5sum = CryptoUtils.MD5(password); | md5sum = CryptoUtils.MD5(password); | ||||
} else { | |||||
} | |||||
else | |||||
{ | |||||
Array.Copy(md5sum, 0, result, 0, MD5_LEN); | Array.Copy(md5sum, 0, result, 0, MD5_LEN); | ||||
Array.Copy(password, 0, result, MD5_LEN, password.Length); | Array.Copy(password, 0, result, MD5_LEN, password.Length); | ||||
md5sum = CryptoUtils.MD5(result); | md5sum = CryptoUtils.MD5(result); | ||||
@@ -88,10 +88,13 @@ namespace Shadowsocks.Encryption.Stream | |||||
protected virtual void initCipher(byte[] iv, bool isEncrypt) | protected virtual void initCipher(byte[] iv, bool isEncrypt) | ||||
{ | { | ||||
if (isEncrypt) { | |||||
if (isEncrypt) | |||||
{ | |||||
_encryptIV = new byte[ivLen]; | _encryptIV = new byte[ivLen]; | ||||
Array.Copy(iv, _encryptIV, ivLen); | Array.Copy(iv, _encryptIV, ivLen); | ||||
} else { | |||||
} | |||||
else | |||||
{ | |||||
_decryptIV = new byte[ivLen]; | _decryptIV = new byte[ivLen]; | ||||
Array.Copy(iv, _decryptIV, ivLen); | Array.Copy(iv, _decryptIV, ivLen); | ||||
} | } | ||||
@@ -108,12 +111,13 @@ namespace Shadowsocks.Encryption.Stream | |||||
int cipherOffset = 0; | int cipherOffset = 0; | ||||
Debug.Assert(_encCircularBuffer != null, "_encCircularBuffer != null"); | Debug.Assert(_encCircularBuffer != null, "_encCircularBuffer != null"); | ||||
_encCircularBuffer.Put(buf, 0, length); | _encCircularBuffer.Put(buf, 0, length); | ||||
if (! _encryptIVSent) { | |||||
if (!_encryptIVSent) | |||||
{ | |||||
// Generate IV | // Generate IV | ||||
byte[] ivBytes = new byte[ivLen]; | byte[] ivBytes = new byte[ivLen]; | ||||
randBytes(ivBytes, ivLen); | randBytes(ivBytes, ivLen); | ||||
initCipher(ivBytes, true); | initCipher(ivBytes, true); | ||||
Array.Copy(ivBytes, 0, outbuf, 0, ivLen); | Array.Copy(ivBytes, 0, outbuf, 0, ivLen); | ||||
cipherOffset = ivLen; | cipherOffset = ivLen; | ||||
_encryptIVSent = true; | _encryptIVSent = true; | ||||
@@ -130,16 +134,22 @@ namespace Shadowsocks.Encryption.Stream | |||||
{ | { | ||||
Debug.Assert(_decCircularBuffer != null, "_circularBuffer != null"); | Debug.Assert(_decCircularBuffer != null, "_circularBuffer != null"); | ||||
_decCircularBuffer.Put(buf, 0, length); | _decCircularBuffer.Put(buf, 0, length); | ||||
if (! _decryptIVReceived) { | |||||
if (_decCircularBuffer.Size <= ivLen) { | |||||
if (!_decryptIVReceived) | |||||
{ | |||||
if (_decCircularBuffer.Size <= ivLen) | |||||
{ | |||||
// we need more data | // we need more data | ||||
outlength = 0; | outlength = 0; | ||||
return; | return; | ||||
} | } | ||||
// start decryption | // start decryption | ||||
_decryptIVReceived = true; | _decryptIVReceived = true; | ||||
byte[] iv = _decCircularBuffer.Get(ivLen); | |||||
initCipher(iv, false); | |||||
if (ivLen > 0) | |||||
{ | |||||
byte[] iv = _decCircularBuffer.Get(ivLen); | |||||
initCipher(iv, false); | |||||
} | |||||
else initCipher(Array.Empty<byte>(), false); | |||||
} | } | ||||
byte[] cipher = _decCircularBuffer.ToArray(); | byte[] cipher = _decCircularBuffer.ToArray(); | ||||
cipherUpdate(false, cipher.Length, cipher, outbuf); | cipherUpdate(false, cipher.Length, cipher, outbuf); | ||||
@@ -158,7 +168,8 @@ namespace Shadowsocks.Encryption.Stream | |||||
// Generate IV | // Generate IV | ||||
randBytes(outbuf, ivLen); | randBytes(outbuf, ivLen); | ||||
initCipher(outbuf, true); | initCipher(outbuf, true); | ||||
lock (_udpTmpBuf) { | |||||
lock (_udpTmpBuf) | |||||
{ | |||||
cipherUpdate(true, length, buf, _udpTmpBuf); | cipherUpdate(true, length, buf, _udpTmpBuf); | ||||
outlength = length + ivLen; | outlength = length + ivLen; | ||||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, ivLen, length); | Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, ivLen, length); | ||||
@@ -170,7 +181,8 @@ 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 (_udpTmpBuf) | |||||
{ | |||||
// C# could be multi-threaded | // C# could be multi-threaded | ||||
Buffer.BlockCopy(buf, ivLen, _udpTmpBuf, 0, length - ivLen); | Buffer.BlockCopy(buf, ivLen, _udpTmpBuf, 0, length - ivLen); | ||||
cipherUpdate(false, length - ivLen, _udpTmpBuf, outbuf); | cipherUpdate(false, length - ivLen, _udpTmpBuf, outbuf); | ||||
@@ -0,0 +1,116 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Shadowsocks.Encryption.Stream | |||||
{ | |||||
public class StreamRc4NativeEncryptor : StreamEncryptor | |||||
{ | |||||
byte[] realkey = new byte[256]; | |||||
byte[] sbox = new byte[256]; | |||||
public StreamRc4NativeEncryptor(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
base.initCipher(iv, isEncrypt); | |||||
if (_cipher == CipherFamily.Rc4Md5) | |||||
{ | |||||
byte[] temp = new byte[keyLen + ivLen]; | |||||
Array.Copy(_key, 0, temp, 0, keyLen); | |||||
Array.Copy(iv, 0, temp, keyLen, ivLen); | |||||
realkey = CryptoUtils.MD5(temp); | |||||
} | |||||
else | |||||
{ | |||||
realkey = _key; | |||||
} | |||||
sbox = SBox(realkey); | |||||
} | |||||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||||
{ | |||||
var ctx = isEncrypt ? enc_ctx : dec_ctx; | |||||
byte[] t = new byte[length]; | |||||
Array.Copy(buf, t, length); | |||||
RC4(ctx, sbox, t, length); | |||||
Array.Copy(t, outbuf, length); | |||||
} | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
// original RC4 doesn't use IV | |||||
{ "rc4", new CipherInfo("rc4", 16, 0, CipherFamily.Rc4) }, | |||||
{ "rc4-md5", new CipherInfo("rc4-md5", 16, 16, CipherFamily.Rc4Md5) }, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#region RC4 | |||||
class Context | |||||
{ | |||||
public int index1 = 0; | |||||
public int index2 = 0; | |||||
} | |||||
private Context enc_ctx = new Context(); | |||||
private Context dec_ctx = new Context(); | |||||
private byte[] SBox(byte[] key) | |||||
{ | |||||
byte[] s = new byte[256]; | |||||
for (int i = 0; i < 256; i++) | |||||
{ | |||||
s[i] = (byte)i; | |||||
} | |||||
for (int i = 0, j = 0; i < 256; i++) | |||||
{ | |||||
j = (j + key[i % key.Length] + s[i]) & 255; | |||||
Swap(s, i, j); | |||||
} | |||||
return s; | |||||
} | |||||
private void RC4(Context ctx, byte[] s, byte[] data, int length) | |||||
{ | |||||
for (int n = 0; n < length; n++) | |||||
{ | |||||
byte b = data[n]; | |||||
ctx.index1 = (ctx.index1 + 1) & 255; | |||||
ctx.index2 = (ctx.index2 + s[ctx.index1]) & 255; | |||||
Swap(s, ctx.index1, ctx.index2); | |||||
data[n] = (byte)(b ^ s[(s[ctx.index1] + s[ctx.index2]) & 255]); | |||||
} | |||||
} | |||||
private static void Swap(byte[] s, int i, int j) | |||||
{ | |||||
byte c = s[i]; | |||||
s[i] = s[j]; | |||||
s[j] = c; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -6,46 +6,19 @@ using System.Threading.Tasks; | |||||
namespace Shadowsocks.Encryption.Stream | namespace Shadowsocks.Encryption.Stream | ||||
{ | { | ||||
public class StreamNativeEncryptor : StreamEncryptor | |||||
public class StreamTableNativeEncryptor : StreamEncryptor | |||||
{ | { | ||||
const int Plain = 0; | |||||
const int Table = 1; | |||||
const int Rc4 = 2; | |||||
const int Rc4Md5 = 3; | |||||
string _password; | string _password; | ||||
byte[] realkey; | |||||
byte[] sbox; | |||||
public StreamNativeEncryptor(string method, string password) : base(method, password) | |||||
public StreamTableNativeEncryptor(string method, string password) : base(method, password) | |||||
{ | { | ||||
_password = password; | _password = password; | ||||
} | } | ||||
public override void Dispose() | |||||
{ | |||||
return; | |||||
} | |||||
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 >= Rc4) | |||||
{ | |||||
if (_cipher == Rc4Md5) | |||||
{ | |||||
byte[] temp = new byte[keyLen + ivLen]; | |||||
Array.Copy(_key, 0, temp, 0, keyLen); | |||||
Array.Copy(iv, 0, temp, keyLen, ivLen); | |||||
realkey = CryptoUtils.MD5(temp); | |||||
} | |||||
else | |||||
{ | |||||
realkey = _key; | |||||
} | |||||
sbox = SBox(realkey); | |||||
} | |||||
else if (_cipher == Table) | |||||
if (_cipher == CipherFamily.Table) | |||||
{ | { | ||||
ulong a = BitConverter.ToUInt64(CryptoUtils.MD5(Encoding.UTF8.GetBytes(_password)), 0); | ulong a = BitConverter.ToUInt64(CryptoUtils.MD5(Encoding.UTF8.GetBytes(_password)), 0); | ||||
for (int i = 0; i < 256; i++) | for (int i = 0; i < 256; i++) | ||||
@@ -65,7 +38,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
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 == Table) | |||||
if (_cipher == 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++) | ||||
@@ -73,36 +46,24 @@ namespace Shadowsocks.Encryption.Stream | |||||
outbuf[i] = table[buf[i]]; | outbuf[i] = table[buf[i]]; | ||||
} | } | ||||
} | } | ||||
else if (_cipher == Plain) | |||||
else if (_cipher == CipherFamily.Plain) | |||||
{ | { | ||||
Array.Copy(buf, outbuf, length); | Array.Copy(buf, outbuf, length); | ||||
} | } | ||||
else | |||||
{ | |||||
var ctx = isEncrypt ? enc_ctx : dec_ctx; | |||||
byte[] t = new byte[length]; | |||||
Array.Copy(buf, t, length); | |||||
RC4(ctx, sbox, t, length); | |||||
Array.Copy(t, outbuf, length); | |||||
} | |||||
} | } | ||||
private static readonly Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo> | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | { | ||||
{"plain", new EncryptorInfo("PLAIN", 0, 0, Plain) }, | |||||
{"table", new EncryptorInfo("TABLE", 0, 0, Table) }, | |||||
{ "rc4", new EncryptorInfo("RC4", 16, 0, Rc4) }, // original RC4 doesn't use IV | |||||
{ "rc4-md5", new EncryptorInfo("RC4", 16, 16, Rc4Md5) }, | |||||
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) }, | |||||
{"table", new CipherInfo("table", 0, 0, CipherFamily.Table) }, | |||||
}; | }; | ||||
public static IEnumerable<string> SupportedCiphers() | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | { | ||||
return _ciphers.Keys; | |||||
return _ciphers; | |||||
} | } | ||||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | { | ||||
return _ciphers; | return _ciphers; | ||||
} | } | ||||
@@ -156,58 +117,5 @@ namespace Shadowsocks.Encryption.Stream | |||||
return sorted; | return sorted; | ||||
} | } | ||||
#endregion | #endregion | ||||
#region RC4 | |||||
class Context | |||||
{ | |||||
public int index1 = 0; | |||||
public int index2 = 0; | |||||
} | |||||
private Context enc_ctx = new Context(); | |||||
private Context dec_ctx = new Context(); | |||||
private byte[] SBox(byte[] key) | |||||
{ | |||||
byte[] s = new byte[256]; | |||||
for (int i = 0; i < 256; i++) | |||||
{ | |||||
s[i] = (byte)i; | |||||
} | |||||
for (int i = 0, j = 0; i < 256; i++) | |||||
{ | |||||
j = (j + key[i % key.Length] + s[i]) & 255; | |||||
Swap(s, i, j); | |||||
} | |||||
return s; | |||||
} | |||||
private void RC4(Context ctx, byte[] s, byte[] data, int length) | |||||
{ | |||||
for (int n = 0; n < length; n++) | |||||
{ | |||||
byte b = data[n]; | |||||
ctx.index1 = (ctx.index1 + 1) & 255; | |||||
ctx.index2 = (ctx.index2 + s[ctx.index1]) & 255; | |||||
Swap(s, ctx.index1, ctx.index2); | |||||
data[n] = (byte)(b ^ s[(s[ctx.index1] + s[ctx.index2]) & 255]); | |||||
} | |||||
} | |||||
private static void Swap(byte[] s, int i, int j) | |||||
{ | |||||
byte c = s[i]; | |||||
s[i] = s[j]; | |||||
s[j] = c; | |||||
} | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -1,4 +1,5 @@ | |||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using Shadowsocks.Encryption; | |||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
using Shadowsocks.Properties; | using Shadowsocks.Properties; | ||||
using System; | using System; | ||||
@@ -19,98 +20,11 @@ namespace Shadowsocks.View | |||||
private bool isChange = false; | private bool isChange = false; | ||||
private class EncryptionMethod | |||||
{ | |||||
public readonly string name; | |||||
public readonly bool deprecated; | |||||
// Edit here to add/delete encryption method displayed in UI | |||||
private static string[] deprecatedMethod = new string[] | |||||
{ | |||||
"rc4-md5", | |||||
"salsa20", | |||||
"chacha20", | |||||
"bf-cfb", | |||||
"rc4", | |||||
"plain", | |||||
"table", | |||||
}; | |||||
private static string[] inuseMethod = new string[] | |||||
{ | |||||
"aes-256-gcm", | |||||
"aes-192-gcm", | |||||
"aes-128-gcm", | |||||
"chacha20-ietf-poly1305", | |||||
"xchacha20-ietf-poly1305", | |||||
"chacha20-ietf", | |||||
"aes-256-cfb", | |||||
"aes-192-cfb", | |||||
"aes-128-cfb", | |||||
"aes-256-ctr", | |||||
"aes-192-ctr", | |||||
"aes-128-ctr", | |||||
"camellia-256-cfb", | |||||
"camellia-192-cfb", | |||||
"camellia-128-cfb", | |||||
}; | |||||
public static EncryptionMethod[] AllMethods | |||||
{ | |||||
get | |||||
{ | |||||
if (!init) Init(); | |||||
return allMethods; | |||||
} | |||||
} | |||||
private static bool init = false; | |||||
private static EncryptionMethod[] allMethods; | |||||
private static Dictionary<string, EncryptionMethod> methodByName = new Dictionary<string, EncryptionMethod>(); | |||||
private static void Init() | |||||
{ | |||||
var all = new List<EncryptionMethod>(); | |||||
all.AddRange(inuseMethod.Select(i => new EncryptionMethod(i, false))); | |||||
all.AddRange(deprecatedMethod.Select(d => new EncryptionMethod(d, true))); | |||||
allMethods = all.ToArray(); | |||||
foreach (var item in all) | |||||
{ | |||||
methodByName[item.name] = item; | |||||
} | |||||
init = true; | |||||
} | |||||
public static EncryptionMethod GetMethod(string name) | |||||
{ | |||||
if (!init) Init(); | |||||
bool success = methodByName.TryGetValue(name, out EncryptionMethod method); | |||||
if (!success) | |||||
{ | |||||
string defaultMethod = Server.DefaultMethod; | |||||
MessageBox.Show(I18N.GetString("Encryption method {0} not exist, will replace with {1}", name, defaultMethod), I18N.GetString("Shadowsocks")); | |||||
return methodByName[defaultMethod]; | |||||
} | |||||
return method; | |||||
} | |||||
private EncryptionMethod(string name, bool deprecated) | |||||
{ | |||||
this.name = name; | |||||
this.deprecated = deprecated; | |||||
} | |||||
public override string ToString() | |||||
{ | |||||
return deprecated ? $"{name} ({I18N.GetString("deprecated")})" : name; | |||||
} | |||||
} | |||||
public ConfigForm(ShadowsocksController controller) | public ConfigForm(ShadowsocksController controller) | ||||
{ | { | ||||
Font = SystemFonts.MessageBoxFont; | Font = SystemFonts.MessageBoxFont; | ||||
InitializeComponent(); | InitializeComponent(); | ||||
EncryptionSelect.Items.AddRange(EncryptionMethod.AllMethods); | |||||
EncryptionSelect.Items.AddRange(EncryptorFactory.ListAvaliableCiphers().ToArray()); | |||||
// a dirty hack | // a dirty hack | ||||
ServersListBox.Dock = DockStyle.Fill; | ServersListBox.Dock = DockStyle.Fill; | ||||
@@ -205,7 +119,7 @@ namespace Shadowsocks.View | |||||
server = address, | server = address, | ||||
server_port = addressPort.Value, | server_port = addressPort.Value, | ||||
password = serverPassword, | password = serverPassword, | ||||
method = ((EncryptionMethod)EncryptionSelect.SelectedItem).name, | |||||
method = ((CipherInfo)EncryptionSelect.SelectedItem).Name, | |||||
plugin = PluginTextBox.Text, | plugin = PluginTextBox.Text, | ||||
plugin_opts = PluginOptionsTextBox.Text, | plugin_opts = PluginOptionsTextBox.Text, | ||||
plugin_args = PluginArgumentsTextBox.Text, | plugin_args = PluginArgumentsTextBox.Text, | ||||
@@ -406,7 +320,7 @@ namespace Shadowsocks.View | |||||
IPTextBox.Text = server.server; | IPTextBox.Text = server.server; | ||||
ServerPortTextBox.Text = server.server_port.ToString(); | ServerPortTextBox.Text = server.server_port.ToString(); | ||||
PasswordTextBox.Text = server.password; | PasswordTextBox.Text = server.password; | ||||
EncryptionSelect.SelectedItem = EncryptionMethod.GetMethod(server.method ?? Server.DefaultMethod); | |||||
EncryptionSelect.SelectedItem = EncryptorFactory.GetCipherInfo(server.method ?? Server.DefaultMethod); | |||||
PluginTextBox.Text = server.plugin; | PluginTextBox.Text = server.plugin; | ||||
PluginOptionsTextBox.Text = server.plugin_opts; | PluginOptionsTextBox.Text = server.plugin_opts; | ||||
PluginArgumentsTextBox.Text = server.plugin_args; | PluginArgumentsTextBox.Text = server.plugin_args; | ||||
@@ -5,8 +5,13 @@ | |||||
<package id="Costura.Fody" version="3.3.3" targetFramework="net472" /> | <package id="Costura.Fody" version="3.3.3" targetFramework="net472" /> | ||||
<package id="Fody" version="4.2.1" targetFramework="net472" developmentDependency="true" /> | <package id="Fody" version="4.2.1" targetFramework="net472" developmentDependency="true" /> | ||||
<package id="GlobalHotKey" version="1.1.0" targetFramework="net472" /> | <package id="GlobalHotKey" version="1.1.0" targetFramework="net472" /> | ||||
<package id="NaCl.Core" version="1.2.0" targetFramework="net472" /> | |||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" /> | <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" /> | ||||
<package id="NLog" version="4.6.8" targetFramework="net472" /> | <package id="NLog" version="4.6.8" targetFramework="net472" /> | ||||
<package id="StringEx.CS" version="0.3.1" targetFramework="net472" developmentDependency="true" /> | <package id="StringEx.CS" version="0.3.1" targetFramework="net472" developmentDependency="true" /> | ||||
<package id="System.Buffers" version="4.4.0" targetFramework="net472" /> | |||||
<package id="System.Memory" version="4.5.2" targetFramework="net472" /> | |||||
<package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" /> | |||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" /> | |||||
<package id="ZXing.Net" version="0.16.5" targetFramework="net472" /> | <package id="ZXing.Net" version="0.16.5" targetFramework="net472" /> | ||||
</packages> | </packages> |
@@ -11,7 +11,12 @@ | |||||
<Version>4.1.9.2</Version> | <Version>4.1.9.2</Version> | ||||
<AssemblyName>Shadowsocks</AssemblyName> | <AssemblyName>Shadowsocks</AssemblyName> | ||||
<ApplicationIcon>shadowsocks.ico</ApplicationIcon> | <ApplicationIcon>shadowsocks.ico</ApplicationIcon> | ||||
<StartupObject /> | |||||
<StartupObject>Shadowsocks.Program</StartupObject> | |||||
<Nullable>enable</Nullable> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | |||||
<DefineConstants>TRACE</DefineConstants> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -58,6 +63,7 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.5" /> | <PackageReference Include="BouncyCastle.NetCore" Version="1.8.5" /> | ||||
<!--TODO: Remove Fody related stuff, as they're either not actually used or has NET Core built-in alternate--> | |||||
<PackageReference Include="Caseless.Fody" Version="1.9.0" /> | <PackageReference Include="Caseless.Fody" Version="1.9.0" /> | ||||
<PackageReference Include="Costura.Fody" Version="4.1.0" /> | <PackageReference Include="Costura.Fody" Version="4.1.0" /> | ||||
<PackageReference Include="Fody" Version="6.1.1"> | <PackageReference Include="Fody" Version="6.1.1"> | ||||
@@ -66,16 +72,17 @@ | |||||
</PackageReference> | </PackageReference> | ||||
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" /> | <PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" /> | ||||
<PackageReference Include="NaCl.Core" Version="1.2.0" /> | |||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | ||||
<PackageReference Include="NLog" Version="4.6.8" /> | <PackageReference Include="NLog" Version="4.6.8" /> | ||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> | |||||
<PackageReference Include="ZXing.Net" Version="0.16.5" /> | |||||
<PackageReference Include="StringEx.CS" Version="0.3.1"> | <PackageReference Include="StringEx.CS" Version="0.3.1"> | ||||
<PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
</PackageReference> | </PackageReference> | ||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> | |||||
<PackageReference Include="System.Management" Version="4.7.0" /> | <PackageReference Include="System.Management" Version="4.7.0" /> | ||||
<PackageReference Include="ZXing.Net" Version="0.16.5" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -1,6 +1,7 @@ | |||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
using Shadowsocks.Encryption; | using Shadowsocks.Encryption; | ||||
using Shadowsocks.Encryption.Stream; | using Shadowsocks.Encryption.Stream; | ||||
using Shadowsocks.Encryption.AEAD; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading; | using System.Threading; | ||||
@@ -10,7 +11,7 @@ namespace Shadowsocks.Test | |||||
[TestClass] | [TestClass] | ||||
public class CryptographyTest | public class CryptographyTest | ||||
{ | { | ||||
Random random = new Random(); | |||||
[TestMethod] | [TestMethod] | ||||
public void TestMD5() | public void TestMD5() | ||||
{ | { | ||||
@@ -18,7 +19,6 @@ namespace Shadowsocks.Test | |||||
{ | { | ||||
System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create(); | System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create(); | ||||
byte[] bytes = new byte[len]; | byte[] bytes = new byte[len]; | ||||
var random = new Random(); | |||||
random.NextBytes(bytes); | random.NextBytes(bytes); | ||||
string md5str = Convert.ToBase64String(md5.ComputeHash(bytes)); | string md5str = Convert.ToBase64String(md5.ComputeHash(bytes)); | ||||
string md5str2 = Convert.ToBase64String(CryptoUtils.MD5(bytes)); | string md5str2 = Convert.ToBase64String(CryptoUtils.MD5(bytes)); | ||||
@@ -32,12 +32,12 @@ namespace Shadowsocks.Test | |||||
byte[] plain = new byte[16384]; | byte[] plain = new byte[16384]; | ||||
byte[] cipher = new byte[plain.Length + 100];// AEAD with IPv4 address type needs +100 | byte[] cipher = new byte[plain.Length + 100];// AEAD with IPv4 address type needs +100 | ||||
byte[] plain2 = new byte[plain.Length + 16]; | byte[] plain2 = new byte[plain.Length + 16]; | ||||
int outLen = 0; | |||||
int outLen2 = 0; | |||||
var random = new Random(); | |||||
//random = new Random(); | |||||
random.NextBytes(plain); | random.NextBytes(plain); | ||||
encryptor.Encrypt(plain, plain.Length, cipher, out outLen); | |||||
decryptor.Decrypt(cipher, outLen, plain2, out outLen2); | |||||
encryptor.Encrypt(plain, plain.Length, cipher, out int outLen); | |||||
decryptor.Decrypt(cipher, outLen, plain2, out int outLen2); | |||||
Assert.AreEqual(plain.Length, outLen2); | Assert.AreEqual(plain.Length, outLen2); | ||||
for (int j = 0; j < plain.Length; j++) | for (int j = 0; j < plain.Length; j++) | ||||
{ | { | ||||
@@ -59,40 +59,48 @@ namespace Shadowsocks.Test | |||||
} | } | ||||
} | } | ||||
private static bool encryptionFailed = false; | |||||
private static object locker = new object(); | |||||
const string password = "barfoo!"; | |||||
[TestMethod] | |||||
public void TestBouncyCastleAEADEncryption() | |||||
private void RunSingleEncryptionThread(Type enc, Type dec, string method) | |||||
{ | { | ||||
encryptionFailed = false; | |||||
// run it once before the multi-threading test to initialize global tables | |||||
RunSingleBouncyCastleAEADEncryptionThread(); | |||||
List<Thread> threads = new List<Thread>(); | |||||
for (int i = 0; i < 10; i++) | |||||
var ector = enc.GetConstructor(new Type[] { typeof(string), typeof(string) }); | |||||
var dctor = dec.GetConstructor(new Type[] { typeof(string), typeof(string) }); | |||||
try | |||||
{ | { | ||||
Thread t = new Thread(new ThreadStart(RunSingleBouncyCastleAEADEncryptionThread)); threads.Add(t); | |||||
t.Start(); | |||||
IEncryptor encryptor = (IEncryptor)ector.Invoke(new object[] { method, password }); | |||||
IEncryptor decryptor = (IEncryptor)dctor.Invoke(new object[] { method, password }); | |||||
encryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||||
decryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||||
for (int i = 0; i < 16; i++) | |||||
{ | |||||
RunEncryptionRound(encryptor, decryptor); | |||||
} | |||||
} | } | ||||
foreach (Thread t in threads) | |||||
catch | |||||
{ | { | ||||
t.Join(); | |||||
encryptionFailed = true; | |||||
throw; | |||||
} | } | ||||
RNG.Close(); | |||||
Assert.IsFalse(encryptionFailed); | |||||
} | } | ||||
[TestMethod] | |||||
public void TestNativeEncryption() | |||||
private static bool encryptionFailed = false; | |||||
private void TestEncryptionMethod(Type enc, string method) | |||||
{ | |||||
TestEncryptionMethod(enc, enc, method); | |||||
} | |||||
private void TestEncryptionMethod(Type enc, Type dec, string method) | |||||
{ | { | ||||
encryptionFailed = false; | encryptionFailed = false; | ||||
// run it once before the multi-threading test to initialize global tables | // run it once before the multi-threading test to initialize global tables | ||||
RunSingleNativeEncryptionThread(); | |||||
RunSingleEncryptionThread(enc, dec, method); | |||||
List<Thread> threads = new List<Thread>(); | List<Thread> threads = new List<Thread>(); | ||||
for (int i = 0; i < 10; i++) | |||||
for (int i = 0; i < 8; i++) | |||||
{ | { | ||||
Thread t = new Thread(new ThreadStart(RunSingleNativeEncryptionThread)); | |||||
threads.Add(t); | |||||
Thread t = new Thread(new ThreadStart(() => RunSingleEncryptionThread(enc, dec, method))); threads.Add(t); | |||||
t.Start(); | t.Start(); | ||||
} | } | ||||
foreach (Thread t in threads) | foreach (Thread t in threads) | ||||
@@ -103,47 +111,44 @@ namespace Shadowsocks.Test | |||||
Assert.IsFalse(encryptionFailed); | Assert.IsFalse(encryptionFailed); | ||||
} | } | ||||
private void RunSingleNativeEncryptionThread() | |||||
[TestMethod] | |||||
public void TestBouncyCastleAEADEncryption() | |||||
{ | { | ||||
try | |||||
{ | |||||
for (int i = 0; i < 100; i++) | |||||
{ | |||||
IEncryptor encryptorN; | |||||
IEncryptor decryptorN; | |||||
encryptorN = new StreamNativeEncryptor("rc4-md5", "barfoo!"); | |||||
decryptorN = new StreamNativeEncryptor("rc4-md5", "barfoo!"); | |||||
RunEncryptionRound(encryptorN, decryptorN); | |||||
} | |||||
} | |||||
catch | |||||
{ | |||||
encryptionFailed = true; | |||||
throw; | |||||
} | |||||
TestEncryptionMethod(typeof(AEADBouncyCastleEncryptor), "aes-128-gcm"); | |||||
TestEncryptionMethod(typeof(AEADBouncyCastleEncryptor), "aes-192-gcm"); | |||||
TestEncryptionMethod(typeof(AEADBouncyCastleEncryptor), "aes-256-gcm"); | |||||
} | } | ||||
private void RunSingleBouncyCastleAEADEncryptionThread() | |||||
[TestMethod] | |||||
public void TestAesGcmNativeAEADEncryption() | |||||
{ | { | ||||
try | |||||
{ | |||||
for (int i = 0; i < 100; i++) | |||||
{ | |||||
var random = new Random(); | |||||
IEncryptor encryptor; | |||||
IEncryptor decryptor; | |||||
encryptor = new Encryption.AEAD.AEADBouncyCastleEncryptor("aes-256-gcm", "barfoo!"); | |||||
encryptor.AddrBufLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||||
decryptor = new Encryption.AEAD.AEADBouncyCastleEncryptor("aes-256-gcm", "barfoo!"); | |||||
decryptor.AddrBufLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||||
RunEncryptionRound(encryptor, decryptor); | |||||
} | |||||
} | |||||
catch | |||||
{ | |||||
encryptionFailed = true; | |||||
throw; | |||||
} | |||||
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-128-gcm"); | |||||
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-192-gcm"); | |||||
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-256-gcm"); | |||||
} | |||||
[TestMethod] | |||||
public void TestNaClAEADEncryption() | |||||
{ | |||||
TestEncryptionMethod(typeof(AEADNaClEncryptor), "chacha20-ietf-poly1305"); | |||||
TestEncryptionMethod(typeof(AEADNaClEncryptor), "xchacha20-ietf-poly1305"); | |||||
} | |||||
[TestMethod] | |||||
public void TestNativeEncryption() | |||||
{ | |||||
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "plain"); | |||||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4"); | |||||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5"); | |||||
} | |||||
[TestMethod] | |||||
public void TestNativeTableEncryption() | |||||
{ | |||||
// Too slow, run once to save CPU | |||||
var enc = new StreamTableNativeEncryptor("table", "barfoo!"); | |||||
var dec = new StreamTableNativeEncryptor("table", "barfoo!"); | |||||
RunEncryptionRound(enc, dec); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -6,6 +6,10 @@ | |||||
<IsPackable>false</IsPackable> | <IsPackable>false</IsPackable> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | |||||
<WarningLevel>0</WarningLevel> | |||||
</PropertyGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="GlobalHotKeyCore" Version="1.2.0" /> | <PackageReference Include="GlobalHotKeyCore" Version="1.2.0" /> | ||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | ||||