Browse Source

drop stream cipher cipherUpdate api, drop table cipher due to performance issue

pull/2865/head
Student Main 5 years ago
parent
commit
b332a6bd1c
11 changed files with 103 additions and 240 deletions
  1. +2
    -2
      shadowsocks-csharp/Controller/Service/TCPRelay.cs
  2. +14
    -15
      shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs
  3. +2
    -2
      shadowsocks-csharp/Encryption/EncryptorBase.cs
  4. +2
    -2
      shadowsocks-csharp/Encryption/EncryptorFactory.cs
  5. +4
    -10
      shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs
  6. +8
    -17
      shadowsocks-csharp/Encryption/Stream/StreamChachaNaClEncryptor.cs
  7. +14
    -17
      shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs
  8. +44
    -0
      shadowsocks-csharp/Encryption/Stream/StreamPlainNativeEncryptor.cs
  9. +11
    -16
      shadowsocks-csharp/Encryption/Stream/StreamRc4NativeEncryptor.cs
  10. +0
    -152
      shadowsocks-csharp/Encryption/Stream/StreamTableNativeEncryptor.cs
  11. +2
    -7
      test/CryptographyTest.cs

+ 2
- 2
shadowsocks-csharp/Controller/Service/TCPRelay.cs View File

@@ -133,10 +133,10 @@ namespace Shadowsocks.Controller
public const int RecvSize = 2048;
// overhead of one chunk, reserved for AEAD ciphers
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.CHUNK_LEN_BYTES;
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes;
// max chunk size
public const uint MaxChunkSize = AEADEncryptor.CHUNK_LEN_MASK + AEADEncryptor.CHUNK_LEN_BYTES + 16 * 2;
public const uint MaxChunkSize = AEADEncryptor.ChunkLengthMask + AEADEncryptor.ChunkLengthBytes + 16 * 2;
// In general, the ciphertext length, we should take overhead into account
public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */;


+ 14
- 15
shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs View File

