Browse Source

span based Encrypt Decrypt

pull/2865/head
Student Main 5 years ago
parent
commit
2cd649a05c
4 changed files with 272 additions and 78 deletions
  1. +8
    -0
      shadowsocks-csharp/Controller/LoggerExtension.cs
  2. +181
    -55
      shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs
  3. +76
    -18
      shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs
  4. +7
    -5
      test/CryptographyTest.cs

+ 8
- 0
shadowsocks-csharp/Controller/LoggerExtension.cs View File

@@ -12,6 +12,10 @@ namespace NLog
public static class LoggerExtension
{
// for key, iv, etc...
public static void Dump(this Logger logger, string tag, ReadOnlySpan<byte> arr)
{
logger.Dump(tag, arr.ToArray(), arr.Length);
}
public static void Dump(this Logger logger, string tag, byte[] arr, int length = -1)
{
if (length == -1) length = arr.Length;
@@ -26,6 +30,10 @@ namespace NLog
logger.Trace(content);
}
// for cipher and plain text, so we can use openssl to test
public static void DumpBase64(this Logger logger, string tag, ReadOnlySpan<byte> arr)
{
logger.DumpBase64(tag, arr.ToArray(), arr.Length);
}
public static void DumpBase64(this Logger logger, string tag, byte[] arr, int length = -1)
{
if (length == -1) length = arr.Length;


+ 181
- 55
shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs View File

@@ -188,6 +188,70 @@ namespace Shadowsocks.Encryption.AEAD
}
}
public override int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
// push data
Span<byte> tmp = sharedBuffer.AsSpan(0, plain.Length + bufPtr);
plain.CopyTo(tmp.Slice(bufPtr));
int outlength = 0;
if (!saltReady)
{
saltReady = true;
// Generate salt
byte[] saltBytes = RNG.GetBytes(saltLen);
InitCipher(saltBytes, true, false);
saltBytes.CopyTo(cipher);
outlength = saltLen;
}
if (!tcpRequestSent)
{
tcpRequestSent = true;
// read addr byte to encrypt
int encAddrBufLength = ChunkEncrypt(tmp.Slice(0, AddressBufferLength), cipher.Slice(outlength));
tmp = tmp.Slice(AddressBufferLength);
outlength += encAddrBufLength;
}
// handle other chunks
while (true)
{
// calculate next chunk size
int bufSize = tmp.Length;
if (bufSize <= 0)
{
return outlength;
}
int chunklength = (int)Math.Min(bufSize, ChunkLengthMask);
// read next chunk
int encChunkLength = ChunkEncrypt(tmp.Slice(0, chunklength), cipher.Slice(outlength));
tmp = tmp.Slice(chunklength);
outlength += encChunkLength;
// check if we have enough space for outbuf
// if not, keep buf for next run, at this condition, buffer is not empty
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize)
{
logger.Debug("enc outbuf almost full, giving up");
// write rest data to head of shared buffer
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return outlength;
}
// check if buffer empty
bufSize = tmp.Length;
if (bufSize <= 0)
{
logger.Debug("No more data to encrypt, leaving");
return outlength;
}
}
}
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength)
{
@@ -239,61 +303,112 @@ namespace Shadowsocks.Encryption.AEAD
return;
}
#region Chunk Decryption
int len = ChunkDecrypt(outbuf.AsSpan(outlength), tmp);
if (len <= 0)
{
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
// byte[] encLenBytes = buffer.Peek(ChunkLengthBytes + tagLen);
byte[] encLenBytes = tmp.Slice(0, ChunkLengthBytes + tagLen).ToArray();
tmp = tmp.Slice(ChunkLengthBytes + tagLen + len + tagLen);
// output to outbuf
outlength += len;
// try to dec chunk len
byte[] decChunkLenBytes = new byte[ChunkLengthBytes];
CipherDecrypt(decChunkLenBytes, encLenBytes);
ushort chunkLen = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0));
if (chunkLen > ChunkLengthMask)
logger.Debug("aead dec outlength " + outlength);
if (outlength + 100 > TCPHandler.BufferSize)
{
// we get invalid chunk
logger.Error($"Invalid chunk length: {chunkLen}");
throw new CryptoErrorException();
logger.Debug("dec outbuf almost full, giving up");
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
}
logger.Debug("Get the real chunk len:" + chunkLen);
bufSize = tmp.Length;
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLen + tagLen)
// check if we already done all of them
if (bufSize <= 0)
{
logger.Debug("No more data to decrypt one chunk");
// write back length data
logger.Debug("No data in _decCircularBuffer, already all done");
return;
}
}
}
public override int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
int outlength = 0;
// drop all into buffer
Span<byte> tmp = sharedBuffer.AsSpan(0, cipher.Length + bufPtr);
cipher.CopyTo(tmp.Slice(bufPtr));
int bufSize = tmp.Length;
logger.Debug("---Start Decryption");
if (!saltReady)
{
// check if we get the leading salt
if (bufSize <= saltLen)
{
// need more, write back cache
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
return outlength;
}
IncrementNonce();
// we have enough data to decrypt one chunk
// drop chunk len and its tag from buffer
// buffer.Skip(ChunkLengthBytes + tagLen);
tmp = tmp.Slice(ChunkLengthBytes + tagLen);
// byte[] encChunkBytes = buffer.Get(chunkLen + tagLen);
byte[] encChunkBytes = tmp.Slice(0, chunkLen + tagLen).ToArray();
tmp = tmp.Slice(chunkLen + tagLen);
saltReady = true;
int len = CipherDecrypt(outbuf.AsSpan(outlength), encChunkBytes);
IncrementNonce();
// buffer.Get(saltLen);
byte[] salt = tmp.Slice(0, saltLen).ToArray();
tmp = tmp.Slice(saltLen);
#endregion
InitCipher(salt, false, false);
logger.Debug("get salt len " + saltLen);
}
// output to outbuf
// handle chunks
while (true)
{
bufSize = tmp.Length;
// check if we have any data
if (bufSize <= 0)
{
logger.Debug("No data in buffer");
return outlength;
}
// first get chunk length
if (bufSize <= ChunkLengthBytes + tagLen)
{
// so we only have chunk length and its tag?
// wait more
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return outlength;
}
int len = ChunkDecrypt(plain.Slice(outlength), tmp);
if (len <= 0)
{
// no chunk decrypted
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return outlength;
}
// drop decrypted data
tmp = tmp.Slice(ChunkLengthBytes + tagLen + len + tagLen);
outlength += len;
logger.Debug("aead dec outlength " + outlength);
if (outlength + 100 > TCPHandler.BufferSize)
{
logger.Debug("dec outbuf almost full, giving up");
tmp.CopyTo(sharedBuffer);
bufPtr = tmp.Length;
return;
return outlength;
}
bufSize = tmp.Length;
// check if we already done all of them
if (bufSize <= 0)
{
logger.Debug("No data in _decCircularBuffer, already all done");
return;
return outlength;
}
}
}
@@ -301,14 +416,6 @@ namespace Shadowsocks.Encryption.AEAD
#endregion
#region UDP
/// <summary>
/// Perform AEAD UDP packet encryption
/// </summary>
/// payload => [salt][encrypted payload][tag]
/// <param name="buf"></param>
/// <param name="length"></param>
/// <param name="outbuf"></param>
/// <param name="outlength"></param>
public override void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength)
{
// Generate salt
@@ -317,7 +424,13 @@ namespace Shadowsocks.Encryption.AEAD
outlength = saltLen + CipherEncrypt(
buf.AsSpan(0, length),
outbuf.AsSpan(saltLen, length + tagLen));
}
public override int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
RNG.GetSpan(cipher.Slice(0, saltLen));
InitCipher(cipher.Slice(0, saltLen).ToArray(), true, true);
return saltLen + CipherEncrypt(plain, cipher.Slice(saltLen));
}
public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength)
@@ -326,6 +439,12 @@ namespace Shadowsocks.Encryption.AEAD
outlength = CipherDecrypt(outbuf, buf.AsSpan(saltLen, length - saltLen));
}
public override int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
InitCipher(cipher.Slice(0, saltLen).ToArray(), false, true);
return CipherDecrypt(plain, cipher.Slice(saltLen));
}
#endregion
private int ChunkEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
@@ -345,24 +464,31 @@ namespace Shadowsocks.Encryption.AEAD
return cipherLenSize + cipherDataSize;
}
public override int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
private int ChunkDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
throw new NotImplementedException();
}
public override int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
throw new NotImplementedException();
}
public override int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
throw new NotImplementedException();
}
public override int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
throw new NotImplementedException();
// try to dec chunk len
byte[] chunkLengthByte = new byte[ChunkLengthBytes];
CipherDecrypt(chunkLengthByte, cipher.Slice(0, ChunkLengthBytes + tagLen));
ushort chunkLength = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(chunkLengthByte, 0));
if (chunkLength > ChunkLengthMask)
{
// we get invalid chunk
logger.Error($"Invalid chunk length: {chunkLength}");
throw new CryptoErrorException();
}
logger.Debug("Get the real chunk len:" + chunkLength);
int bufSize = cipher.Length;
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLength + tagLen)
{
logger.Debug("No data to decrypt one chunk");
return 0;
}
IncrementNonce();
// 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));
IncrementNonce();
return len;
}
}
}

