Browse Source

Replace BouncyCastle with CryptoBase

Shadowsocks.Net
pull/3084/head
Bruce Wayne 3 years ago
parent
commit
002816eb9f
No known key found for this signature in database GPG Key ID: 6D396097F05051BF
23 changed files with 186 additions and 2035 deletions
  1. +0
    -54
      Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs
  2. +0
    -75
      Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs
  3. +7
    -5
      Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs
  4. +72
    -0
      Shadowsocks.Net/Crypto/AEAD/AEADCryptoBaseCrypto.cs
  5. +2
    -0
      Shadowsocks.Net/Crypto/CryptoBase.cs
  6. +5
    -29
      Shadowsocks.Net/Crypto/CryptoFactory.cs
  7. +2
    -50
      Shadowsocks.Net/Crypto/CryptoUtils.cs
  8. +0
    -23
      Shadowsocks.Net/Crypto/Extensions/Check.cs
  9. +0
    -264
      Shadowsocks.Net/Crypto/Extensions/MyCfbBlockCipher.cs
  10. +0
    -183
      Shadowsocks.Net/Crypto/Extensions/MyChaChaEngine.cs
  11. +0
    -363
      Shadowsocks.Net/Crypto/Extensions/MySalsa20Engine.cs
  12. +0
    -56
      Shadowsocks.Net/Crypto/Extensions/Pack.cs
  13. +0
    -98
      Shadowsocks.Net/Crypto/Extensions/XChaCha20Engine.cs
  14. +0
    -588
      Shadowsocks.Net/Crypto/Extensions/XChaCha20Poly1305.cs
  15. +1
    -1
      Shadowsocks.Net/Crypto/ICrypto.cs
  16. +0
    -63
      Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs
  17. +0
    -59
      Shadowsocks.Net/Crypto/Stream/StreamChachaBouncyCastleCrypto.cs
  18. +89
    -0
      Shadowsocks.Net/Crypto/Stream/StreamCryptoBaseCrypto.cs
  19. +2
    -0
      Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs
  20. +0
    -121
      Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs
  21. +1
    -1
      Shadowsocks.Net/Shadowsocks.Net.csproj
  22. +3
    -0
      Shadowsocks.Net/TCPRelay.cs
  23. +2
    -2
      Shadowsocks.Net/UDPRelay.cs

+ 0
- 54
Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs View File

@@ -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<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 static Dictionary<string, CipherInfo> 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<byte> plain, Span<byte> cipher)
{
aes.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen));
return plain.Length + tagLen;
}

public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
int clen = cipher.Length - tagLen;
ReadOnlySpan<byte> ciphertxt = cipher.Slice(0, clen);
ReadOnlySpan<byte> tag = cipher.Slice(clen);
aes.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen));
return clen;
}
}
}

+ 0
- 75
Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs View File

@@ -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<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)},
{"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;
}
#endregion

public override void InitCipher(byte[] salt, bool isEncrypt)
{
base.InitCipher(salt, isEncrypt);
enc = isEncrypt;
}

public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
return CipherUpdate(cipher, plain);
}

public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
return CipherUpdate(plain, cipher);
}

private int CipherUpdate(ReadOnlySpan<byte> i, Span<byte> 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;
}
}
}

+ 7
- 5
Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs View File

@@ -1,3 +1,4 @@
using CryptoBase;
using Shadowsocks.Net.Crypto.Exception; using Shadowsocks.Net.Crypto.Exception;
using Shadowsocks.Net.Crypto.Stream; using Shadowsocks.Net.Crypto.Stream;
using Splat; using Splat;
@@ -5,6 +6,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text; using System.Text;


namespace Shadowsocks.Net.Crypto.AEAD namespace Shadowsocks.Net.Crypto.AEAD
@@ -90,7 +92,7 @@ namespace Shadowsocks.Net.Crypto.AEAD
this.salt = new byte[saltLen]; this.salt = new byte[saltLen];
Array.Copy(salt, this.salt, 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($"salt {instanceId}", salt, saltLen);
this.Log().Debug($"sessionkey {instanceId}", sessionKey, keyLen); 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)); byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length));
int cipherLenSize = CipherEncrypt(lenbuf, cipher); int cipherLenSize = CipherEncrypt(lenbuf, cipher);
CryptoUtils.SodiumIncrement(nonce);
nonce.Increment();
int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize)); int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize));
CryptoUtils.SodiumIncrement(nonce);
nonce.Increment();


return cipherLenSize + cipherDataSize; 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}"); this.Log().Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}");
return 0; return 0;
} }
CryptoUtils.SodiumIncrement(nonce);
nonce.Increment();
// we have enough data to decrypt one chunk // we have enough data to decrypt one chunk
// drop chunk len and its tag from buffer // drop chunk len and its tag from buffer
int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen)); 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}"); this.Log().Debug($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}");
return len; return len;
} }