@@ -22,11 +22,10 @@ namespace Shadowsocks.Encryption.AEAD
protected static byte[] _udpTmpBuf = new byte[65536];
// every connection should create its own buffer
private ByteCircularBuffer buffer = new ByteCircularBuffer(MAX_INPUT_SIZE * 2);
// private ByteCircularBuffer buffer = new ByteCircularBuffer(MAX_INPUT_SIZE * 2);
private ByteCircularBuffer buffer = new ByteCircularBuffer(MaxInputSize * 2);
public const int CHUNK_LEN_BYTES = 2;
public const uint CHUNK_LEN_MASK = 0x3FFFu;
public const int ChunkLengthBytes = 2;
public const uint ChunkLengthMask = 0x3FFFu;
protected Dictionary<string, CipherInfo> ciphers;
@@ -180,11 +179,11 @@ namespace Shadowsocks.Encryption.AEAD
{
tcpRequestSent = true;
// The first TCP request
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES];
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + ChunkLengthBytes];
byte[] addrBytes = buffer.Get(AddressBufferLength);
int encAddrBufLength = ChunkEncrypt(addrBytes, encAddrBufBytes);
// ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength);
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES);
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + ChunkLengthBytes);
Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength);
outlength += encAddrBufLength;
logger.Debug($"_tcpRequestSent outlength {outlength}");
@@ -195,12 +194,12 @@ namespace Shadowsocks.Encryption.AEAD
{
uint bufSize = (uint)buffer.Size;
if (bufSize <= 0) return;
var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK);
var chunklength = (int)Math.Min(bufSize, ChunkLengthMask);
byte[] chunkBytes = buffer.Get(chunklength);
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + CHUNK_LEN_BYTES];
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + ChunkLengthBytes];
int encChunkLength = ChunkEncrypt(chunkBytes, encChunkBytes);
// ChunkEncrypt(chunkBytes, chunklength, encChunkBytes, out encChunkLength);
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + CHUNK_LEN_BYTES);
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + ChunkLengthBytes);
Buffer.BlockCopy(encChunkBytes, 0, outbuf, outlength, encChunkLength);
outlength += encChunkLength;
logger.Debug("chunks enc outlength " + outlength);
@@ -256,7 +255,7 @@ namespace Shadowsocks.Encryption.AEAD
}
// first get chunk length
if (bufSize <= CHUNK_LEN_BYTES + tagLen)
if (bufSize <= ChunkLengthBytes + tagLen)
{
// so we only have chunk length and its tag?
return;
@@ -264,12 +263,12 @@ namespace Shadowsocks.Encryption.AEAD
#region Chunk Decryption
byte[] encLenBytes = buffer.Peek(CHUNK_LEN_BYTES + tagLen);
byte[] encLenBytes = buffer.Peek(ChunkLengthBytes + tagLen);
// try to dec chunk len
byte[] decChunkLenBytes = CipherDecrypt2(encLenBytes);
ushort chunkLen = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0));
if (chunkLen > CHUNK_LEN_MASK)
if (chunkLen > ChunkLengthMask)
{
// we get invalid chunk
logger.Error($"Invalid chunk length: {chunkLen}");
@@ -277,7 +276,7 @@ namespace Shadowsocks.Encryption.AEAD
}
logger.Debug("Get the real chunk len:" + chunkLen);
bufSize = buffer.Size;
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen)
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLen + tagLen)
{
logger.Debug("No more data to decrypt one chunk");
return;
@@ -286,7 +285,7 @@ namespace Shadowsocks.Encryption.AEAD
// we have enough data to decrypt one chunk
// drop chunk len and its tag from buffer
buffer.Skip(CHUNK_LEN_BYTES + tagLen);
buffer.Skip(ChunkLengthBytes + tagLen);
byte[] encChunkBytes = buffer.Get(chunkLen + tagLen);
byte[] decChunkBytes = CipherDecrypt2(encChunkBytes);
IncrementNonce();
@@ -363,7 +362,7 @@ namespace Shadowsocks.Encryption.AEAD
private int ChunkEncrypt(Span<byte> plain, Span<byte> cipher)
{
if (plain.Length > CHUNK_LEN_MASK)
if (plain.Length > ChunkLengthMask)
{
logger.Error("enc chunk too big");
throw new CryptoErrorException();


+ 2
- 2
shadowsocks-csharp/Encryption/EncryptorBase.cs View File

@@ -4,7 +4,7 @@
{
private static int _currentId = 0;
public const int MAX_INPUT_SIZE = 32768;
public const int MaxInputSize = 32768;
public const int MAX_DOMAIN_LEN = 255;
public const int ADDR_PORT_LEN = 2;
@@ -14,7 +14,7 @@
public const int ATYP_DOMAIN = 0x03;
public const int ATYP_IPv6 = 0x04;
public const int MD5_LEN = 16;
public const int MD5Length = 16;
// for debugging only, give it a number to trace data stream
public readonly int instanceId;


+ 2
- 2
shadowsocks-csharp/Encryption/EncryptorFactory.cs View File

@@ -17,12 +17,12 @@ namespace Shadowsocks.Encryption
static EncryptorFactory()
{
foreach (var method in StreamTableNativeEncryptor.SupportedCiphers())
foreach (var method in StreamPlainNativeEncryptor.SupportedCiphers())
{
if (!_registeredEncryptors.ContainsKey(method.Key))
{
ciphers.Add(method.Key, method.Value);
_registeredEncryptors.Add(method.Key, typeof(StreamTableNativeEncryptor));
_registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeEncryptor));
}
}
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers())


+ 4
- 10
shadowsocks-csharp/Encryption/Stream/StreamAesBouncyCastleEncryptor.cs View File

@@ -14,7 +14,6 @@ namespace Shadowsocks.Encryption.Stream
public StreamAesBouncyCastleEncryptor(string method, string password) : base(method, password)
{
c = new BufferedBlockCipher(new CfbBlockCipher(new AesEngine(), 128));
// c = CipherUtilities.GetCipher("AES/CFB/NoPadding");
}

protected override void initCipher(byte[] iv, bool isEncrypt)
@@ -23,13 +22,6 @@ namespace Shadowsocks.Encryption.Stream
c.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv));
}

protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf)
{
var i = buf.AsSpan(0, length);
if (isEncrypt) CipherEncrypt(i, outbuf);
else CipherDecrypt(outbuf, i);
}

protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher)
{
CipherUpdate(plain, cipher);
@@ -42,9 +34,9 @@ namespace Shadowsocks.Encryption.Stream
return cipher.Length;
}


private void CipherUpdate(Span<byte> i, Span<byte> o)
{
// there's some secret in OpenSSL's EVP context.
var ob = new byte[o.Length];
int blklen = c.ProcessBytes(i.ToArray(), 0, i.Length, ob, 0);
int restlen = i.Length - blklen;
@@ -55,9 +47,11 @@ namespace Shadowsocks.Encryption.Stream
ob.CopyTo(o);
}

#region Ciphers
#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)},
};



+ 8
- 17
shadowsocks-csharp/Encryption/Stream/StreamChachaNaClEncryptor.cs View File

@@ -7,11 +7,15 @@ namespace Shadowsocks.Encryption.Stream
public class StreamChachaNaClEncryptor : StreamEncryptor
{
const int BlockSize = 64;
// when new data arrive, put it in correct offset of chunk
// tcp is stream, which can split into chunks at unexpected position...
// so we need some special handling, as we can't read all data before encrypt

// when new data arrive, put it on correct offset
// and update it, ignore other data, get it in correct offset...
byte[] chachaBuf = new byte[32768 + BlockSize];
byte[] chachaBuf = new byte[MaxInputSize + BlockSize];
// the 'correct offset', always in 0~BlockSize range, so input data always fit into buffer
int remain = 0;
// increase counter only when a chunk fully recieved
// increase counter manually...
int ic = 0;
public StreamChachaNaClEncryptor(string method, string password) : base(method, password)
{
@@ -41,20 +45,7 @@ namespace Shadowsocks.Encryption.Stream
return len;
}

protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf)
{
var i = buf.AsSpan(0, length);
if (isEncrypt)
{
CipherEncrypt(i, outbuf);
}
else
{
CipherDecrypt(outbuf, i);
}
}

#region Ciphers
#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) },


+ 14
- 17
shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs View File

