@@ -0,0 +1,23 @@ | |||
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,0 +1,264 @@ | |||
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,0 +1,183 @@ | |||
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,0 +1,363 @@ | |||
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,0 +1,56 @@ | |||
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,0 +1,98 @@ | |||
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,0 +1,588 @@ | |||
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); | |||
} | |||
} | |||
} | |||
} |