diff --git a/crypto/src/crypto/modes/CcmBlockCipher.cs b/crypto/src/crypto/modes/CcmBlockCipher.cs
index 256cc1b13..46e7b9c55 100644
--- a/crypto/src/crypto/modes/CcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/CcmBlockCipher.cs
@@ -149,10 +149,11 @@ namespace Org.BouncyCastle.Crypto.Modes
return 0;
}
- public virtual int DoFinal(
- byte[] outBytes,
- int outOff)
+ public virtual int DoFinal(byte[] outBytes, int outOff)
{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return DoFinal(outBytes.AsSpan(outOff));
+#else
byte[] input = data.GetBuffer();
int inLen = Convert.ToInt32(data.Length);
@@ -161,7 +162,22 @@ namespace Org.BouncyCastle.Crypto.Modes
Reset();
return len;
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ byte[] input = data.GetBuffer();
+ int inLen = Convert.ToInt32(data.Length);
+
+ int len = ProcessPacket(input.AsSpan(0, inLen), output);
+
+ Reset();
+
+ return len;
}
+#endif
public virtual void Reset()
{
@@ -341,8 +357,106 @@ namespace Org.BouncyCastle.Crypto.Modes
return outputLen;
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
+ {
+ int inLen = input.Length;
+
+ // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
+ // Need to keep the CTR and CBC Mac parts around and reset
+ if (keyParam == null)
+ throw new InvalidOperationException("CCM cipher unitialized.");
+
+ int n = nonce.Length;
+ int q = 15 - n;
+ if (q < 4)
+ {
+ int limitLen = 1 << (8 * q);
+ if (inLen >= limitLen)
+ throw new InvalidOperationException("CCM packet too large for choice of q.");
+ }
+
+ byte[] iv = new byte[BlockSize];
+ iv[0] = (byte)((q - 1) & 0x7);
+ nonce.CopyTo(iv, 1);
+
+ IBlockCipher ctrCipher = new SicBlockCipher(cipher);
+ ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
+
+ int outputLen;
+ int index = 0;
+ Span<byte> block = stackalloc byte[BlockSize];
+
+ if (forEncryption)
+ {
+ outputLen = inLen + macSize;
+ Check.OutputLength(output, outputLen, "output buffer too short");
+
+ CalculateMac(input, macBlock);
+
+ byte[] encMac = new byte[BlockSize];
+ ctrCipher.ProcessBlock(macBlock, encMac); // S0
+
+ while (index < (inLen - BlockSize)) // S1...
+ {
+ ctrCipher.ProcessBlock(input[index..], output[index..]);
+ index += BlockSize;
+ }
+
+ input[index..].CopyTo(block);
+
+ ctrCipher.ProcessBlock(block, block);
+
+ block[..(inLen - index)].CopyTo(output[index..]);
+
+ encMac.AsSpan(0, macSize).CopyTo(output[inLen..]);
+ }
+ else
+ {
+ if (inLen < macSize)
+ throw new InvalidCipherTextException("data too short");
+
+ outputLen = inLen - macSize;
+ Check.OutputLength(output, outputLen, "output buffer too short");
+
+ input[outputLen..].CopyTo(macBlock);
+
+ ctrCipher.ProcessBlock(macBlock, macBlock);
+
+ for (int i = macSize; i != macBlock.Length; i++)
+ {
+ macBlock[i] = 0;
+ }
+
+ while (index < (outputLen - BlockSize))
+ {
+ ctrCipher.ProcessBlock(input[index..], output[index..]);
+ index += BlockSize;
+ }
+
+ input[index..outputLen].CopyTo(block);
+
+ ctrCipher.ProcessBlock(block, block);
+
+ block[..(outputLen - index)].CopyTo(output[index..]);
+
+ Span<byte> calculatedMacBlock = stackalloc byte[BlockSize];
+
+ CalculateMac(output[..outputLen], calculatedMacBlock);
+
+ if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
+ throw new InvalidCipherTextException("mac check in CCM failed");
+ }
+
+ return outputLen;
+ }
+#endif
+
private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return CalculateMac(data.AsSpan(dataOff, dataLen), macBlock);
+#else
IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
cMac.Init(keyParam);
@@ -429,8 +543,101 @@ namespace Org.BouncyCastle.Crypto.Modes
cMac.BlockUpdate(data, dataOff, dataLen);
return cMac.DoFinal(macBlock, 0);
+#endif
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private int CalculateMac(ReadOnlySpan<byte> data, Span<byte> macBlock)
+ {
+ IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
+
+ cMac.Init(keyParam);
+
+ //
+ // build b0
+ //
+ byte[] b0 = new byte[16];
+
+ if (HasAssociatedText())
+ {
+ b0[0] |= 0x40;
+ }
+
+ b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
+
+ b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
+
+ Array.Copy(nonce, 0, b0, 1, nonce.Length);
+
+ int q = data.Length;
+ int count = 1;
+ while (q > 0)
+ {
+ b0[b0.Length - count] = (byte)(q & 0xff);
+ q >>= 8;
+ count++;
+ }
+
+ cMac.BlockUpdate(b0, 0, b0.Length);
+
+ //
+ // process associated text
+ //
+ if (HasAssociatedText())
+ {
+ int extra;
+
+ int textLength = GetAssociatedTextLength();
+ if (textLength < ((1 << 16) - (1 << 8)))
+ {
+ cMac.Update((byte)(textLength >> 8));
+ cMac.Update((byte)textLength);
+
+ extra = 2;
+ }
+ else // can't go any higher than 2^32
+ {
+ cMac.Update((byte)0xff);
+ cMac.Update((byte)0xfe);
+ cMac.Update((byte)(textLength >> 24));
+ cMac.Update((byte)(textLength >> 16));
+ cMac.Update((byte)(textLength >> 8));
+ cMac.Update((byte)textLength);
+
+ extra = 6;
+ }
+
+ if (initialAssociatedText != null)
+ {
+ cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
+ }
+ if (associatedText.Length > 0)
+ {
+ byte[] input = associatedText.GetBuffer();
+ int len = Convert.ToInt32(associatedText.Length);
+
+ cMac.BlockUpdate(input, 0, len);
+ }
+
+ extra = (extra + textLength) % 16;
+ if (extra != 0)
+ {
+ for (int i = extra; i < 16; ++i)
+ {
+ cMac.Update((byte)0x00);
+ }
+ }
+ }
+
+ //
+ // add the text
+ //
+ cMac.BlockUpdate(data);
+
+ return cMac.DoFinal(macBlock);
+ }
+#endif
+
private int GetMacSize(bool forEncryption, int requestedMacBits)
{
if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15)))
diff --git a/crypto/src/crypto/modes/ChaCha20Poly1305.cs b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
index 385977fd5..9e30dc510 100644
--- a/crypto/src/crypto/modes/ChaCha20Poly1305.cs
+++ b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
@@ -397,6 +397,9 @@ namespace Org.BouncyCastle.Crypto.Modes
if (outOff < 0)
throw new ArgumentException("cannot be negative", "outOff");
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return DoFinal(outBytes.AsSpan(outOff));
+#else
CheckData();
Array.Clear(mMac, 0, MacSize);
@@ -423,9 +426,7 @@ namespace Org.BouncyCastle.Crypto.Modes
FinishData(State.DecFinal);
if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen))
- {
throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
- }
break;
}
@@ -453,8 +454,69 @@ namespace Org.BouncyCastle.Crypto.Modes
Reset(false, true);
return resultLen;
+#endif
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ 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(output, resultLen, "output buffer too short");
+
+ if (resultLen > 0)
+ {
+ mPoly1305.BlockUpdate(mBuf, 0, resultLen);
+ ProcessData(mBuf.AsSpan(0, resultLen), output);
+ }
+
+ 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(output, resultLen, "output buffer too short");
+
+ if (mBufPos > 0)
+ {
+ ProcessData(mBuf.AsSpan(0, mBufPos), output);
+ mPoly1305.BlockUpdate(output[..mBufPos]);
+ }
+
+ FinishData(State.EncFinal);
+
+ mMac.AsSpan(0, MacSize).CopyTo(output[mBufPos..]);
+ break;
+ }
+ default:
+ throw new InvalidOperationException();
+ }
+
+ Reset(false, true);
+
+ return resultLen;
+ }
+#endif
+
public virtual byte[] GetMac()
{
return Arrays.Clone(mMac);
@@ -577,6 +639,16 @@ namespace Org.BouncyCastle.Crypto.Modes
this.mDataCount = IncrementCount(mDataCount, 128U, DataLimit);
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private void ProcessData(ReadOnlySpan<byte> input, Span<byte> output)
+ {
+ Check.OutputLength(output, input.Length, "output buffer too short");
+
+ mChacha20.ProcessBytes(input, output);
+
+ this.mDataCount = IncrementCount(mDataCount, (uint)input.Length, DataLimit);
+ }
+#else
private void ProcessData(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
{
Check.OutputLength(outBytes, outOff, inLen, "output buffer too short");
@@ -585,6 +657,7 @@ namespace Org.BouncyCastle.Crypto.Modes
this.mDataCount = IncrementCount(mDataCount, (uint)inLen, DataLimit);
}
+#endif
private void Reset(bool clearMac, bool resetCipher)
{
diff --git a/crypto/src/crypto/modes/EAXBlockCipher.cs b/crypto/src/crypto/modes/EAXBlockCipher.cs
index ffe32ec68..440b5f439 100644
--- a/crypto/src/crypto/modes/EAXBlockCipher.cs
+++ b/crypto/src/crypto/modes/EAXBlockCipher.cs
@@ -238,11 +238,12 @@ namespace Org.BouncyCastle.Crypto.Modes
return resultLen;
}
- public virtual int DoFinal(
- byte[] outBytes,
- int outOff)
+ public virtual int DoFinal(byte[] outBytes, int outOff)
{
- InitCipher();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return DoFinal(outBytes.AsSpan(outOff));
+#else
+ InitCipher();
int extra = bufOff;
byte[] tmp = new byte[bufBlock.Length];
@@ -251,7 +252,7 @@ namespace Org.BouncyCastle.Crypto.Modes
if (forEncryption)
{
- Check.OutputLength(outBytes, outOff, extra + macSize, "Output buffer too short");
+ Check.OutputLength(outBytes, outOff, extra + macSize, "output buffer too short");
cipher.ProcessBlock(bufBlock, 0, tmp, 0);
@@ -272,7 +273,7 @@ namespace Org.BouncyCastle.Crypto.Modes
if (extra < macSize)
throw new InvalidCipherTextException("data too short");
- Check.OutputLength(outBytes, outOff, extra - macSize, "Output buffer too short");
+ Check.OutputLength(outBytes, outOff, extra - macSize, "output buffer too short");
if (extra > macSize)
{
@@ -292,9 +293,66 @@ namespace Org.BouncyCastle.Crypto.Modes
return extra - macSize;
}
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ InitCipher();
+
+ int extra = bufOff;
+ Span<byte> tmp = stackalloc byte[bufBlock.Length];
+
+ bufOff = 0;
+
+ if (forEncryption)
+ {
+ Check.OutputLength(output, extra + macSize, "output buffer too short");
+
+ cipher.ProcessBlock(bufBlock, tmp);
+
+ tmp[..extra].CopyTo(output);
+
+ mac.BlockUpdate(tmp[..extra]);
+
+ CalculateMac();
+
+ macBlock.AsSpan(0, macSize).CopyTo(output[extra..]);
+
+ Reset(false);
+
+ return extra + macSize;
+ }
+ else
+ {
+ if (extra < macSize)
+ throw new InvalidCipherTextException("data too short");
+
+ Check.OutputLength(output, extra - macSize, "output buffer too short");
+
+ if (extra > macSize)
+ {
+ mac.BlockUpdate(bufBlock.AsSpan(0, extra - macSize));
+
+ cipher.ProcessBlock(bufBlock, tmp);
+
+ tmp[..(extra - macSize)].CopyTo(output);
+ }
+
+ CalculateMac();
+
+ if (!VerifyMac(bufBlock, extra - macSize))
+ throw new InvalidCipherTextException("mac check in EAX failed");
+
+ Reset(false);
+
+ return extra - macSize;
+ }
}
+#endif
- public virtual byte[] GetMac()
+ public virtual byte[] GetMac()
{
byte[] mac = new byte[macSize];
diff --git a/crypto/src/crypto/modes/GCMBlockCipher.cs b/crypto/src/crypto/modes/GCMBlockCipher.cs
index bf9c14e28..c2b2cf86d 100644
--- a/crypto/src/crypto/modes/GCMBlockCipher.cs
+++ b/crypto/src/crypto/modes/GCMBlockCipher.cs
@@ -510,7 +510,7 @@ namespace Org.BouncyCastle.Crypto.Modes
if (forEncryption)
{
- Check.OutputLength(output, outOff, extra + macSize, "Output buffer too short");
+ Check.OutputLength(output, outOff, extra + macSize, "output buffer too short");
}
else
{
@@ -519,7 +519,7 @@ namespace Org.BouncyCastle.Crypto.Modes
extra -= macSize;
- Check.OutputLength(output, outOff, extra, "Output buffer too short");
+ Check.OutputLength(output, outOff, extra, "output buffer too short");
}
if (extra > 0)
@@ -607,6 +607,21 @@ namespace Org.BouncyCastle.Crypto.Modes
return resultLen;
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ // TODO[span] Implement efficiently
+
+ int outputLen = GetOutputSize(0);
+ Check.OutputLength(output, outputLen, "output buffer too short");
+
+ byte[] bytes = new byte[outputLen];
+ int len = DoFinal(bytes, 0);
+ bytes[..len].CopyTo(output);
+ return len;
+ }
+#endif
+
public virtual void Reset()
{
Reset(true);
diff --git a/crypto/src/crypto/modes/GcmSivBlockCipher.cs b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
index 284a952a6..d2f17809d 100644
--- a/crypto/src/crypto/modes/GcmSivBlockCipher.cs
+++ b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
@@ -295,8 +295,7 @@ namespace Org.BouncyCastle.Crypto.Modes
public virtual void ProcessAadBytes(byte[] pData, int pOffset, int pLen)
{
- /* Check input buffer */
- CheckBuffer(pData, pOffset, pLen, false);
+ Check.DataLength(pData, pOffset, pLen, "input buffer too short");
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
ProcessAadBytes(pData.AsSpan(pOffset, pLen));
@@ -345,8 +344,7 @@ namespace Org.BouncyCastle.Crypto.Modes
/* Check that we have initialised */
CheckStatus(pLen);
- /* Check input buffer */
- CheckBuffer(pData, pOffset, pLen, false);
+ Check.DataLength(pData, pOffset, pLen, "input buffer too short");
/* Store the data */
if (forEncryption)
@@ -365,20 +363,22 @@ namespace Org.BouncyCastle.Crypto.Modes
public virtual int DoFinal(byte[] pOutput, int pOffset)
{
+ Check.OutputLength(pOutput, pOffset, GetOutputSize(0), "output buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return DoFinal(pOutput.AsSpan(pOffset));
+#else
/* Check that we have initialised */
CheckStatus(0);
- /* Check output buffer */
- CheckBuffer(pOutput, pOffset, GetOutputSize(0), true);
-
/* If we are encrypting */
if (forEncryption)
{
/* Derive the tag */
- byte[] myTag = calculateTag();
+ byte[] myTag = CalculateTag();
/* encrypt the plain text */
- int myDataLen = BUFLEN + encryptPlain(myTag, pOutput, pOffset);
+ int myDataLen = BUFLEN + EncryptPlain(myTag, pOutput, pOffset);
/* Add the tag to the output */
Array.Copy(myTag, 0, pOutput, pOffset + Convert.ToInt32(thePlain.Length), BUFLEN);
@@ -392,7 +392,7 @@ namespace Org.BouncyCastle.Crypto.Modes
else
{
/* decrypt to plain text */
- decryptPlain();
+ DecryptPlain();
/* Release plain text */
int myDataLen = Streams.WriteBufTo(thePlain, pOutput, pOffset);
@@ -401,8 +401,54 @@ namespace Org.BouncyCastle.Crypto.Modes
ResetStreams();
return myDataLen;
}
+#endif
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ /* Check that we have initialised */
+ CheckStatus(0);
+
+ Check.OutputLength(output, GetOutputSize(0), "output buffer too short");
+
+ /* If we are encrypting */
+ if (forEncryption)
+ {
+ /* Derive the tag */
+ byte[] myTag = CalculateTag();
+
+ /* encrypt the plain text */
+ int myDataLen = BUFLEN + EncryptPlain(myTag, output);
+
+ /* Add the tag to the output */
+ myTag.AsSpan(0, BUFLEN).CopyTo(output[Convert.ToInt32(thePlain.Length)..]);
+
+ /* Reset the streams */
+ ResetStreams();
+ return myDataLen;
+
+ /* else we are decrypting */
+ }
+ else
+ {
+ /* decrypt to plain text */
+ DecryptPlain();
+
+ /* Release plain text */
+ if (!thePlain.TryGetBuffer(out var buffer))
+ throw new InvalidOperationException();
+
+ buffer.AsSpan().CopyTo(output);
+ int myDataLen = buffer.Count;
+
+ /* Reset the streams */
+ ResetStreams();
+ return myDataLen;
+ }
+ }
+#endif
+
public virtual byte[] GetMac()
{
throw new InvalidOperationException();
@@ -468,37 +514,43 @@ namespace Org.BouncyCastle.Crypto.Modes
return pBuffer == null ? 0 : pBuffer.Length;
}
- /**
- * Check buffer.
- * @param pBuffer the buffer
- * @param pOffset the offset
- * @param pLen the length
- * @param pOutput is this an output buffer?
- */
- private static void CheckBuffer(byte[] pBuffer, int pOffset, int pLen, bool pOutput)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private int EncryptPlain(byte[] pCounter, Span<byte> target)
{
- /* Access lengths */
- int myBufLen = bufLength(pBuffer);
- int myLast = pOffset + pLen;
+ byte[] thePlainBuf = thePlain.GetBuffer();
+ int thePlainLen = Convert.ToInt32(thePlain.Length);
- /* Check for negative values and buffer overflow */
- bool badLen = pLen < 0 || pOffset < 0 || myLast < 0;
- if (badLen || myLast > myBufLen)
+ byte[] mySrc = thePlainBuf;
+ byte[] myCounter = Arrays.Clone(pCounter);
+ myCounter[BUFLEN - 1] |= MASK;
+ byte[] myMask = new byte[BUFLEN];
+ long myRemaining = thePlainLen;
+ int myOff = 0;
+
+ /* While we have data to process */
+ while (myRemaining > 0)
{
- throw pOutput
- ? new OutputLengthException("output buffer too short.")
- : new DataLengthException("input buffer too short.");
+ /* Generate the next mask */
+ theCipher.ProcessBlock(myCounter, 0, myMask, 0);
+
+ /* Xor data into mask */
+ int myLen = (int)System.Math.Min(BUFLEN, myRemaining);
+ xorBlock(myMask, mySrc, myOff, myLen);
+
+ /* Copy encrypted data to output */
+ myMask.AsSpan(0, myLen).CopyTo(target[myOff..]);
+
+ /* Adjust counters */
+ myRemaining -= myLen;
+ myOff += myLen;
+ incrementCounter(myCounter);
}
- }
- /**
- * encrypt data stream.
- * @param pCounter the counter
- * @param pTarget the target buffer
- * @param pOffset the target offset
- * @return the length of data encrypted
- */
- private int encryptPlain(byte[] pCounter, byte[] pTarget, int pOffset)
+ /* Return the amount of data processed */
+ return thePlainLen;
+ }
+#else
+ private int EncryptPlain(byte[] pCounter, byte[] pTarget, int pOffset)
{
byte[] thePlainBuf = thePlain.GetBuffer();
int thePlainLen = Convert.ToInt32(thePlain.Length);
@@ -532,12 +584,9 @@ namespace Org.BouncyCastle.Crypto.Modes
/* Return the amount of data processed */
return thePlainLen;
}
+#endif
- /**
- * decrypt data stream.
- * @throws InvalidCipherTextException on data too short or mac check failed
- */
- private void decryptPlain()
+ private void DecryptPlain()
{
byte[] theEncDataBuf = theEncData.GetBuffer();
int theEncDataLen = Convert.ToInt32(theEncData.Length);
@@ -579,7 +628,7 @@ namespace Org.BouncyCastle.Crypto.Modes
}
/* Derive and check the tag */
- byte[] myTag = calculateTag();
+ byte[] myTag = CalculateTag();
if (!Arrays.ConstantTimeAreEqual(myTag, myExpected))
{
Reset();
@@ -591,7 +640,7 @@ namespace Org.BouncyCastle.Crypto.Modes
* calculate tag.
* @return the calculated tag
*/
- private byte[] calculateTag()
+ private byte[] CalculateTag()
{
/* Complete the hash */
theDataHasher.completeHash();
diff --git a/crypto/src/crypto/modes/IAeadCipher.cs b/crypto/src/crypto/modes/IAeadCipher.cs
index c61e13b01..f80f3a247 100644
--- a/crypto/src/crypto/modes/IAeadCipher.cs
+++ b/crypto/src/crypto/modes/IAeadCipher.cs
@@ -83,6 +83,10 @@ namespace Org.BouncyCastle.Crypto.Modes
*/
int DoFinal(byte[] outBytes, int outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ int DoFinal(Span<byte> output);
+#endif
+
/**
* Return the value of the MAC associated with the last stream processed.
*
diff --git a/crypto/src/crypto/modes/KCcmBlockCipher.cs b/crypto/src/crypto/modes/KCcmBlockCipher.cs
index afa68a794..db86cf890 100644
--- a/crypto/src/crypto/modes/KCcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/KCcmBlockCipher.cs
@@ -248,6 +248,9 @@ namespace Org.BouncyCastle.Crypto.Modes
Check.DataLength(input, inOff, len, "input buffer too short");
Check.OutputLength(output, outOff, len, "output buffer too short");
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return ProcessPacket(input.AsSpan(inOff, len), output.AsSpan(outOff));
+#else
if (associatedText.Length > 0)
{
byte[] aad = associatedText.GetBuffer();
@@ -268,7 +271,7 @@ namespace Org.BouncyCastle.Crypto.Modes
int totalLength = len;
while (totalLength > 0)
{
- ProcessBlock(input, inOff, len, output, outOff);
+ ProcessBlock(input, inOff, output, outOff);
totalLength -= engine.GetBlockSize();
inOff += engine.GetBlockSize();
outOff += engine.GetBlockSize();
@@ -302,7 +305,7 @@ namespace Org.BouncyCastle.Crypto.Modes
for (int blockNum = 0; blockNum<blocks; blockNum++)
{
- ProcessBlock(input, inOff, len, output, outOff);
+ ProcessBlock(input, inOff, output, outOff);
inOff += engine.GetBlockSize();
outOff += engine.GetBlockSize();
@@ -350,43 +353,190 @@ namespace Org.BouncyCastle.Crypto.Modes
return len - macSize;
}
+#endif
}
- private void ProcessBlock(byte[] input, int inOff, int len, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
{
+ int len = input.Length;
+ Check.OutputLength(output, len, "output buffer too short");
+
+ if (associatedText.Length > 0)
+ {
+ byte[] aad = associatedText.GetBuffer();
+ int aadLen = Convert.ToInt32(associatedText.Length);
+
+ int dataLen = Convert.ToInt32(data.Length) - (forEncryption ? 0 : macSize);
+
+ ProcessAAD(aad, 0, aadLen, dataLen);
+ }
+
+ int blockSize = engine.GetBlockSize(), index = 0;
+ if (forEncryption)
+ {
+ Check.DataLength(len % blockSize != 0, "partial blocks not supported");
+
+ CalculateMac(input);
+ engine.ProcessBlock(nonce, s);
+
+ int totalLength = len;
+ while (totalLength > 0)
+ {
+ ProcessBlock(input[index..], output[index..]);
+ totalLength -= blockSize;
+ index += blockSize;
+ }
+
+ for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, buffer);
+
+ for (int byteIndex = 0; byteIndex < macSize; byteIndex++)
+ {
+ output[index + byteIndex] = (byte)(buffer[byteIndex] ^ macBlock[byteIndex]);
+ }
+
+ Array.Copy(macBlock, 0, mac, 0, macSize);
+
+ Reset();
+
+ return len + macSize;
+ }
+ else
+ {
+ Check.DataLength((len - macSize) % blockSize != 0, "partial blocks not supported");
+
+ engine.ProcessBlock(nonce, 0, s, 0);
+
+ int blocks = len / engine.GetBlockSize();
+
+ for (int blockNum = 0; blockNum < blocks; blockNum++)
+ {
+ ProcessBlock(input[index..], output[index..]);
+ index += blockSize;
+ }
+
+ if (len > index)
+ {
+ for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, buffer);
+
+ for (int byteIndex = 0; byteIndex < macSize; byteIndex++)
+ {
+ output[index + byteIndex] = (byte)(buffer[byteIndex] ^ input[index + byteIndex]);
+ }
+ index += macSize;
+ }
+
+ for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, buffer);
+
+ output[(index - macSize)..index].CopyTo(buffer);
+ CalculateMac(output[..(index - macSize)]);
+
+ Array.Copy(macBlock, 0, mac, 0, macSize);
+
+ Span<byte> calculatedMac = stackalloc byte[macSize];
+
+ buffer.AsSpan(0, macSize).CopyTo(calculatedMac);
+
+ if (!Arrays.ConstantTimeAreEqual(mac.AsSpan(0, macSize), calculatedMac))
+ throw new InvalidCipherTextException("mac check failed");
+
+ Reset();
+
+ return len - macSize;
+ }
+ }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private void CalculateMac(ReadOnlySpan<byte> authText)
+ {
+ int blockSize = engine.GetBlockSize();
+
+ while (!authText.IsEmpty)
+ {
+ for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
+ {
+ macBlock[byteIndex] ^= authText[byteIndex];
+ }
+
+ engine.ProcessBlock(macBlock, macBlock);
+
+ authText = authText[blockSize..];
+ }
+ }
+
+ private void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+ {
for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
{
s[byteIndex] += counter[byteIndex];
}
- engine.ProcessBlock(s, 0, buffer, 0);
+ engine.ProcessBlock(s, buffer);
- for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ int blockSize = engine.GetBlockSize();
+ for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
{
- output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+ output[byteIndex] = (byte)(buffer[byteIndex] ^ input[byteIndex]);
}
}
-
+#else
private void CalculateMac(byte[] authText, int authOff, int len)
{
+ int blockSize = engine.GetBlockSize();
int totalLen = len;
while (totalLen > 0)
{
- for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
{
macBlock[byteIndex] ^= authText[authOff + byteIndex];
}
engine.ProcessBlock(macBlock, 0, macBlock, 0);
- totalLen -= engine.GetBlockSize();
- authOff += engine.GetBlockSize();
+ totalLen -= blockSize;
+ authOff += blockSize;
}
}
+ private void ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
+ {
+
+ for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+ {
+ s[byteIndex] += counter[byteIndex];
+ }
+
+ engine.ProcessBlock(s, 0, buffer, 0);
+
+ for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+ {
+ output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+ }
+ }
+#endif
+
public virtual int DoFinal(byte[] output, int outOff)
{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return DoFinal(output.AsSpan(outOff));
+#else
byte[] buf = data.GetBuffer();
int bufLen = Convert.ToInt32(data.Length);
@@ -395,8 +545,23 @@ namespace Org.BouncyCastle.Crypto.Modes
Reset();
return len;
+#endif
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ byte[] buf = data.GetBuffer();
+ int bufLen = Convert.ToInt32(data.Length);
+
+ int len = ProcessPacket(buf.AsSpan(0, bufLen), output);
+
+ Reset();
+
+ return len;
+ }
+#endif
+
public virtual byte[] GetMac()
{
return Arrays.Clone(mac);
diff --git a/crypto/src/crypto/modes/OCBBlockCipher.cs b/crypto/src/crypto/modes/OCBBlockCipher.cs
index db6aa39ae..8281c96c1 100644
--- a/crypto/src/crypto/modes/OCBBlockCipher.cs
+++ b/crypto/src/crypto/modes/OCBBlockCipher.cs
@@ -331,6 +331,9 @@ namespace Org.BouncyCastle.Crypto.Modes
public virtual int DoFinal(byte[] output, int outOff)
{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return DoFinal(output.AsSpan(outOff));
+#else
/*
* For decryption, get the tag from the end of the message
*/
@@ -371,7 +374,7 @@ namespace Org.BouncyCastle.Crypto.Modes
Xor(mainBlock, Pad);
- Check.OutputLength(output, outOff, mainBlockPos, "Output buffer too short");
+ Check.OutputLength(output, outOff, mainBlockPos, "output buffer too short");
Array.Copy(mainBlock, 0, output, outOff, mainBlockPos);
if (!forEncryption)
@@ -399,7 +402,7 @@ namespace Org.BouncyCastle.Crypto.Modes
if (forEncryption)
{
- Check.OutputLength(output, outOff, resultLen + macSize, "Output buffer too short");
+ Check.OutputLength(output, outOff, resultLen + macSize, "output buffer too short");
// Append tag to the message
Array.Copy(macBlock, 0, output, outOff + resultLen, macSize);
@@ -415,8 +418,99 @@ namespace Org.BouncyCastle.Crypto.Modes
Reset(false);
return resultLen;
+#endif
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual int DoFinal(Span<byte> output)
+ {
+ /*
+ * For decryption, get the tag from the end of the message
+ */
+ byte[] tag = null;
+ if (!forEncryption)
+ {
+ if (mainBlockPos < macSize)
+ throw new InvalidCipherTextException("data too short");
+
+ mainBlockPos -= macSize;
+ tag = new byte[macSize];
+ Array.Copy(mainBlock, mainBlockPos, tag, 0, macSize);
+ }
+
+ /*
+ * HASH: Process any final partial block; compute final hash value
+ */
+ if (hashBlockPos > 0)
+ {
+ OCB_extend(hashBlock, hashBlockPos);
+ UpdateHASH(L_Asterisk);
+ }
+
+ /*
+ * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block
+ */
+ if (mainBlockPos > 0)
+ {
+ if (forEncryption)
+ {
+ OCB_extend(mainBlock, mainBlockPos);
+ Xor(Checksum, mainBlock);
+ }
+
+ Xor(OffsetMAIN, L_Asterisk);
+
+ byte[] Pad = new byte[16];
+ hashCipher.ProcessBlock(OffsetMAIN, 0, Pad, 0);
+
+ Xor(mainBlock, Pad);
+
+ Check.OutputLength(output, mainBlockPos, "output buffer too short");
+ mainBlock.AsSpan(0, mainBlockPos).CopyTo(output);
+
+ if (!forEncryption)
+ {
+ OCB_extend(mainBlock, mainBlockPos);
+ Xor(Checksum, mainBlock);
+ }
+ }
+
+ /*
+ * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag
+ */
+ Xor(Checksum, OffsetMAIN);
+ Xor(Checksum, L_Dollar);
+ hashCipher.ProcessBlock(Checksum, 0, Checksum, 0);
+ Xor(Checksum, Sum);
+
+ this.macBlock = new byte[macSize];
+ Array.Copy(Checksum, 0, macBlock, 0, macSize);
+
+ /*
+ * Validate or append tag and reset this cipher for the next run
+ */
+ int resultLen = mainBlockPos;
+
+ if (forEncryption)
+ {
+ // Append tag to the message
+ Check.OutputLength(output, resultLen + macSize, "output buffer too short");
+ macBlock.AsSpan(0, macSize).CopyTo(output[resultLen..]);
+ resultLen += macSize;
+ }
+ else
+ {
+ // Compare the tag from the message with the calculated one
+ if (!Arrays.ConstantTimeAreEqual(macBlock, tag))
+ throw new InvalidCipherTextException("mac check in OCB failed");
+ }
+
+ Reset(false);
+
+ return resultLen;
+ }
+#endif
+
public virtual void Reset()
{
Reset(true);
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 7a1e80115..d3dae98a9 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -124,11 +124,27 @@ namespace Org.BouncyCastle.Utilities
int d = 0;
for (int i = 0; i < len; ++i)
{
- d |= (a[aOff + i] ^ b[bOff + i]);
+ d |= a[aOff + i] ^ b[bOff + i];
}
return 0 == d;
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public static bool ConstantTimeAreEqual(Span<byte> a, Span<byte> b)
+ {
+ if (a.Length != b.Length)
+ throw new ArgumentException("Spans to compare must have equal length");
+
+ int d = 0;
+ for (int i = 0, count = a.Length; i < count; ++i)
+ {
+ d |= a[i] ^ b[i];
+ }
+ return 0 == d;
+
+ }
+#endif
+
public static bool AreEqual(
int[] a,
int[] b)
|