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