From 114297f192b5da1b789ea554ed02e7329cc2e9fb Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Tue, 30 Aug 2022 17:35:03 +0700 Subject: Span-based variant for IAeadCipher.DoFinal --- crypto/src/crypto/modes/CcmBlockCipher.cs | 213 ++++++++++++++++++++++++++- crypto/src/crypto/modes/ChaCha20Poly1305.cs | 77 +++++++++- crypto/src/crypto/modes/EAXBlockCipher.cs | 72 ++++++++- crypto/src/crypto/modes/GCMBlockCipher.cs | 19 ++- crypto/src/crypto/modes/GcmSivBlockCipher.cs | 135 +++++++++++------ crypto/src/crypto/modes/IAeadCipher.cs | 4 + crypto/src/crypto/modes/KCcmBlockCipher.cs | 185 +++++++++++++++++++++-- crypto/src/crypto/modes/OCBBlockCipher.cs | 98 +++++++++++- crypto/src/util/Arrays.cs | 18 ++- 9 files changed, 751 insertions(+), 70 deletions(-) (limited to 'crypto/src') 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); @@ -160,8 +161,23 @@ namespace Org.BouncyCastle.Crypto.Modes Reset(); + return len; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public virtual int DoFinal(Span 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 input, Span 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 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 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 data, Span 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 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 input, Span 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) { @@ -290,11 +291,68 @@ namespace Org.BouncyCastle.Crypto.Modes Reset(false); + return extra - macSize; + } +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public virtual int DoFinal(Span output) + { + InitCipher(); + + int extra = bufOff; + Span 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 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 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 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 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 input, Span 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 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 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 input, Span 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 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 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 a, Span 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) -- cgit 1.4.1