+ 76
- 18
shadowsocks-csharp/Encryption/Stream/StreamEncryptor.cs View File

@@ -117,6 +117,28 @@ namespace Shadowsocks.Encryption.Stream
outlength = size + cipherOffset;
}
public override int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
int cipherOffset = 0;
logger.Trace($"{instanceId} encrypt TCP, generate iv: {!ivReady}");
if (!ivReady)
{
// Generate IV
byte[] ivBytes = RNG.GetBytes(ivLen);
initCipher(ivBytes, true);
ivBytes.CopyTo(cipher);
cipherOffset = ivLen;
cipher = cipher.Slice(cipherOffset);
ivReady = true;
}
int clen = CipherEncrypt(plain, cipher);
logger.DumpBase64($"plain {instanceId}", plain);
logger.DumpBase64($"cipher {instanceId}", cipher);
logger.Dump($"iv {instanceId}", iv, ivLen);
return clen + cipherOffset;
}
private int recieveCtr = 0;
[MethodImpl(MethodImplOptions.Synchronized)]
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength)
@@ -161,6 +183,48 @@ namespace Shadowsocks.Encryption.Stream
outlength = tmp.Length;
}
public override int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
Span<byte> tmp;// = cipher;
logger.Trace($"{instanceId} decrypt TCP, read iv: {!ivReady}");
int cipherOffset = 0;
// is first packet, need read iv
if (!ivReady)
{
// push to buffer in case of not enough data
cipher.CopyTo(sharedBuffer.AsSpan(recieveCtr));
recieveCtr += cipher.Length;
// not enough data for read iv, return 0 byte data
if (recieveCtr <= ivLen)
{
return 0;
}
// start decryption
ivReady = true;
if (ivLen > 0)
{
// read iv
byte[] iv = sharedBuffer.AsSpan(0, ivLen).ToArray();
initCipher(iv, false);
}
else
{
initCipher(Array.Empty<byte>(), false);
}
cipherOffset += ivLen;
tmp = sharedBuffer.AsSpan(ivLen, recieveCtr - ivLen);
}
// read all data from buffer
int len = CipherDecrypt(plain, cipher.Slice(cipherOffset));
logger.DumpBase64($"cipher {instanceId}", cipher.Slice(cipherOffset));
logger.DumpBase64($"plain {instanceId}", plain);
logger.Dump($"iv {instanceId}", iv, ivLen);
return len;
}
#endregion
#region UDP
@@ -178,6 +242,14 @@ namespace Shadowsocks.Encryption.Stream
}
}
public override int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
byte[] iv = RNG.GetBytes(ivLen);
iv.CopyTo(cipher);
initCipher(iv, true);
return ivLen + CipherEncrypt(plain, cipher.Slice(ivLen));
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength)
{
@@ -192,26 +264,12 @@ namespace Shadowsocks.Encryption.Stream
}
}
#endregion
public override int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
throw new NotImplementedException();
}
public override int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
throw new NotImplementedException();
}
public override int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
throw new NotImplementedException();
}
public override int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
throw new NotImplementedException();
initCipher(cipher.Slice(0, ivLen).ToArray(), false);
return CipherDecrypt(plain, cipher.Slice(ivLen));
}
#endregion
}
}