+ 72
- 0
Shadowsocks.Net/Crypto/AEAD/AEADCryptoBaseCrypto.cs View File

@@ -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<string, CipherInfo> _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<string, CipherInfo> GetCiphers()
{
return _ciphers;
}

public static Dictionary<string, CipherInfo> 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<byte> plain, Span<byte> cipher)
{
_crypto!.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen));
return plain.Length + tagLen;
}

public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> 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();
}
}
}

+ 2
- 0
Shadowsocks.Net/Crypto/CryptoBase.cs View File

@@ -44,5 +44,7 @@ namespace Shadowsocks.Net.Crypto
public abstract int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher); public abstract int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher);


public int AddressBufferLength { get; set; } = -1; public int AddressBufferLength { get; set; } = -1;

public abstract void Dispose();
} }
} }

+ 5
- 29
Shadowsocks.Net/Crypto/CryptoFactory.cs View File

@@ -26,46 +26,22 @@ namespace Shadowsocks.Net.Crypto
_registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeCrypto)); _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)) if (!_registeredEncryptors.ContainsKey(method.Key))
{ {
ciphers.Add(method.Key, method.Value); 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)) if (!_registeredEncryptors.ContainsKey(method.Key))
{ {
ciphers.Add(method.Key, method.Value); ciphers.Add(method.Key, method.Value);
_registeredEncryptors.Add(method.Key, typeof(AEADBouncyCastleCrypto));
_registeredEncryptors.Add(method.Key, typeof(AEADCryptoBaseCrypto));
} }
} }
} }


+ 2
- 50
Shadowsocks.Net/Crypto/CryptoUtils.cs View File

@@ -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 namespace Shadowsocks.Net.Crypto
{ {
public static class CryptoUtils public static class CryptoUtils
{ {
private static readonly ThreadLocal<MD5> Md5Hasher = new ThreadLocal<MD5>(System.Security.Cryptography.MD5.Create);

public static byte[] MD5(byte[] b) public static byte[] MD5(byte[] b)
{ {
var hash = new byte[CryptoBase.MD5Length]; var hash = new byte[CryptoBase.MD5Length];
Md5Hasher.Value.TryComputeHash(b, hash, out _);
return hash;
}
// currently useless, just keep api same
public static Span<byte> MD5(Span<byte> span)
{
Span<byte> hash = new byte[CryptoBase.MD5Length];
Md5Hasher.Value.TryComputeHash(span, hash, out _);
MD5Utils.Default(b, hash);
return 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<byte> HKDF(int keylen, Span<byte> master, Span<byte> salt, Span<byte> 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<byte> salt)
{
for (var i = 0; i < salt.Length; ++i)
{
if (++salt[i] != 0)
{
break;
}
}
}
} }
} }

+ 0
- 23
Shadowsocks.Net/Crypto/Extensions/Check.cs View File

@@ -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);
}
}
}
}

+ 0
- 264
Shadowsocks.Net/Crypto/Extensions/MyCfbBlockCipher.cs View File

@@ -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();
}
}
}

+ 0
- 183
Shadowsocks.Net/Crypto/Extensions/MyChaChaEngine.cs View File

@@ -1,183 +0,0 @@
using System;

namespace Shadowsocks.Net.Crypto.Extensions
{
/// <summary>
/// Implementation of Daniel J. Bernstein's ChaCha stream cipher.
/// </summary>
public class MyChaChaEngine : MySalsa20Engine
{
/// <summary>
/// Creates a ChaCha engine with a specific number of rounds.
/// </summary>
/// <param name="rounds">the number of rounds (must be an even number).</param>
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);
}

/// <summary>
/// ChaCha function.
/// </summary>
/// <param name="rounds">The number of ChaCha rounds to execute</param>
/// <param name="input">The input words.</param>
/// <param name="x">The ChaCha state to modify.</param>
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];
}
}
}

+ 0
- 363
Shadowsocks.Net/Crypto/Extensions/MySalsa20Engine.cs View File

@@ -1,363 +0,0 @@
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Utilities;
using System;

namespace Shadowsocks.Net.Crypto.Extensions
{
/// <summary>
/// Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005
/// </summary>
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;

/// <summary>
/// Creates a Salsa20 engine with a specific number of rounds.
/// </summary>
/// <param name="rounds">the number of rounds (must be an even number).</param>
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;
}
}
}

+ 0
- 56
Shadowsocks.Net/Crypto/Extensions/Pack.cs View File

@@ -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);
}
}
}

+ 0
- 98
Shadowsocks.Net/Crypto/Extensions/XChaCha20Engine.cs View File

@@ -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);
}
}
}
}

+ 0
- 588
Shadowsocks.Net/Crypto/Extensions/XChaCha20Poly1305.cs View File

