diff --git a/Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs deleted file mode 100644 index 043f09f1..00000000 --- a/Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Cryptography; - -namespace Shadowsocks.Net.Crypto.AEAD -{ - public class AEADAesGcmNativeCrypto : AEADCrypto - { - public AEADAesGcmNativeCrypto(string method, string password) : base(method, password) - { - } - - #region Cipher Info - private static readonly Dictionary _ciphers = new Dictionary - { - {"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 GetCiphers() - { - return _ciphers; - } - - public static Dictionary SupportedCiphers() - { - return _ciphers; - } - #endregion - - AesGcm aes; - public override void InitCipher(byte[] salt, bool isEncrypt) - { - base.InitCipher(salt, isEncrypt); - aes = new AesGcm(sessionKey); - } - - public override int CipherEncrypt(ReadOnlySpan plain, Span cipher) - { - aes.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen)); - return plain.Length + tagLen; - } - - public override int CipherDecrypt(Span plain, ReadOnlySpan cipher) - { - int clen = cipher.Length - tagLen; - ReadOnlySpan ciphertxt = cipher.Slice(0, clen); - ReadOnlySpan tag = cipher.Slice(clen); - aes.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen)); - return clen; - } - } -} diff --git a/Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs deleted file mode 100644 index e2f0d37f..00000000 --- a/Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Parameters; -using Shadowsocks.Net.Crypto.Extensions; -using System; -using System.Collections.Generic; - -namespace Shadowsocks.Net.Crypto.AEAD -{ - public class AEADBouncyCastleCrypto : AEADCrypto - { - IAeadCipher aead; - bool enc; - public AEADBouncyCastleCrypto(string method, string password) : base(method, password) - { - aead = cipherFamily switch - { - CipherFamily.AesGcm => new GcmBlockCipher(new AesEngine()), - CipherFamily.Chacha20Poly1305 => new ChaCha20Poly1305(), - CipherFamily.XChacha20Poly1305 => new XChaCha20Poly1305(), - _ => throw new NotSupportedException(), - }; - } - - #region Cipher Info - private static readonly Dictionary _ciphers = new Dictionary - { - {"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)}, - {"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 GetCiphers() - { - return _ciphers; - } - - public static Dictionary SupportedCiphers() - { - return _ciphers; - } - #endregion - - public override void InitCipher(byte[] salt, bool isEncrypt) - { - base.InitCipher(salt, isEncrypt); - enc = isEncrypt; - } - - public override int CipherDecrypt(Span plain, ReadOnlySpan cipher) - { - return CipherUpdate(cipher, plain); - } - - public override int CipherEncrypt(ReadOnlySpan plain, Span cipher) - { - return CipherUpdate(plain, cipher); - } - - private int CipherUpdate(ReadOnlySpan i, Span o) - { - byte[] t = new byte[o.Length]; - byte[] n = new byte[nonce.Length]; - nonce.CopyTo(n, 0); - aead.Init(enc, new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, n)); - - int r = aead.ProcessBytes(i.ToArray(), 0, i.Length, t, 0); - r += aead.DoFinal(t, r); - t.CopyTo(o); - return r; - } - } -} diff --git a/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs index 9ac79f2c..55fc9c40 100644 --- a/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs +++ b/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs @@ -1,3 +1,4 @@ +using CryptoBase; using Shadowsocks.Net.Crypto.Exception; using Shadowsocks.Net.Crypto.Stream; using Splat; @@ -5,6 +6,7 @@ using System; using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; +using System.Security.Cryptography; using System.Text; namespace Shadowsocks.Net.Crypto.AEAD @@ -90,7 +92,7 @@ namespace Shadowsocks.Net.Crypto.AEAD this.salt = new byte[saltLen]; Array.Copy(salt, this.salt, saltLen); - CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey, 0); + HKDF.DeriveKey(HashAlgorithmName.SHA1, masterKey, sessionKey, salt, InfoBytes); this.Log().Debug($"salt {instanceId}", salt, saltLen); this.Log().Debug($"sessionkey {instanceId}", sessionKey, keyLen); @@ -272,9 +274,9 @@ namespace Shadowsocks.Net.Crypto.AEAD byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length)); int cipherLenSize = CipherEncrypt(lenbuf, cipher); - CryptoUtils.SodiumIncrement(nonce); + nonce.Increment(); int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize)); - CryptoUtils.SodiumIncrement(nonce); + nonce.Increment(); return cipherLenSize + cipherDataSize; } @@ -299,11 +301,11 @@ namespace Shadowsocks.Net.Crypto.AEAD this.Log().Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}"); return 0; } - CryptoUtils.SodiumIncrement(nonce); + nonce.Increment(); // we have enough data to decrypt one chunk // drop chunk len and its tag from buffer int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen)); - CryptoUtils.SodiumIncrement(nonce); + nonce.Increment(); this.Log().Debug($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}"); return len; } diff --git a/Shadowsocks.Net/Crypto/AEAD/AEADCryptoBaseCrypto.cs b/Shadowsocks.Net/Crypto/AEAD/AEADCryptoBaseCrypto.cs new file mode 100644 index 00000000..383b9442 --- /dev/null +++ b/Shadowsocks.Net/Crypto/AEAD/AEADCryptoBaseCrypto.cs @@ -0,0 +1,72 @@ +#nullable enable +using CryptoBase; +using CryptoBase.Abstractions.SymmetricCryptos; +using System; +using System.Collections.Generic; + +namespace Shadowsocks.Net.Crypto.AEAD +{ + public class AEADCryptoBaseCrypto : AEADCrypto + { + private IAEADCrypto? _crypto; + + public AEADCryptoBaseCrypto(string method, string password) : base(method, password) + { + } + + #region Cipher Info + private static readonly Dictionary _ciphers = new() + { + { "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) }, + { "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 GetCiphers() + { + return _ciphers; + } + + public static Dictionary SupportedCiphers() + { + return _ciphers; + } + #endregion + + public override void InitCipher(byte[] salt, bool isEncrypt) + { + base.InitCipher(salt, isEncrypt); + _crypto?.Dispose(); + + _crypto = cipherFamily switch + { + CipherFamily.AesGcm => AEADCryptoCreate.AesGcm(sessionKey), + CipherFamily.Chacha20Poly1305 => AEADCryptoCreate.ChaCha20Poly1305(sessionKey), + CipherFamily.XChacha20Poly1305 => AEADCryptoCreate.XChaCha20Poly1305(sessionKey), + _ => throw new NotSupportedException() + }; + } + + public override int CipherEncrypt(ReadOnlySpan plain, Span cipher) + { + _crypto!.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen)); + return plain.Length + tagLen; + } + + public override int CipherDecrypt(Span plain, ReadOnlySpan cipher) + { + var clen = cipher.Length - tagLen; + var ciphertxt = cipher.Slice(0, clen); + var tag = cipher.Slice(clen); + _crypto!.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen)); + return clen; + } + + public override void Dispose() + { + _crypto?.Dispose(); + } + } +} diff --git a/Shadowsocks.Net/Crypto/CryptoBase.cs b/Shadowsocks.Net/Crypto/CryptoBase.cs index f1d945ea..261c1b0d 100644 --- a/Shadowsocks.Net/Crypto/CryptoBase.cs +++ b/Shadowsocks.Net/Crypto/CryptoBase.cs @@ -44,5 +44,7 @@ namespace Shadowsocks.Net.Crypto public abstract int DecryptUDP(Span plain, ReadOnlySpan cipher); public int AddressBufferLength { get; set; } = -1; + + public abstract void Dispose(); } } diff --git a/Shadowsocks.Net/Crypto/CryptoFactory.cs b/Shadowsocks.Net/Crypto/CryptoFactory.cs index 5eb7b492..764637d7 100644 --- a/Shadowsocks.Net/Crypto/CryptoFactory.cs +++ b/Shadowsocks.Net/Crypto/CryptoFactory.cs @@ -26,46 +26,22 @@ namespace Shadowsocks.Net.Crypto _registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeCrypto)); } } - foreach (var method in StreamRc4NativeCrypto.SupportedCiphers()) - { - if (!_registeredEncryptors.ContainsKey(method.Key)) - { - ciphers.Add(method.Key, method.Value); - _registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeCrypto)); - } - } - foreach (var method in StreamAesCfbBouncyCastleCrypto.SupportedCiphers()) - { - if (!_registeredEncryptors.ContainsKey(method.Key)) - { - ciphers.Add(method.Key, method.Value); - _registeredEncryptors.Add(method.Key, typeof(StreamAesCfbBouncyCastleCrypto)); - } - } - foreach (var method in StreamChachaBouncyCastleCrypto.SupportedCiphers()) - { - if (!_registeredEncryptors.ContainsKey(method.Key)) - { - ciphers.Add(method.Key, method.Value); - _registeredEncryptors.Add(method.Key, typeof(StreamChachaBouncyCastleCrypto)); - } - } - - foreach (var method in AEADAesGcmNativeCrypto.SupportedCiphers()) + foreach (var method in StreamCryptoBaseCrypto.SupportedCiphers()) { if (!_registeredEncryptors.ContainsKey(method.Key)) { ciphers.Add(method.Key, method.Value); - _registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeCrypto)); + _registeredEncryptors.Add(method.Key, typeof(StreamCryptoBaseCrypto)); } } - foreach (var method in AEADBouncyCastleCrypto.SupportedCiphers()) + + foreach (var method in AEADCryptoBaseCrypto.SupportedCiphers()) { if (!_registeredEncryptors.ContainsKey(method.Key)) { ciphers.Add(method.Key, method.Value); - _registeredEncryptors.Add(method.Key, typeof(AEADBouncyCastleCrypto)); + _registeredEncryptors.Add(method.Key, typeof(AEADCryptoBaseCrypto)); } } } diff --git a/Shadowsocks.Net/Crypto/CryptoUtils.cs b/Shadowsocks.Net/Crypto/CryptoUtils.cs index 8aa75a0c..92a170c1 100644 --- a/Shadowsocks.Net/Crypto/CryptoUtils.cs +++ b/Shadowsocks.Net/Crypto/CryptoUtils.cs @@ -1,62 +1,14 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; -using System; -using System.Security.Cryptography; -using System.Threading; +using CryptoBase.Digests.MD5; namespace Shadowsocks.Net.Crypto { public static class CryptoUtils { - private static readonly ThreadLocal Md5Hasher = new ThreadLocal(System.Security.Cryptography.MD5.Create); - public static byte[] MD5(byte[] b) { var hash = new byte[CryptoBase.MD5Length]; - Md5Hasher.Value.TryComputeHash(b, hash, out _); - return hash; - } - // currently useless, just keep api same - public static Span MD5(Span span) - { - Span hash = new byte[CryptoBase.MD5Length]; - Md5Hasher.Value.TryComputeHash(span, hash, out _); + MD5Utils.Default(b, hash); return hash; } - - public static byte[] HKDF(int keylen, byte[] master, byte[] salt, byte[] info) - { - byte[] ret = new byte[keylen]; - IDigest degist = new Sha1Digest(); - HkdfParameters parameters = new HkdfParameters(master, salt, info); - HkdfBytesGenerator hkdf = new HkdfBytesGenerator(degist); - hkdf.Init(parameters); - hkdf.GenerateBytes(ret, 0, keylen); - return ret; - } - // currently useless, just keep api same, again - public static Span HKDF(int keylen, Span master, Span salt, Span info) - { - byte[] ret = new byte[keylen]; - IDigest degist = new Sha1Digest(); - HkdfParameters parameters = new HkdfParameters(master.ToArray(), salt.ToArray(), info.ToArray()); - HkdfBytesGenerator hkdf = new HkdfBytesGenerator(degist); - hkdf.Init(parameters); - hkdf.GenerateBytes(ret, 0, keylen); - return ret.AsSpan(); - } - - public static void SodiumIncrement(Span salt) - { - for (var i = 0; i < salt.Length; ++i) - { - if (++salt[i] != 0) - { - break; - } - } - } } } diff --git a/Shadowsocks.Net/Crypto/Extensions/Check.cs b/Shadowsocks.Net/Crypto/Extensions/Check.cs deleted file mode 100644 index 691efe31..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/Check.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Org.BouncyCastle.Crypto; - -namespace Shadowsocks.Net.Crypto.Extensions -{ - internal static class Check - { - internal static void DataLength(byte[] buf, int off, int len, string msg) - { - if (off > buf.Length - len) - { - throw new DataLengthException(msg); - } - } - - internal static void OutputLength(byte[] buf, int off, int len, string msg) - { - if (off > buf.Length - len) - { - throw new OutputLengthException(msg); - } - } - } -} \ No newline at end of file diff --git a/Shadowsocks.Net/Crypto/Extensions/MyCfbBlockCipher.cs b/Shadowsocks.Net/Crypto/Extensions/MyCfbBlockCipher.cs deleted file mode 100644 index c108d5b1..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/MyCfbBlockCipher.cs +++ /dev/null @@ -1,264 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using System; -using System.IO; - -namespace Shadowsocks.Net.Crypto.Extensions -{ - /** - * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. - */ - public class MyCfbBlockCipher - : IBlockCipher - { - private byte[] IV; - private byte[] cfbV; - private byte[] cfbOutV; - private byte[] buff; - private int _offset; - private bool encrypting; - - private readonly int blockSize; - private readonly IBlockCipher cipher; - - /** - * Basic constructor. - * - * @param cipher the block cipher to be used as the basis of the - * feedback mode. - * @param blockSize the block size in bits (note: a multiple of 8) - */ - public MyCfbBlockCipher( - IBlockCipher cipher, - int bitBlockSize) - { - this.cipher = cipher; - blockSize = bitBlockSize / 8; - IV = new byte[cipher.GetBlockSize()]; - cfbV = new byte[cipher.GetBlockSize()]; - cfbOutV = new byte[cipher.GetBlockSize()]; - buff = new byte[cipher.GetBlockSize()]; - _offset = 0; - } - /** - * return the underlying block cipher that we are wrapping. - * - * @return the underlying block cipher that we are wrapping. - */ - public IBlockCipher GetUnderlyingCipher() - { - return cipher; - } - /** - * Initialise the cipher and, possibly, the initialisation vector (IV). - * If an IV isn't passed as part of the parameter, the IV will be all zeros. - * An IV which is too short is handled in FIPS compliant fashion. - * - * @param forEncryption if true the cipher is initialised for - * encryption, if false for decryption. - * @param param the key and other data required by the cipher. - * @exception ArgumentException if the parameters argument is - * inappropriate. - */ - public void Init( - bool forEncryption, - ICipherParameters parameters) - { - encrypting = forEncryption; - if (parameters is ParametersWithIV ivParam) - { - var iv = ivParam.GetIV(); - var diff = IV.Length - iv.Length; - Array.Copy(iv, 0, IV, diff, iv.Length); - Array.Clear(IV, 0, diff); - - parameters = ivParam.Parameters; - } - Reset(); - - // if it's null, key is to be reused. - if (parameters != null) - { - cipher.Init(true, parameters); - } - } - - /** - * return the algorithm name and mode. - * - * @return the name of the underlying algorithm followed by "/CFB" - * and the block size in bits. - */ - public string AlgorithmName => $@"{cipher.AlgorithmName}/CFB{blockSize * 8}"; - - public bool IsPartialBlockOkay => true; - - /** - * return the block size we are operating at. - * - * @return the block size we are operating at (in bytes). - */ - public int GetBlockSize() - { - return blockSize; - } - - /** - * Process one block of input from the array in and write it to - * the out array. - * - * @param in the array containing the input data. - * @param inOff offset into the in array the data starts at. - * @param out the array the output data will be copied into. - * @param outOff the offset into the out array the output will start at. - * @exception DataLengthException if there isn't enough data in in, or - * space in out. - * @exception InvalidOperationException if the cipher isn't initialised. - * @return the number of bytes processed and produced. - */ - private int ProcessBlock( - byte[] input, - int inOff, - byte[] output, - int outOff, - bool change) - { - return encrypting - ? EncryptBlock(input, inOff, output, outOff, change) - : DecryptBlock(input, inOff, output, outOff, change); - } - - public int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff) - { - using var m = new MemoryStream(inBuf, inOff, inBuf.Length); - var tmp = new byte[blockSize]; - var o = new byte[outBuf.Length - outOff + blockSize * 8]; - using var outStream = new MemoryStream(o); - - var ptr = _offset; - int read; - while ((read = m.Read(buff, _offset, buff.Length - _offset)) > 0) - { - if (read + _offset < buff.Length) - { - var len = ProcessBlock(buff, 0, tmp, 0, false); - outStream.Write(tmp, 0, len); - _offset += read; - break; - } - else - { - var len = ProcessBlock(buff, 0, tmp, 0, true); - outStream.Write(tmp, 0, len); - _offset = 0; - } - } - - outStream.Seek(ptr, SeekOrigin.Begin); - var res = inBuf.Length; - outStream.Read(outBuf, outOff, res); - return res; - } - - /** - * Do the appropriate processing for CFB mode encryption. - * - * @param in the array containing the data to be encrypted. - * @param inOff offset into the in array the data starts at. - * @param out the array the encrypted data will be copied into. - * @param outOff the offset into the out array the output will start at. - * @exception DataLengthException if there isn't enough data in in, or - * space in out. - * @exception InvalidOperationException if the cipher isn't initialised. - * @return the number of bytes processed and produced. - */ - public int EncryptBlock( - byte[] input, - int inOff, - byte[] outBytes, - int outOff, - bool change) - { - if (inOff + blockSize > input.Length) - { - throw new DataLengthException("input buffer too short"); - } - if (outOff + blockSize > outBytes.Length) - { - throw new DataLengthException("output buffer too short"); - } - cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); - // - // XOR the cfbV with the plaintext producing the ciphertext - // - for (var i = 0; i < blockSize; i++) - { - outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); - } - // - // change over the input block. - // - if (change) - { - Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); - Array.Copy(outBytes, outOff, cfbV, cfbV.Length - blockSize, blockSize); - } - return blockSize; - } - /** - * Do the appropriate processing for CFB mode decryption. - * - * @param in the array containing the data to be decrypted. - * @param inOff offset into the in array the data starts at. - * @param out the array the encrypted data will be copied into. - * @param outOff the offset into the out array the output will start at. - * @exception DataLengthException if there isn't enough data in in, or - * space in out. - * @exception InvalidOperationException if the cipher isn't initialised. - * @return the number of bytes processed and produced. - */ - public int DecryptBlock( - byte[] input, - int inOff, - byte[] outBytes, - int outOff, - bool change) - { - if (inOff + blockSize > input.Length) - { - throw new DataLengthException("input buffer too short"); - } - if (outOff + blockSize > outBytes.Length) - { - throw new DataLengthException("output buffer too short"); - } - cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); - // - // change over the input block. - // - if (change) - { - Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); - Array.Copy(input, inOff, cfbV, cfbV.Length - blockSize, blockSize); - } - // - // XOR the cfbV with the ciphertext producing the plaintext - // - for (var i = 0; i < blockSize; i++) - { - outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); - } - return blockSize; - } - /** - * reset the chaining vector back to the IV and reset the underlying - * cipher. - */ - public void Reset() - { - Array.Copy(IV, 0, cfbV, 0, IV.Length); - _offset = 0; - cipher.Reset(); - } - } -} diff --git a/Shadowsocks.Net/Crypto/Extensions/MyChaChaEngine.cs b/Shadowsocks.Net/Crypto/Extensions/MyChaChaEngine.cs deleted file mode 100644 index 34569dfa..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/MyChaChaEngine.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; - -namespace Shadowsocks.Net.Crypto.Extensions -{ - /// - /// Implementation of Daniel J. Bernstein's ChaCha stream cipher. - /// - public class MyChaChaEngine : MySalsa20Engine - { - /// - /// Creates a ChaCha engine with a specific number of rounds. - /// - /// the number of rounds (must be an even number). - protected MyChaChaEngine(int rounds) : base(rounds) { } - - public override string AlgorithmName => $@"ChaCha{rounds}"; - - protected override void AdvanceCounter() - { - if (++engineState[12] == 0) - { - ++engineState[13]; - } - } - - protected override void ResetCounter() - { - engineState[12] = engineState[13] = 0; - } - - protected override void SetKey(byte[] keyBytes, byte[] ivBytes) - { - if (keyBytes != null) - { - if (keyBytes.Length != 16 && keyBytes.Length != 32) - { - throw new ArgumentException($@"{AlgorithmName} requires 128 bit or 256 bit key"); - } - - PackTauOrSigma(keyBytes.Length, engineState, 0); - - // Key - Pack.LE_To_UInt32(keyBytes, 0, engineState, 4, 4); - Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 8, 4); - } - - // IV - Pack.LE_To_UInt32(ivBytes, 0, engineState, 14, 2); - } - - protected override void GenerateKeyStream(byte[] output) - { - ChachaCore(rounds, engineState, x); - Pack.UInt32_To_LE(x, output, 0); - } - - /// - /// ChaCha function. - /// - /// The number of ChaCha rounds to execute - /// The input words. - /// The ChaCha state to modify. - private static void ChachaCore(int rounds, uint[] input, uint[] x) - { - if (input.Length != 16) - { - throw new ArgumentException(); - } - - if (x.Length != 16) - { - throw new ArgumentException(); - } - - if (rounds % 2 != 0) - { - throw new ArgumentException(@"Number of rounds must be even"); - } - - var x00 = input[0]; - var x01 = input[1]; - var x02 = input[2]; - var x03 = input[3]; - var x04 = input[4]; - var x05 = input[5]; - var x06 = input[6]; - var x07 = input[7]; - var x08 = input[8]; - var x09 = input[9]; - var x10 = input[10]; - var x11 = input[11]; - var x12 = input[12]; - var x13 = input[13]; - var x14 = input[14]; - var x15 = input[15]; - - for (var i = rounds; i > 0; i -= 2) - { - x00 += x04; - x12 = R(x12 ^ x00, 16); - x08 += x12; - x04 = R(x04 ^ x08, 12); - x00 += x04; - x12 = R(x12 ^ x00, 8); - x08 += x12; - x04 = R(x04 ^ x08, 7); - x01 += x05; - x13 = R(x13 ^ x01, 16); - x09 += x13; - x05 = R(x05 ^ x09, 12); - x01 += x05; - x13 = R(x13 ^ x01, 8); - x09 += x13; - x05 = R(x05 ^ x09, 7); - x02 += x06; - x14 = R(x14 ^ x02, 16); - x10 += x14; - x06 = R(x06 ^ x10, 12); - x02 += x06; - x14 = R(x14 ^ x02, 8); - x10 += x14; - x06 = R(x06 ^ x10, 7); - x03 += x07; - x15 = R(x15 ^ x03, 16); - x11 += x15; - x07 = R(x07 ^ x11, 12); - x03 += x07; - x15 = R(x15 ^ x03, 8); - x11 += x15; - x07 = R(x07 ^ x11, 7); - x00 += x05; - x15 = R(x15 ^ x00, 16); - x10 += x15; - x05 = R(x05 ^ x10, 12); - x00 += x05; - x15 = R(x15 ^ x00, 8); - x10 += x15; - x05 = R(x05 ^ x10, 7); - x01 += x06; - x12 = R(x12 ^ x01, 16); - x11 += x12; - x06 = R(x06 ^ x11, 12); - x01 += x06; - x12 = R(x12 ^ x01, 8); - x11 += x12; - x06 = R(x06 ^ x11, 7); - x02 += x07; - x13 = R(x13 ^ x02, 16); - x08 += x13; - x07 = R(x07 ^ x08, 12); - x02 += x07; - x13 = R(x13 ^ x02, 8); - x08 += x13; - x07 = R(x07 ^ x08, 7); - x03 += x04; - x14 = R(x14 ^ x03, 16); - x09 += x14; - x04 = R(x04 ^ x09, 12); - x03 += x04; - x14 = R(x14 ^ x03, 8); - x09 += x14; - x04 = R(x04 ^ x09, 7); - } - - x[0] = x00 + input[0]; - x[1] = x01 + input[1]; - x[2] = x02 + input[2]; - x[3] = x03 + input[3]; - x[4] = x04 + input[4]; - x[5] = x05 + input[5]; - x[6] = x06 + input[6]; - x[7] = x07 + input[7]; - x[8] = x08 + input[8]; - x[9] = x09 + input[9]; - x[10] = x10 + input[10]; - x[11] = x11 + input[11]; - x[12] = x12 + input[12]; - x[13] = x13 + input[13]; - x[14] = x14 + input[14]; - x[15] = x15 + input[15]; - } - } -} diff --git a/Shadowsocks.Net/Crypto/Extensions/MySalsa20Engine.cs b/Shadowsocks.Net/Crypto/Extensions/MySalsa20Engine.cs deleted file mode 100644 index c45f2e18..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/MySalsa20Engine.cs +++ /dev/null @@ -1,363 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Utilities; -using System; - -namespace Shadowsocks.Net.Crypto.Extensions -{ - /// - /// Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 - /// - public class MySalsa20Engine : IStreamCipher - { - public static readonly int DefaultRounds = 20; - - /** Constants */ - private const int StateSize = 16; // 16, 32 bit ints = 64 bytes - - private static readonly uint[] TauSigma = Pack.LE_To_UInt32(Strings.ToAsciiByteArray("expand 16-byte k" + "expand 32-byte k"), 0, 8); - - internal static void PackTauOrSigma(int keyLength, uint[] state, int stateOffset) - { - var tsOff = (keyLength - 16) / 4; - state[stateOffset] = TauSigma[tsOff]; - state[stateOffset + 1] = TauSigma[tsOff + 1]; - state[stateOffset + 2] = TauSigma[tsOff + 2]; - state[stateOffset + 3] = TauSigma[tsOff + 3]; - } - - protected int rounds; - - /* - * variables to hold the state of the engine - * during encryption and decryption - */ - private int index = 0; - internal uint[] engineState = new uint[StateSize]; // state - internal uint[] x = new uint[StateSize]; // internal buffer - private byte[] keyStream = new byte[StateSize * 4]; // expanded state, 64 bytes - private bool initialised = false; - - /* - * internal counter - */ - private uint cW0, cW1, cW2; - - /// - /// Creates a Salsa20 engine with a specific number of rounds. - /// - /// the number of rounds (must be an even number). - protected MySalsa20Engine(int rounds) - { - if (rounds <= 0 || (rounds & 1) != 0) - { - throw new ArgumentException("'rounds' must be a positive, even number"); - } - - this.rounds = rounds; - } - - public virtual void Init( - bool forEncryption, - ICipherParameters parameters) - { - /* - * Salsa20 encryption and decryption is completely - * symmetrical, so the 'forEncryption' is - * irrelevant. (Like 90% of stream ciphers) - */ - - var ivParams = parameters as ParametersWithIV; - if (ivParams == null) - { - throw new ArgumentException(AlgorithmName + " Init requires an IV", "parameters"); - } - - var iv = ivParams.GetIV(); - if (iv == null || iv.Length != NonceSize) - { - throw new ArgumentException(AlgorithmName + " requires exactly " + NonceSize + " bytes of IV"); - } - - var keyParam = ivParams.Parameters; - if (keyParam == null) - { - if (!initialised) - { - throw new InvalidOperationException(AlgorithmName + " KeyParameter can not be null for first initialisation"); - } - - SetKey(null, iv); - } - else if (keyParam is KeyParameter keyParameter) - { - SetKey(keyParameter.GetKey(), iv); - } - else - { - throw new ArgumentException(AlgorithmName + " Init parameters must contain a KeyParameter (or null for re-init)"); - } - - Reset(); - initialised = true; - } - - protected virtual int NonceSize => 8; - - public virtual string AlgorithmName - { - get - { - var name = @"Salsa20"; - if (rounds != DefaultRounds) - { - name += $"/{rounds}"; - } - return name; - } - } - - public virtual byte ReturnByte( - byte input) - { - if (LimitExceeded()) - { - throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV"); - } - - if (index == 0) - { - GenerateKeyStream(keyStream); - AdvanceCounter(); - } - - var output = (byte)(keyStream[index] ^ input); - index = (index + 1) & 63; - - return output; - } - - protected virtual void AdvanceCounter() - { - if (++engineState[8] == 0) - { - ++engineState[9]; - } - } - - public virtual void ProcessBytes( - byte[] inBytes, - int inOff, - int len, - byte[] outBytes, - int outOff) - { - if (!initialised) - { - throw new InvalidOperationException(AlgorithmName + " not initialised"); - } - - Check.DataLength(inBytes, inOff, len, "input buffer too short"); - Check.OutputLength(outBytes, outOff, len, "output buffer too short"); - - if (LimitExceeded((uint)len)) - { - throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV"); - } - - for (var i = 0; i < len; i++) - { - if (index == 0) - { - GenerateKeyStream(keyStream); - AdvanceCounter(); - } - outBytes[i + outOff] = (byte)(keyStream[index] ^ inBytes[i + inOff]); - index = (index + 1) & 63; - } - } - - public virtual void Reset() - { - index = 0; - ResetLimitCounter(); - ResetCounter(); - } - - protected virtual void ResetCounter() - { - engineState[8] = engineState[9] = 0; - } - - protected virtual void SetKey(byte[] keyBytes, byte[] ivBytes) - { - if (keyBytes != null) - { - if ((keyBytes.Length != 16) && (keyBytes.Length != 32)) - { - throw new ArgumentException(AlgorithmName + " requires 128 bit or 256 bit key"); - } - - var tsOff = (keyBytes.Length - 16) / 4; - engineState[0] = TauSigma[tsOff]; - engineState[5] = TauSigma[tsOff + 1]; - engineState[10] = TauSigma[tsOff + 2]; - engineState[15] = TauSigma[tsOff + 3]; - - // Key - Pack.LE_To_UInt32(keyBytes, 0, engineState, 1, 4); - Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 11, 4); - } - - // IV - Pack.LE_To_UInt32(ivBytes, 0, engineState, 6, 2); - } - - protected virtual void GenerateKeyStream(byte[] output) - { - SalsaCore(rounds, engineState, x); - Pack.UInt32_To_LE(x, output, 0); - } - - internal static void SalsaCore(int rounds, uint[] input, uint[] x) - { - if (input.Length != 16) - { - throw new ArgumentException(); - } - - if (x.Length != 16) - { - throw new ArgumentException(); - } - - if (rounds % 2 != 0) - { - throw new ArgumentException("Number of rounds must be even"); - } - - var x00 = input[0]; - var x01 = input[1]; - var x02 = input[2]; - var x03 = input[3]; - var x04 = input[4]; - var x05 = input[5]; - var x06 = input[6]; - var x07 = input[7]; - var x08 = input[8]; - var x09 = input[9]; - var x10 = input[10]; - var x11 = input[11]; - var x12 = input[12]; - var x13 = input[13]; - var x14 = input[14]; - var x15 = input[15]; - - for (var i = rounds; i > 0; i -= 2) - { - x04 ^= R((x00 + x12), 7); - x08 ^= R((x04 + x00), 9); - x12 ^= R((x08 + x04), 13); - x00 ^= R((x12 + x08), 18); - x09 ^= R((x05 + x01), 7); - x13 ^= R((x09 + x05), 9); - x01 ^= R((x13 + x09), 13); - x05 ^= R((x01 + x13), 18); - x14 ^= R((x10 + x06), 7); - x02 ^= R((x14 + x10), 9); - x06 ^= R((x02 + x14), 13); - x10 ^= R((x06 + x02), 18); - x03 ^= R((x15 + x11), 7); - x07 ^= R((x03 + x15), 9); - x11 ^= R((x07 + x03), 13); - x15 ^= R((x11 + x07), 18); - - x01 ^= R((x00 + x03), 7); - x02 ^= R((x01 + x00), 9); - x03 ^= R((x02 + x01), 13); - x00 ^= R((x03 + x02), 18); - x06 ^= R((x05 + x04), 7); - x07 ^= R((x06 + x05), 9); - x04 ^= R((x07 + x06), 13); - x05 ^= R((x04 + x07), 18); - x11 ^= R((x10 + x09), 7); - x08 ^= R((x11 + x10), 9); - x09 ^= R((x08 + x11), 13); - x10 ^= R((x09 + x08), 18); - x12 ^= R((x15 + x14), 7); - x13 ^= R((x12 + x15), 9); - x14 ^= R((x13 + x12), 13); - x15 ^= R((x14 + x13), 18); - } - - x[0] = x00 + input[0]; - x[1] = x01 + input[1]; - x[2] = x02 + input[2]; - x[3] = x03 + input[3]; - x[4] = x04 + input[4]; - x[5] = x05 + input[5]; - x[6] = x06 + input[6]; - x[7] = x07 + input[7]; - x[8] = x08 + input[8]; - x[9] = x09 + input[9]; - x[10] = x10 + input[10]; - x[11] = x11 + input[11]; - x[12] = x12 + input[12]; - x[13] = x13 + input[13]; - x[14] = x14 + input[14]; - x[15] = x15 + input[15]; - } - - /** - * Rotate left - * - * @param x value to rotate - * @param y amount to rotate x - * - * @return rotated x - */ - internal static uint R(uint x, int y) - { - return (x << y) | (x >> (32 - y)); - } - - private void ResetLimitCounter() - { - cW0 = 0; - cW1 = 0; - cW2 = 0; - } - - private bool LimitExceeded() - { - if (++cW0 == 0) - { - if (++cW1 == 0) - { - return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) - } - } - - return false; - } - - /* - * this relies on the fact len will always be positive. - */ - private bool LimitExceeded( - uint len) - { - var old = cW0; - cW0 += len; - if (cW0 < old) - { - if (++cW1 == 0) - { - return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) - } - } - - return false; - } - } -} diff --git a/Shadowsocks.Net/Crypto/Extensions/Pack.cs b/Shadowsocks.Net/Crypto/Extensions/Pack.cs deleted file mode 100644 index 6f851455..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/Pack.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace Shadowsocks.Net.Crypto.Extensions -{ - internal static class Pack - { - private static void UInt32_To_LE(uint n, byte[] bs, int off) - { - bs[off] = (byte)n; - bs[off + 1] = (byte)(n >> 8); - bs[off + 2] = (byte)(n >> 16); - bs[off + 3] = (byte)(n >> 24); - } - - internal static void UInt32_To_LE(uint[] ns, byte[] bs, int off) - { - foreach (var nsb in ns) - { - UInt32_To_LE(nsb, bs, off); - off += 4; - } - } - - private static uint LE_To_UInt32(byte[] bs, int off) - { - return bs[off] - | (uint)bs[off + 1] << 8 - | (uint)bs[off + 2] << 16 - | (uint)bs[off + 3] << 24; - } - - internal static void LE_To_UInt32(byte[] bs, int bOff, uint[] ns, int nOff, int count) - { - for (var i = 0; i < count; ++i) - { - ns[nOff + i] = LE_To_UInt32(bs, bOff); - bOff += 4; - } - } - - internal static uint[] LE_To_UInt32(byte[] bs, int off, int count) - { - var ns = new uint[count]; - for (var i = 0; i < ns.Length; ++i) - { - ns[i] = LE_To_UInt32(bs, off); - off += 4; - } - return ns; - } - - internal static void UInt64_To_LE(ulong n, byte[] bs, int off) - { - UInt32_To_LE((uint)n, bs, off); - UInt32_To_LE((uint)(n >> 32), bs, off + 4); - } - } -} diff --git a/Shadowsocks.Net/Crypto/Extensions/XChaCha20Engine.cs b/Shadowsocks.Net/Crypto/Extensions/XChaCha20Engine.cs deleted file mode 100644 index f3fbd319..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/XChaCha20Engine.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Org.BouncyCastle.Utilities; -using System; - -namespace Shadowsocks.Net.Crypto.Extensions -{ - public class XChaCha20Engine : MyChaChaEngine - { - public XChaCha20Engine() : base(20) - { - } - - protected override int NonceSize => 24; - - public override string AlgorithmName => @"XChaCha20"; - - private static readonly uint[] Sigma = Pack.LE_To_UInt32(Strings.ToAsciiByteArray("expand 32-byte k"), 0, 4); - - protected override void SetKey(byte[] keyBytes, byte[] ivBytes) - { - base.SetKey(keyBytes, ivBytes); - - if (keyBytes == null || keyBytes.Length != 32) - { - throw new ArgumentException($@"{AlgorithmName} requires a 256 bit key"); - } - - if (ivBytes == null || ivBytes.Length != NonceSize) - { - throw new ArgumentException($@"{AlgorithmName} requires a 192 bit nonce"); - } - - var nonceInt = Pack.LE_To_UInt32(ivBytes, 0, 6); - - var chachaKey = HChaCha20Internal(keyBytes, nonceInt); - SetSigma(engineState); - SetKey(engineState, chachaKey); - engineState[12] = 1; // Counter - engineState[13] = 0; - engineState[14] = nonceInt[4]; - engineState[15] = nonceInt[5]; - } - - private static uint[] HChaCha20Internal(byte[] key, uint[] nonceInt) - { - var x = new uint[16]; - var intKey = Pack.LE_To_UInt32(key, 0, 8); - - SetSigma(x); - SetKey(x, intKey); - SetIntNonce(x, nonceInt); - DoubleRound(x); - Array.Copy(x, 12, x, 4, 4); - return x; - } - - private static void SetSigma(uint[] state) - { - Array.Copy(Sigma, 0, state, 0, Sigma.Length); - } - - private static void SetKey(uint[] state, uint[] key) - { - Array.Copy(key, 0, state, 4, 8); - } - - private static void SetIntNonce(uint[] state, uint[] nonce) - { - Array.Copy(nonce, 0, state, 12, 4); - } - - private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d) - { - x[a] += x[b]; - x[d] = R(x[d] ^ x[a], 16); - x[c] += x[d]; - x[b] = R(x[b] ^ x[c], 12); - x[a] += x[b]; - x[d] = R(x[d] ^ x[a], 8); - x[c] += x[d]; - x[b] = R(x[b] ^ x[c], 7); - } - - private static void DoubleRound(uint[] state) - { - for (var i = 0; i < 10; ++i) - { - QuarterRound(state, 0, 4, 8, 12); - QuarterRound(state, 1, 5, 9, 13); - QuarterRound(state, 2, 6, 10, 14); - QuarterRound(state, 3, 7, 11, 15); - QuarterRound(state, 0, 5, 10, 15); - QuarterRound(state, 1, 6, 11, 12); - QuarterRound(state, 2, 7, 8, 13); - QuarterRound(state, 3, 4, 9, 14); - } - } - } -} diff --git a/Shadowsocks.Net/Crypto/Extensions/XChaCha20Poly1305.cs b/Shadowsocks.Net/Crypto/Extensions/XChaCha20Poly1305.cs deleted file mode 100644 index e28b95be..00000000 --- a/Shadowsocks.Net/Crypto/Extensions/XChaCha20Poly1305.cs +++ /dev/null @@ -1,588 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Macs; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Utilities; -using System; - -namespace Shadowsocks.Net.Crypto.Extensions -{ - public sealed class XChaCha20Poly1305 : IAeadCipher - { - private enum State - { - Uninitialized = 0, - EncInit = 1, - EncAad = 2, - EncData = 3, - EncFinal = 4, - DecInit = 5, - DecAad = 6, - DecData = 7, - DecFinal = 8, - } - - private const int BufSize = 64; - private const int KeySize = 32; - private const int NonceSize = 24; - private const int MacSize = 16; - private static readonly byte[] Zeroes = new byte[MacSize - 1]; - - private const ulong AadLimit = ulong.MaxValue; - private const ulong DataLimit = ((1UL << 32) - 1) * 64; - - private readonly XChaCha20Engine mChacha20; - private readonly IMac mPoly1305; - - private readonly byte[] mKey = new byte[KeySize]; - private readonly byte[] mNonce = new byte[NonceSize]; - private readonly byte[] mBuf = new byte[BufSize + MacSize]; - private readonly byte[] mMac = new byte[MacSize]; - - private byte[] mInitialAad; - - private ulong mAadCount; - private ulong mDataCount; - private State mState = State.Uninitialized; - private int mBufPos; - - public XChaCha20Poly1305() : this(new Poly1305()) - { - } - - public XChaCha20Poly1305(IMac poly1305) - { - if (null == poly1305) - { - throw new ArgumentNullException(nameof(poly1305)); - } - - if (MacSize != poly1305.GetMacSize()) - { - throw new ArgumentException("must be a 128-bit MAC", nameof(poly1305)); - } - - mChacha20 = new XChaCha20Engine(); - mPoly1305 = poly1305; - } - - public string AlgorithmName => @"XChaCha20Poly1305"; - - public void Init(bool forEncryption, ICipherParameters parameters) - { - KeyParameter initKeyParam; - byte[] initNonce; - ICipherParameters chacha20Params; - - if (parameters is AeadParameters aeadParams) - { - var macSizeBits = aeadParams.MacSize; - if (MacSize * 8 != macSizeBits) - { - throw new ArgumentException("Invalid value for MAC size: " + macSizeBits); - } - - initKeyParam = aeadParams.Key; - initNonce = aeadParams.GetNonce(); - chacha20Params = new ParametersWithIV(initKeyParam, initNonce); - - mInitialAad = aeadParams.GetAssociatedText(); - } - else if (parameters is ParametersWithIV ivParams) - { - initKeyParam = (KeyParameter)ivParams.Parameters; - initNonce = ivParams.GetIV(); - chacha20Params = ivParams; - - mInitialAad = null; - } - else - { - throw new ArgumentException("invalid parameters passed to ChaCha20Poly1305", nameof(parameters)); - } - - // Validate key - if (null == initKeyParam) - { - if (State.Uninitialized == mState) - { - throw new ArgumentException("Key must be specified in initial init"); - } - } - else - { - if (KeySize != initKeyParam.GetKey().Length) - { - throw new ArgumentException("Key must be 256 bits"); - } - } - - // Validate nonce - if (null == initNonce || NonceSize != initNonce.Length) - { - throw new ArgumentException("Nonce must be 192 bits"); - } - - // Check for encryption with reused nonce - if (State.Uninitialized != mState && forEncryption && Arrays.AreEqual(mNonce, initNonce)) - { - if (null == initKeyParam || Arrays.AreEqual(mKey, initKeyParam.GetKey())) - { - throw new ArgumentException("cannot reuse nonce for ChaCha20Poly1305 encryption"); - } - } - - if (null != initKeyParam) - { - Array.Copy(initKeyParam.GetKey(), 0, mKey, 0, KeySize); - } - - Array.Copy(initNonce, 0, mNonce, 0, NonceSize); - - mChacha20.Init(true, chacha20Params); - - mState = forEncryption ? State.EncInit : State.DecInit; - - Reset(true, false); - } - - public int GetOutputSize(int len) - { - var total = Math.Max(0, len) + mBufPos; - - switch (mState) - { - case State.DecInit: - case State.DecAad: - case State.DecData: - return Math.Max(0, total - MacSize); - case State.EncInit: - case State.EncAad: - case State.EncData: - return total + MacSize; - default: - throw new InvalidOperationException(); - } - } - - public int GetUpdateOutputSize(int len) - { - var total = Math.Max(0, len) + mBufPos; - - switch (mState) - { - case State.DecInit: - case State.DecAad: - case State.DecData: - total = Math.Max(0, total - MacSize); - break; - case State.EncInit: - case State.EncAad: - case State.EncData: - break; - default: - throw new InvalidOperationException(); - } - - return total - total % BufSize; - } - - public void ProcessAadByte(byte input) - { - CheckAad(); - - mAadCount = IncrementCount(mAadCount, 1, AadLimit); - mPoly1305.Update(input); - } - - public void ProcessAadBytes(byte[] inBytes, int inOff, int len) - { - if (null == inBytes) - { - throw new ArgumentNullException(nameof(inBytes)); - } - - if (inOff < 0) - { - throw new ArgumentException("cannot be negative", nameof(inOff)); - } - - if (len < 0) - { - throw new ArgumentException("cannot be negative", nameof(len)); - } - - Check.DataLength(inBytes, inOff, len, "input buffer too short"); - - CheckAad(); - - if (len > 0) - { - mAadCount = IncrementCount(mAadCount, (uint)len, AadLimit); - mPoly1305.BlockUpdate(inBytes, inOff, len); - } - } - - public int ProcessByte(byte input, byte[] outBytes, int outOff) - { - CheckData(); - - switch (mState) - { - case State.DecData: - { - mBuf[mBufPos] = input; - if (++mBufPos == mBuf.Length) - { - mPoly1305.BlockUpdate(mBuf, 0, BufSize); - ProcessData(mBuf, 0, BufSize, outBytes, outOff); - Array.Copy(mBuf, BufSize, mBuf, 0, MacSize); - mBufPos = MacSize; - return BufSize; - } - - return 0; - } - case State.EncData: - { - mBuf[mBufPos] = input; - if (++mBufPos == BufSize) - { - ProcessData(mBuf, 0, BufSize, outBytes, outOff); - mPoly1305.BlockUpdate(outBytes, outOff, BufSize); - mBufPos = 0; - return BufSize; - } - - return 0; - } - default: - throw new InvalidOperationException(); - } - } - - public int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff) - { - if (null == inBytes) - { - throw new ArgumentNullException(nameof(inBytes)); - } - - if (null == outBytes) - { - throw new ArgumentNullException(nameof(outBytes)); - } - - if (inOff < 0) - { - throw new ArgumentException("cannot be negative", nameof(inOff)); - } - - if (len < 0) - { - throw new ArgumentException("cannot be negative", nameof(len)); - } - - Check.DataLength(inBytes, inOff, len, "input buffer too short"); - if (outOff < 0) - { - throw new ArgumentException("cannot be negative", nameof(outOff)); - } - - CheckData(); - - var resultLen = 0; - - switch (mState) - { - case State.DecData: - { - for (var i = 0; i < len; ++i) - { - mBuf[mBufPos] = inBytes[inOff + i]; - if (++mBufPos == mBuf.Length) - { - mPoly1305.BlockUpdate(mBuf, 0, BufSize); - ProcessData(mBuf, 0, BufSize, outBytes, outOff + resultLen); - Array.Copy(mBuf, BufSize, mBuf, 0, MacSize); - mBufPos = MacSize; - resultLen += BufSize; - } - } - break; - } - case State.EncData: - { - if (mBufPos != 0) - { - while (len > 0) - { - --len; - mBuf[mBufPos] = inBytes[inOff++]; - if (++mBufPos == BufSize) - { - ProcessData(mBuf, 0, BufSize, outBytes, outOff); - mPoly1305.BlockUpdate(outBytes, outOff, BufSize); - mBufPos = 0; - resultLen = BufSize; - break; - } - } - } - - while (len >= BufSize) - { - ProcessData(inBytes, inOff, BufSize, outBytes, outOff + resultLen); - mPoly1305.BlockUpdate(outBytes, outOff + resultLen, BufSize); - inOff += BufSize; - len -= BufSize; - resultLen += BufSize; - } - - if (len > 0) - { - Array.Copy(inBytes, inOff, mBuf, 0, len); - mBufPos = len; - } - break; - } - default: - throw new InvalidOperationException(); - } - - return resultLen; - } - - public int DoFinal(byte[] outBytes, int outOff) - { - if (null == outBytes) - { - throw new ArgumentNullException(nameof(outBytes)); - } - - if (outOff < 0) - { - throw new ArgumentException("cannot be negative", nameof(outOff)); - } - - CheckData(); - - Array.Clear(mMac, 0, MacSize); - - int resultLen; - - switch (mState) - { - case State.DecData: - { - if (mBufPos < MacSize) - { - throw new InvalidCipherTextException("data too short"); - } - - resultLen = mBufPos - MacSize; - - Check.OutputLength(outBytes, outOff, resultLen, "output buffer too short"); - - if (resultLen > 0) - { - mPoly1305.BlockUpdate(mBuf, 0, resultLen); - ProcessData(mBuf, 0, resultLen, outBytes, outOff); - } - - FinishData(State.DecFinal); - - if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen)) - { - throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed"); - } - - break; - } - case State.EncData: - { - resultLen = mBufPos + MacSize; - - Check.OutputLength(outBytes, outOff, resultLen, "output buffer too short"); - - if (mBufPos > 0) - { - ProcessData(mBuf, 0, mBufPos, outBytes, outOff); - mPoly1305.BlockUpdate(outBytes, outOff, mBufPos); - } - - FinishData(State.EncFinal); - - Array.Copy(mMac, 0, outBytes, outOff + mBufPos, MacSize); - break; - } - default: - throw new InvalidOperationException(); - } - - Reset(false, true); - - return resultLen; - } - - public byte[] GetMac() - { - return Arrays.Clone(mMac); - } - - public void Reset() - { - Reset(true, true); - } - - private void CheckAad() - { - switch (mState) - { - case State.DecInit: - mState = State.DecAad; - break; - case State.EncInit: - mState = State.EncAad; - break; - case State.DecAad: - case State.EncAad: - break; - case State.EncFinal: - throw new InvalidOperationException("ChaCha20Poly1305 cannot be reused for encryption"); - default: - throw new InvalidOperationException(); - } - } - - private void CheckData() - { - switch (mState) - { - case State.DecInit: - case State.DecAad: - FinishAad(State.DecData); - break; - case State.EncInit: - case State.EncAad: - FinishAad(State.EncData); - break; - case State.DecData: - case State.EncData: - break; - case State.EncFinal: - throw new InvalidOperationException("ChaCha20Poly1305 cannot be reused for encryption"); - default: - throw new InvalidOperationException(); - } - } - - private void FinishAad(State nextState) - { - PadMac(mAadCount); - - mState = nextState; - } - - private void FinishData(State nextState) - { - PadMac(mDataCount); - - var lengths = new byte[16]; - Pack.UInt64_To_LE(mAadCount, lengths, 0); - Pack.UInt64_To_LE(mDataCount, lengths, 8); - mPoly1305.BlockUpdate(lengths, 0, 16); - - mPoly1305.DoFinal(mMac, 0); - - mState = nextState; - } - - private ulong IncrementCount(ulong count, uint increment, ulong limit) - { - if (count > limit - increment) - { - throw new InvalidOperationException("Limit exceeded"); - } - - return count + increment; - } - - private void InitMac() - { - var firstBlock = new byte[64]; - try - { - mChacha20.ProcessBytes(firstBlock, 0, 64, firstBlock, 0); - mPoly1305.Init(new KeyParameter(firstBlock, 0, 32)); - } - finally - { - Array.Clear(firstBlock, 0, 64); - } - } - - private void PadMac(ulong count) - { - var partial = (int)count % MacSize; - if (0 != partial) - { - mPoly1305.BlockUpdate(Zeroes, 0, MacSize - partial); - } - } - - private void ProcessData(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff) - { - Check.OutputLength(outBytes, outOff, inLen, "output buffer too short"); - - mChacha20.ProcessBytes(inBytes, inOff, inLen, outBytes, outOff); - - mDataCount = IncrementCount(mDataCount, (uint)inLen, DataLimit); - } - - private void Reset(bool clearMac, bool resetCipher) - { - Array.Clear(mBuf, 0, mBuf.Length); - - if (clearMac) - { - Array.Clear(mMac, 0, mMac.Length); - } - - mAadCount = 0UL; - mDataCount = 0UL; - mBufPos = 0; - - switch (mState) - { - case State.DecInit: - case State.EncInit: - break; - case State.DecAad: - case State.DecData: - case State.DecFinal: - mState = State.DecInit; - break; - case State.EncAad: - case State.EncData: - case State.EncFinal: - mState = State.EncFinal; - return; - default: - throw new InvalidOperationException(); - } - - if (resetCipher) - { - mChacha20.Reset(); - } - - InitMac(); - - if (null != mInitialAad) - { - ProcessAadBytes(mInitialAad, 0, mInitialAad.Length); - } - } - } -} diff --git a/Shadowsocks.Net/Crypto/ICrypto.cs b/Shadowsocks.Net/Crypto/ICrypto.cs index 3967fe0c..b128d11c 100644 --- a/Shadowsocks.Net/Crypto/ICrypto.cs +++ b/Shadowsocks.Net/Crypto/ICrypto.cs @@ -2,7 +2,7 @@ using System; namespace Shadowsocks.Net.Crypto { - public interface ICrypto + public interface ICrypto : IDisposable { int Encrypt(ReadOnlySpan plain, Span cipher); int Decrypt(Span plain, ReadOnlySpan cipher); diff --git a/Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs deleted file mode 100644 index e9675774..00000000 --- a/Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -using Shadowsocks.Net.Crypto.Extensions; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace Shadowsocks.Net.Crypto.Stream -{ - public class StreamAesCfbBouncyCastleCrypto : StreamCrypto - { - private readonly MyCfbBlockCipher b; - public StreamAesCfbBouncyCastleCrypto(string method, string password) : base(method, password) - { - b = new MyCfbBlockCipher(new AesEngine(), 128); - } - - protected override void InitCipher(byte[] iv, bool isEncrypt) - { - base.InitCipher(iv, isEncrypt); - b.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); - } - - protected override int CipherEncrypt(ReadOnlySpan plain, Span cipher) - { - return CipherUpdate(plain, cipher); - } - - protected override int CipherDecrypt(Span plain, ReadOnlySpan cipher) - { - return CipherUpdate(cipher, plain); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CipherUpdate(ReadOnlySpan input, Span output) - { - var i = input.ToArray(); - var o = new byte[i.Length]; - var res = b.ProcessBlock(i, 0, o, 0); - o.CopyTo(output); - return res; - } - - #region Cipher Info - private static readonly Dictionary _ciphers = new Dictionary - { - {"aes-128-cfb",new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, - {"aes-192-cfb",new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, - {"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, - }; - - public static Dictionary SupportedCiphers() - { - return _ciphers; - } - - protected override Dictionary GetCiphers() - { - return _ciphers; - } - #endregion - } -} diff --git a/Shadowsocks.Net/Crypto/Stream/StreamChachaBouncyCastleCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamChachaBouncyCastleCrypto.cs deleted file mode 100644 index 83a0e650..00000000 --- a/Shadowsocks.Net/Crypto/Stream/StreamChachaBouncyCastleCrypto.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -using System; -using System.Collections.Generic; - -namespace Shadowsocks.Net.Crypto.Stream -{ - public class StreamChachaBouncyCastleCrypto : StreamCrypto - { - private readonly BufferedCipherBase _encryptor; - - public StreamChachaBouncyCastleCrypto(string method, string password) : base(method, password) - { - _encryptor = new BufferedStreamCipher(new ChaCha7539Engine()); - } - - protected override void InitCipher(byte[] iv, bool isEncrypt) - { - base.InitCipher(iv, isEncrypt); - _encryptor.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); - } - - protected override int CipherEncrypt(ReadOnlySpan plain, Span cipher) - { - return CipherUpdate(plain, cipher); - } - - protected override int CipherDecrypt(Span plain, ReadOnlySpan cipher) - { - return CipherUpdate(cipher, plain); - } - - protected virtual int CipherUpdate(ReadOnlySpan input, Span output) - { - var i = input.ToArray(); - var o = new byte[_encryptor.GetOutputSize(i.Length)]; - var res = _encryptor.ProcessBytes(i, 0, i.Length, o, 0); - o.CopyTo(output); - return res; - } - - #region Cipher Info - private static readonly Dictionary _ciphers = new Dictionary - { - { "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, - }; - public static Dictionary SupportedCiphers() - { - return _ciphers; - } - - protected override Dictionary GetCiphers() - { - return _ciphers; - } - #endregion - } -} diff --git a/Shadowsocks.Net/Crypto/Stream/StreamCryptoBaseCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamCryptoBaseCrypto.cs new file mode 100644 index 00000000..aed5edfa --- /dev/null +++ b/Shadowsocks.Net/Crypto/Stream/StreamCryptoBaseCrypto.cs @@ -0,0 +1,89 @@ +#nullable enable +using CryptoBase; +using CryptoBase.Abstractions.SymmetricCryptos; +using CryptoBase.Digests.MD5; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Shadowsocks.Net.Crypto.Stream +{ + public class StreamCryptoBaseCrypto : StreamCrypto + { + private IStreamCrypto? _crypto; + + public StreamCryptoBaseCrypto(string method, string password) : base(method, password) + { + } + + protected override void InitCipher(byte[] iv, bool isEncrypt) + { + base.InitCipher(iv, isEncrypt); + _crypto?.Dispose(); + + if (cipherFamily == CipherFamily.Rc4Md5) + { + Span temp = stackalloc byte[keyLen + ivLen]; + var realKey = new byte[MD5Length]; + key.CopyTo(temp); + iv.CopyTo(temp.Slice(keyLen)); + MD5Utils.Fast440(temp, realKey); + + _crypto = StreamCryptoCreate.Rc4(realKey); + return; + } + + _crypto = cipherFamily switch + { + CipherFamily.AesCfb => StreamCryptoCreate.AesCfb(isEncrypt, key, iv), + CipherFamily.Chacha20 => StreamCryptoCreate.ChaCha20(key, iv), + CipherFamily.Rc4 => StreamCryptoCreate.Rc4(key), + _ => throw new NotSupportedException() + }; + } + + protected override int CipherEncrypt(ReadOnlySpan plain, Span cipher) + { + return CipherUpdate(plain, cipher); + } + + protected override int CipherDecrypt(Span plain, ReadOnlySpan cipher) + { + return CipherUpdate(cipher, plain); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CipherUpdate(ReadOnlySpan input, Span output) + { + _crypto!.Update(input, output); + return input.Length; + } + + #region Cipher Info + private static readonly Dictionary _ciphers = new() + { + { "aes-128-cfb", new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) }, + { "aes-192-cfb", new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) }, + { "aes-256-cfb", new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) }, + { "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, + { "rc4", new CipherInfo("rc4", 16, 0, CipherFamily.Rc4) }, + { "rc4-md5", new CipherInfo("rc4-md5", 16, 16, CipherFamily.Rc4Md5) }, + }; + + public static Dictionary SupportedCiphers() + { + return _ciphers; + } + + protected override Dictionary GetCiphers() + { + return _ciphers; + } + #endregion + + public override void Dispose() + { + _crypto?.Dispose(); + } + } +} diff --git a/Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs index c09fda2d..90bd9b69 100644 --- a/Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs +++ b/Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs @@ -39,5 +39,7 @@ namespace Shadowsocks.Net.Crypto.Stream return _ciphers; } #endregion + + public override void Dispose() { } } } diff --git a/Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs b/Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs deleted file mode 100644 index 64b96996..00000000 --- a/Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace Shadowsocks.Net.Crypto.Stream -{ - public class StreamRc4NativeCrypto : StreamCrypto - { - byte[] realkey = new byte[256]; - byte[] sbox = new byte[256]; - public StreamRc4NativeCrypto(string method, string password) : base(method, password) - { - } - - protected override void InitCipher(byte[] iv, bool isEncrypt) - { - base.InitCipher(iv, isEncrypt); - // rc4-md5 is rc4 with md5 based session key - if (cipherFamily == 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 int CipherEncrypt(ReadOnlySpan plain, Span cipher) - { - return CipherUpdate(plain, cipher); - } - - protected override int CipherDecrypt(Span plain, ReadOnlySpan cipher) - { - return CipherUpdate(cipher, plain); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CipherUpdate(ReadOnlySpan i, Span o) - { - // don't know why we need third array, but it works... - Span t = new byte[i.Length]; - i.CopyTo(t); - RC4(sbox, t, t.Length); - t.CopyTo(o); - return t.Length; - } - - #region Cipher Info - private static readonly Dictionary _ciphers = new Dictionary - { - // 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 SupportedCiphers() - { - return _ciphers; - } - - protected override Dictionary GetCiphers() - { - return _ciphers; - } - #endregion - - #region RC4 - - int index1 = 0; - int index2 = 0; - - 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(Span s, Span data, int length) - { - for (int n = 0; n < length; n++) - { - byte b = data[n]; - - index1 = (index1 + 1) & 255; - index2 = (index2 + s[index1]) & 255; - - Swap(s, index1, index2); - data[n] = (byte)(b ^ s[(s[index1] + s[index2]) & 255]); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Swap(Span s, int i, int j) - { - byte c = s[i]; - - s[i] = s[j]; - s[j] = c; - } - #endregion - } -} diff --git a/Shadowsocks.Net/Shadowsocks.Net.csproj b/Shadowsocks.Net/Shadowsocks.Net.csproj index 836c9791..2e72c00e 100644 --- a/Shadowsocks.Net/Shadowsocks.Net.csproj +++ b/Shadowsocks.Net/Shadowsocks.Net.csproj @@ -5,7 +5,7 @@ - + diff --git a/Shadowsocks.Net/TCPRelay.cs b/Shadowsocks.Net/TCPRelay.cs index 713f4c6d..91589765 100644 --- a/Shadowsocks.Net/TCPRelay.cs +++ b/Shadowsocks.Net/TCPRelay.cs @@ -291,6 +291,9 @@ namespace Shadowsocks.Net { _connection.Shutdown(SocketShutdown.Both); _connection.Close(); + + encryptor?.Dispose(); + decryptor?.Dispose(); } catch (Exception e) { diff --git a/Shadowsocks.Net/UDPRelay.cs b/Shadowsocks.Net/UDPRelay.cs index 4f6e8be0..352b57f4 100644 --- a/Shadowsocks.Net/UDPRelay.cs +++ b/Shadowsocks.Net/UDPRelay.cs @@ -93,7 +93,7 @@ namespace Shadowsocks.Net public async Task SendAsync(ReadOnlyMemory data) { - ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); + using ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); using IMemoryOwner mem = pool.Rent(data.Length + 1000); // byte[] dataOut = new byte[slicedData.Length + 1000]; @@ -120,7 +120,7 @@ namespace Shadowsocks.Net using IMemoryOwner owner = pool.Rent(bytesRead + 3); Memory o = owner.Memory; - ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); + using ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead)); this.Log().Debug($"{_remoteEndPoint} {_localEndPoint} {outlen} UDP Relay down"); if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment data))