培训考核三期,新版培训,网页版培训登录器
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

562 lines
17 KiB

#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
#pragma warning disable
using System;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes
{
public class ChaCha20Poly1305
: 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 = 12;
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 ChaCha7539Engine 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 ChaCha20Poly1305()
: this(new Poly1305())
{
}
public ChaCha20Poly1305(IMac poly1305)
{
if (null == poly1305)
throw new ArgumentNullException("poly1305");
if (MacSize != poly1305.GetMacSize())
throw new ArgumentException("must be a 128-bit MAC", "poly1305");
this.mChacha20 = new ChaCha7539Engine();
this.mPoly1305 = poly1305;
}
public virtual string AlgorithmName
{
get { return "ChaCha20Poly1305"; }
}
public virtual void Init(bool forEncryption, ICipherParameters parameters)
{
KeyParameter initKeyParam;
byte[] initNonce;
ICipherParameters chacha20Params;
if (parameters is AeadParameters)
{
AeadParameters aeadParams = (AeadParameters)parameters;
int 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);
this.mInitialAad = aeadParams.GetAssociatedText();
}
else if (parameters is ParametersWithIV)
{
ParametersWithIV ivParams = (ParametersWithIV)parameters;
initKeyParam = (KeyParameter)ivParams.Parameters;
initNonce = ivParams.GetIV();
chacha20Params = ivParams;
this.mInitialAad = null;
}
else
{
throw new ArgumentException("invalid parameters passed to ChaCha20Poly1305", "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 96 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);
this.mState = forEncryption ? State.EncInit : State.DecInit;
Reset(true, false);
}
public virtual int GetOutputSize(int len)
{
int total = System.Math.Max(0, len) + mBufPos;
switch (mState)
{
case State.DecInit:
case State.DecAad:
case State.DecData:
return System.Math.Max(0, total - MacSize);
case State.EncInit:
case State.EncAad:
case State.EncData:
return total + MacSize;
default:
throw new InvalidOperationException();
}
}
public virtual int GetUpdateOutputSize(int len)
{
int total = System.Math.Max(0, len) + mBufPos;
switch (mState)
{
case State.DecInit:
case State.DecAad:
case State.DecData:
total = System.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 virtual void ProcessAadByte(byte input)
{
CheckAad();
this.mAadCount = IncrementCount(mAadCount, 1, AadLimit);
mPoly1305.Update(input);
}
public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
{
if (null == inBytes)
throw new ArgumentNullException("inBytes");
if (inOff < 0)
throw new ArgumentException("cannot be negative", "inOff");
if (len < 0)
throw new ArgumentException("cannot be negative", "len");
Check.DataLength(inBytes, inOff, len, "input buffer too short");
CheckAad();
if (len > 0)
{
this.mAadCount = IncrementCount(mAadCount, (uint)len, AadLimit);
mPoly1305.BlockUpdate(inBytes, inOff, len);
}
}
public virtual 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);
this.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);
this.mBufPos = 0;
return BufSize;
}
return 0;
}
default:
throw new InvalidOperationException();
}
}
public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
{
if (null == inBytes)
throw new ArgumentNullException("inBytes");
/*
* Following bc-java, we allow null when no output is expected (e.g. based on a
* GetUpdateOutputSize call).
*/
if (null == outBytes)
{
//throw new ArgumentNullException("outBytes");
}
if (inOff < 0)
throw new ArgumentException("cannot be negative", "inOff");
if (len < 0)
throw new ArgumentException("cannot be negative", "len");
Check.DataLength(inBytes, inOff, len, "input buffer too short");
if (outOff < 0)
throw new ArgumentException("cannot be negative", "outOff");
CheckData();
int resultLen = 0;
switch (mState)
{
case State.DecData:
{
for (int 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);
this.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);
this.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);
this.mBufPos = len;
}
break;
}
default:
throw new InvalidOperationException();
}
return resultLen;
}
public virtual int DoFinal(byte[] outBytes, int outOff)
{
if (null == outBytes)
throw new ArgumentNullException("outBytes");
if (outOff < 0)
throw new ArgumentException("cannot be negative", "outOff");
CheckData();
Array.Clear(mMac, 0, MacSize);
int resultLen = 0;
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 virtual byte[] GetMac()
{
return Arrays.Clone(mMac);
}
public virtual void Reset()
{
Reset(true, true);
}
private void CheckAad()
{
switch (mState)
{
case State.DecInit:
this.mState = State.DecAad;
break;
case State.EncInit:
this.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);
this.mState = nextState;
}
private void FinishData(State nextState)
{
PadMac(mDataCount);
byte[] 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);
this.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()
{
byte[] 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)
{
int partial = (int)count & (MacSize - 1);
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);
this.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);
}
this.mAadCount = 0UL;
this.mDataCount = 0UL;
this.mBufPos = 0;
switch (mState)
{
case State.DecInit:
case State.EncInit:
break;
case State.DecAad:
case State.DecData:
case State.DecFinal:
this.mState = State.DecInit;
break;
case State.EncAad:
case State.EncData:
case State.EncFinal:
this.mState = State.EncFinal;
return;
default:
throw new InvalidOperationException();
}
if (resetCipher)
{
mChacha20.Reset();
}
InitMac();
if (null != mInitialAad)
{
ProcessAadBytes(mInitialAad, 0, mInitialAad.Length);
}
}
}
}
#pragma warning restore
#endif