@@ -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);
}
}
}
}

+ 1
- 1
Shadowsocks.Net/Crypto/ICrypto.cs View File

@@ -2,7 +2,7 @@ using System;


namespace Shadowsocks.Net.Crypto namespace Shadowsocks.Net.Crypto
{ {
public interface ICrypto
public interface ICrypto : IDisposable
{ {
int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher); int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher);
int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher); int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher);


+ 0
- 63
Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs View File

@@ -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<byte> plain, Span<byte> cipher)
{
return CipherUpdate(plain, cipher);
}

protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
return CipherUpdate(cipher, plain);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> 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<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{"aes-128-cfb",new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)},
{"aes-192-cfb",new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)},
{"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)},
};

public static Dictionary<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}

protected override Dictionary<string, CipherInfo> GetCiphers()
{
return _ciphers;
}
#endregion
}
}

+ 0
- 59
Shadowsocks.Net/Crypto/Stream/StreamChachaBouncyCastleCrypto.cs View File

@@ -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<byte> plain, Span<byte> cipher)
{
return CipherUpdate(plain, cipher);
}

protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
return CipherUpdate(cipher, plain);
}

protected virtual int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> 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<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) },
};
public static Dictionary<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}

protected override Dictionary<string, CipherInfo> GetCiphers()
{
return _ciphers;
}
#endregion
}
}

+ 89
- 0
Shadowsocks.Net/Crypto/Stream/StreamCryptoBaseCrypto.cs View File

@@ -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<byte> 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<byte> plain, Span<byte> cipher)
{
return CipherUpdate(plain, cipher);
}

protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
return CipherUpdate(cipher, plain);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output)
{
_crypto!.Update(input, output);
return input.Length;
}

#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _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<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}

protected override Dictionary<string, CipherInfo> GetCiphers()
{
return _ciphers;
}
#endregion

public override void Dispose()
{
_crypto?.Dispose();
}
}
}

+ 2
- 0
Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs View File

@@ -39,5 +39,7 @@ namespace Shadowsocks.Net.Crypto.Stream
return _ciphers; return _ciphers;
} }
#endregion #endregion

public override void Dispose() { }
} }
} }

+ 0
- 121
Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs View File

@@ -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<byte> plain, Span<byte> cipher)
{
return CipherUpdate(plain, cipher);
}

protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
return CipherUpdate(cipher, plain);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CipherUpdate(ReadOnlySpan<byte> i, Span<byte> o)
{
// don't know why we need third array, but it works...
Span<byte> 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<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;
}
#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<byte> s, Span<byte> 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<byte> s, int i, int j)
{
byte c = s[i];

s[i] = s[j];
s[j] = c;
}
#endregion
}
}

+ 1
- 1
Shadowsocks.Net/Shadowsocks.Net.csproj View File

@@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>


<ItemGroup> <ItemGroup>
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.8" />
<PackageReference Include="CryptoBase" Version="1.1.1" />
<PackageReference Include="Splat" Version="9.8.1" /> <PackageReference Include="Splat" Version="9.8.1" />
</ItemGroup> </ItemGroup>




+ 3
- 0
Shadowsocks.Net/TCPRelay.cs View File

@@ -291,6 +291,9 @@ namespace Shadowsocks.Net
{ {
_connection.Shutdown(SocketShutdown.Both); _connection.Shutdown(SocketShutdown.Both);
_connection.Close(); _connection.Close();

encryptor?.Dispose();
decryptor?.Dispose();
} }
catch (Exception e) catch (Exception e)
{ {


+ 2
- 2
Shadowsocks.Net/UDPRelay.cs View File

@@ -93,7 +93,7 @@ namespace Shadowsocks.Net


public async Task SendAsync(ReadOnlyMemory<byte> data) public async Task SendAsync(ReadOnlyMemory<byte> data)
{ {
ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password);
using ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password);
using IMemoryOwner<byte> mem = pool.Rent(data.Length + 1000); using IMemoryOwner<byte> mem = pool.Rent(data.Length + 1000);


// byte[] dataOut = new byte[slicedData.Length + 1000]; // byte[] dataOut = new byte[slicedData.Length + 1000];
@@ -120,7 +120,7 @@ namespace Shadowsocks.Net
using IMemoryOwner<byte> owner = pool.Rent(bytesRead + 3); using IMemoryOwner<byte> owner = pool.Rent(bytesRead + 3);
Memory<byte> o = owner.Memory; Memory<byte> 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)); int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead));
this.Log().Debug($"{_remoteEndPoint} {_localEndPoint} {outlen} UDP Relay down"); this.Log().Debug($"{_remoteEndPoint} {_localEndPoint} {outlen} UDP Relay down");
if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment<byte> data)) if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment<byte> data))


Loading…
Cancel
Save