@@ -1,10 +1,10 @@
using System;
using NLog;
using Shadowsocks.Controller;
using Shadowsocks.Encryption.CircularBuffer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Shadowsocks.Encryption.CircularBuffer;
using Shadowsocks.Controller;
using NLog;
namespace Shadowsocks.Encryption.Stream
{
@@ -54,7 +54,7 @@ namespace Shadowsocks.Encryption.Stream
public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen)
{
byte[] result = new byte[password.Length + MD5_LEN];
byte[] result = new byte[password.Length + MD5Length];
int i = 0;
byte[] md5sum = Array.Empty<byte>();
while (i < keylen)
@@ -65,12 +65,12 @@ namespace Shadowsocks.Encryption.Stream
}
else
{
Array.Copy(md5sum, 0, result, 0, MD5_LEN);
Array.Copy(password, 0, result, MD5_LEN, password.Length);
Array.Copy(md5sum, 0, result, 0, MD5Length);
Array.Copy(password, 0, result, MD5Length, password.Length);
md5sum = CryptoUtils.MD5(result);
}
Array.Copy(md5sum, 0, key, i, Math.Min(MD5_LEN, keylen - i));
i += MD5_LEN;
Array.Copy(md5sum, 0, key, i, Math.Min(MD5Length, keylen - i));
i += MD5Length;
}
}
@@ -81,13 +81,9 @@ namespace Shadowsocks.Encryption.Stream
Array.Copy(iv, this.iv, ivLen);
}
protected abstract void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf);
protected abstract int CipherEncrypt(Span<byte> plain, Span<byte> cipher);
protected abstract int CipherDecrypt(Span<byte> plain, Span<byte> cipher);
//protected static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); }
#region TCP
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength)
@@ -109,7 +105,8 @@ namespace Shadowsocks.Encryption.Stream
int size = buffer.Size;
byte[] plain = buffer.Get(size);
byte[] cipher = new byte[size];
cipherUpdate(true, size, plain, cipher);
CipherEncrypt(plain, cipher);
logger.DumpBase64($"plain {instanceId}", plain, size);
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length);
logger.Dump($"iv {instanceId}", iv, ivLen);
@@ -140,7 +137,7 @@ namespace Shadowsocks.Encryption.Stream
else initCipher(Array.Empty<byte>(), false);
}
byte[] cipher = buffer.ToArray();
cipherUpdate(false, cipher.Length, cipher, outbuf);
CipherDecrypt(outbuf, cipher);
logger.DumpBase64($"cipher {instanceId}", cipher, cipher.Length);
logger.DumpBase64($"plain {instanceId}", outbuf, cipher.Length);
logger.Dump($"iv {instanceId}", iv, ivLen);
@@ -162,7 +159,7 @@ namespace Shadowsocks.Encryption.Stream
initCipher(outbuf, true);
lock (udpBuffer)
{
cipherUpdate(true, length, buf, udpBuffer);
CipherEncrypt(buf, udpBuffer);
outlength = length + ivLen;
Buffer.BlockCopy(udpBuffer, 0, outbuf, ivLen, length);
}
@@ -177,7 +174,7 @@ namespace Shadowsocks.Encryption.Stream
{
// C# could be multi-threaded
Buffer.BlockCopy(buf, ivLen, udpBuffer, 0, length - ivLen);
cipherUpdate(false, length - ivLen, udpBuffer, outbuf);
CipherDecrypt(outbuf, udpBuffer);
}
}


+ 44
- 0
shadowsocks-csharp/Encryption/Stream/StreamPlainNativeEncryptor.cs View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace Shadowsocks.Encryption.Stream
{
public class StreamPlainNativeEncryptor : StreamEncryptor
{

public StreamPlainNativeEncryptor(string method, string password) : base(method, password)
{
}

protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher)
{
cipher.CopyTo(plain);
return cipher.Length;
}

protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher)
{
plain.CopyTo(cipher);
return plain.Length;
}

#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) },
};

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

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

+ 11
- 16
shadowsocks-csharp/Encryption/Stream/StreamRc4NativeEncryptor.cs View File

@@ -14,6 +14,7 @@ namespace Shadowsocks.Encryption.Stream
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];
@@ -28,33 +29,27 @@ namespace Shadowsocks.Encryption.Stream
sbox = SBox(realkey);
}

protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf)
{
byte[] t = new byte[length];
Array.Copy(buf, t, length);

RC4(ctx, sbox, t, length);
Array.Copy(t, outbuf, length);
}

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

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

private int DoRC4(Span<byte> i, Span<byte> o)
private int CipherUpdate(Span<byte> i, Span<byte> o)
{
i.CopyTo(o);
RC4(ctx, sbox, o, o.Length);
return o.Length;
// don't know why we need third array, but it works...
Span<byte> t = new byte[i.Length];
i.CopyTo(t);
RC4(ctx, sbox, t, t.Length);
t.CopyTo(o);
return t.Length;
}

#region Ciphers
#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
// original RC4 doesn't use IV


+ 0
- 152
shadowsocks-csharp/Encryption/Stream/StreamTableNativeEncryptor.cs View File