+ 7
- 5
test/CryptographyTest.cs View File

@@ -37,8 +37,10 @@ namespace Shadowsocks.Test
byte[] plain2 = new byte[plain.Length + 16];
random.NextBytes(plain);
encryptor.Encrypt(plain, length, cipher, out int outLen);
decryptor.Decrypt(cipher, outLen, plain2, out int outLen2);
int outLen = encryptor.Encrypt(plain, cipher);
int outLen2 = decryptor.Decrypt(plain2, cipher.AsSpan(0, outLen));
//encryptor.Encrypt(plain, length, cipher, out int outLen);
//decryptor.Decrypt(cipher, outLen, plain2, out int outLen2);
Assert.AreEqual(length, outLen2);
ArrayEqual<byte>(plain.AsSpan(0, length).ToArray(), plain2.AsSpan(0, length).ToArray());
}
@@ -123,16 +125,16 @@ namespace Shadowsocks.Test
Assert.IsFalse(encryptionFailed);
}
#endregion
// encryption test cases
private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor)
{
SingleEncryptionTestCase(encryptor, decryptor, 16384);
SingleEncryptionTestCase(encryptor, decryptor, 7); // for not aligned data
SingleEncryptionTestCase(encryptor, decryptor, 1000);
SingleEncryptionTestCase(encryptor, decryptor, 12333);
SingleEncryptionTestCase(encryptor, decryptor, 16384);
}
[TestMethod]
public void TestAEADAesGcmNativeEncryption()
{


Loading…
Cancel
Save