@@ -1,152 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Shadowsocks.Encryption.Stream
{
public class StreamTableNativeEncryptor : StreamEncryptor
{
// table mode use special way to generate key
readonly string _password;

public StreamTableNativeEncryptor(string method, string password) : base(method, password)
{
_password = password;
}

protected override void initCipher(byte[] iv, bool isEncrypt)
{
// another cipher is plain, needn't a table
if (cipherFamily != CipherFamily.Table) return;
ulong a = BitConverter.ToUInt64(CryptoUtils.MD5(Encoding.UTF8.GetBytes(_password)), 0);
for (int i = 0; i < 256; i++)
{
_encryptTable[i] = (byte)i;
}
Span<byte> t = _encryptTable;
// copy array 1024 times? excuse me?
for (int i = 1; i < 1024; i++)
{
t = MergeSort(t, a, i);
}
_encryptTable = t.ToArray();
for (int i = 0; i < 256; i++)
{
_decryptTable[_encryptTable[i]] = (byte)i;
}
}

protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf)
{
if (cipherFamily == CipherFamily.Table)
{
var table = isEncrypt ? _encryptTable : _decryptTable;
for (int i = 0; i < length; i++)
{
outbuf[i] = table[buf[i]];
}
}
else if (cipherFamily == CipherFamily.Plain)
{
Array.Copy(buf, outbuf, length);
}
}

protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher)
{
if (cipherFamily == CipherFamily.Plain)
{
cipher.CopyTo(plain);
return cipher.Length;
}

for (int i = 0; i < cipher.Length; i++)
{
plain[i] = _decryptTable[cipher[i]];
}
return cipher.Length;
}

protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher)
{
if (cipherFamily == CipherFamily.Plain)
{
plain.CopyTo(cipher);
return plain.Length;
}

for (int i = 0; i < plain.Length; i++)
{
cipher[i] = _decryptTable[plain[i]];
}
return plain.Length;
}

#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) },
{"table", new CipherInfo("table", 0, 0, CipherFamily.Table) },
};

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

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

#region Table
private byte[] _encryptTable = new byte[256];
private byte[] _decryptTable = new byte[256];
private byte[] _tmp = new byte[256];

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static long Compare(byte x, byte y, ulong a, int i)
{
return (long)(a % (ulong)(x + i)) - (long)(a % (ulong)(y + i));
}

byte[] buf = new byte[1024];
private Span<byte> MergeSort(Span<byte> array, ulong a, int j)
{
if (array.Length == 1)
{
return array;
}
int middle = array.Length / 2;

Span<byte> left = MergeSort(array.Slice(0, middle), a, j);;
Span<byte> right = MergeSort(array.Slice(middle), a, j);

int leftptr = 0;
int rightptr = 0;

// why a new array?
Span<byte> sorted = new byte[array.Length];// buf.AsSpan().Slice(0,array.Length); // // _tmp;
for (int k = 0; k < array.Length; k++)
{
if (rightptr == right.Length || ((leftptr < left.Length) && (Compare(left[leftptr], right[rightptr], a, j) <= 0)))
{
sorted[k] = left[leftptr];
leftptr++;
}
else if (leftptr == left.Length || ((rightptr < right.Length) && (Compare(right[rightptr], left[leftptr], a, j)) <= 0))
{
sorted[k] = right[rightptr];
rightptr++;
}
}
return sorted;
}
#endregion
}
}

+ 2
- 7
test/CryptographyTest.cs View File

@@ -147,16 +147,11 @@ namespace Shadowsocks.Test
[TestMethod]
public void TestNativeEncryption()
{
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "plain");
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4");
TestEncryptionMethod(typeof(StreamPlainNativeEncryptor), "plain");
// TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4");
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5");
}
[TestMethod]
public void TestNativeTableEncryption()
{
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "table");
}
[TestMethod]
public void TestStreamAesBouncyCastleEncryption()
{


Loading…
Cancel
Save