BlockSizeInBytes = blockSizeInBytes;
}
- public abstract int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset);
+ public abstract int Transform(ReadOnlySpan<byte> input, Span<byte> output);
- public abstract byte[] TransformFinal(byte[] input, int inputOffset, int count);
+ public abstract int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output);
public int BlockSizeInBytes { get; private set; }
base.Dispose(disposing);
}
- public override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count > 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(output.Length - outputOffset >= count);
+ Debug.Assert(input.Length > 0);
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
int numBytesWritten;
+
if (_encrypting)
{
- numBytesWritten = Interop.BCrypt.BCryptEncrypt(_hKey, input, inputOffset, count, _currentIv, output, outputOffset, output.Length - outputOffset);
+ numBytesWritten = Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output);
}
else
{
- numBytesWritten = Interop.BCrypt.BCryptDecrypt(_hKey, input, inputOffset, count, _currentIv, output, outputOffset, output.Length - outputOffset);
+ numBytesWritten = Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
}
- if (numBytesWritten != count)
+ if (numBytesWritten != input.Length)
{
// CNG gives us no way to tell BCryptDecrypt() that we're decrypting the final block, nor is it performing any
// padding /depadding for us. So there's no excuse for a provider to hold back output for "future calls." Though
return numBytesWritten;
}
- public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+ public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
-
- byte[] output = new byte[count];
- if (count != 0)
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+ int numBytesWritten = 0;
+
+ if (input.Length != 0)
{
- int numBytesWritten = Transform(input, inputOffset, count, output, 0);
- Debug.Assert(numBytesWritten == count); // Our implementation of Transform() guarantees this. See comment above.
+ numBytesWritten = Transform(input, output);
+ Debug.Assert(numBytesWritten == input.Length); // Our implementation of Transform() guarantees this. See comment above.
}
Reset();
- return output;
+ return numBytesWritten;
}
private void Reset()
{
}
- protected sealed override int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ protected override int UncheckedTransformBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
{
//
// If we're decrypting, it's possible to be called with the last blocks of the data, and then
// If we have data saved from a previous call, decrypt that into the output first
if (_heldoverCipher != null)
{
- int depadDecryptLength = BasicSymmetricCipher.Transform(_heldoverCipher, 0, _heldoverCipher.Length, outputBuffer, outputOffset);
- outputOffset += depadDecryptLength;
+ int depadDecryptLength = BasicSymmetricCipher.Transform(_heldoverCipher, outputBuffer);
+ outputBuffer = outputBuffer.Slice(depadDecryptLength);
decryptedBytes += depadDecryptLength;
}
else
}
// Postpone the last block to the next round.
- Debug.Assert(inputCount >= _heldoverCipher.Length, "inputCount >= _heldoverCipher.Length");
- int startOfLastBlock = inputOffset + inputCount - _heldoverCipher.Length;
- Buffer.BlockCopy(inputBuffer, startOfLastBlock, _heldoverCipher, 0, _heldoverCipher.Length);
- inputCount -= _heldoverCipher.Length;
- Debug.Assert(inputCount % InputBlockSize == 0, "Did not remove whole blocks for depadding");
+ Debug.Assert(inputBuffer.Length >= _heldoverCipher.Length, "inputBuffer.Length >= _heldoverCipher.Length");
+ inputBuffer.Slice(inputBuffer.Length - _heldoverCipher.Length).CopyTo(_heldoverCipher);
+ inputBuffer = inputBuffer.Slice(0, inputBuffer.Length - _heldoverCipher.Length);
+ Debug.Assert(inputBuffer.Length % InputBlockSize == 0, "Did not remove whole blocks for depadding");
}
- if (inputCount > 0)
+ if (inputBuffer.Length > 0)
{
- decryptedBytes += BasicSymmetricCipher.Transform(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+ decryptedBytes += BasicSymmetricCipher.Transform(inputBuffer, outputBuffer);
}
return decryptedBytes;
}
- protected sealed override byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ protected override unsafe int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
{
// We can't complete decryption on a partial block
- if (inputCount % InputBlockSize != 0)
+ if (inputBuffer.Length % InputBlockSize != 0)
throw new CryptographicException(SR.Cryptography_PartialBlock);
//
// Otherwise the decryption buffer is just the input data.
//
- byte[]? ciphertext = null;
+ ReadOnlySpan<byte> inputCiphertext;
+ Span<byte> ciphertext;
+ byte[]? rentedCiphertext = null;
+ int rentedCiphertextSize = 0;
- if (_heldoverCipher == null)
+ try
{
- ciphertext = new byte[inputCount];
- Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, 0, inputCount);
+ if (_heldoverCipher == null)
+ {
+ rentedCiphertextSize = inputBuffer.Length;
+ rentedCiphertext = CryptoPool.Rent(inputBuffer.Length);
+ ciphertext = rentedCiphertext.AsSpan(0, inputBuffer.Length);
+ inputCiphertext = inputBuffer;
+ }
+ else
+ {
+ rentedCiphertextSize = _heldoverCipher.Length + inputBuffer.Length;
+ rentedCiphertext = CryptoPool.Rent(rentedCiphertextSize);
+ ciphertext = rentedCiphertext.AsSpan(0, rentedCiphertextSize);
+ _heldoverCipher.AsSpan().CopyTo(ciphertext);
+ inputBuffer.CopyTo(ciphertext.Slice(_heldoverCipher.Length));
+
+ // Decrypt in-place
+ inputCiphertext = ciphertext;
+ }
+
+ int unpaddedLength = 0;
+
+ fixed (byte* pCiphertext = ciphertext)
+ {
+ // Decrypt the data, then strip the padding to get the final decrypted data. Note that even if the cipherText length is 0, we must
+ // invoke TransformFinal() so that the cipher object knows to reset for the next cipher operation.
+ int decryptWritten = BasicSymmetricCipher.TransformFinal(inputCiphertext, ciphertext);
+ Span<byte> decryptedBytes = ciphertext.Slice(0, decryptWritten);
+
+ if (decryptedBytes.Length > 0)
+ {
+ unpaddedLength = GetPaddingLength(decryptedBytes);
+ decryptedBytes.Slice(0, unpaddedLength).CopyTo(outputBuffer);
+ }
+ }
+
+ Reset();
+ return unpaddedLength;
}
- else
+ finally
{
- ciphertext = new byte[_heldoverCipher.Length + inputCount];
- Buffer.BlockCopy(_heldoverCipher, 0, ciphertext, 0, _heldoverCipher.Length);
- Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, _heldoverCipher.Length, inputCount);
+ if (rentedCiphertext != null)
+ {
+ CryptoPool.Return(rentedCiphertext, clearSize: rentedCiphertextSize);
+ }
}
+ }
- // Decrypt the data, then strip the padding to get the final decrypted data. Note that even if the cipherText length is 0, we must
- // invoke TransformFinal() so that the cipher object knows to reset for the next cipher operation.
- byte[] decryptedBytes = BasicSymmetricCipher.TransformFinal(ciphertext, 0, ciphertext.Length);
- byte[] outputData;
- if (ciphertext.Length > 0)
+ protected override unsafe byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ if (DepaddingRequired)
{
- unsafe
+ byte[] rented = CryptoPool.Rent(inputCount + InputBlockSize);
+ int written = 0;
+
+ fixed (byte* pRented = rented)
{
- fixed (byte* decryptedBytesPtr = decryptedBytes)
+ try
{
- outputData = DepadBlock(decryptedBytes, 0, decryptedBytes.Length);
-
- if (outputData != decryptedBytes)
- {
- CryptographicOperations.ZeroMemory(decryptedBytes);
- }
+ written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), rented);
+ return rented.AsSpan(0, written).ToArray();
+ }
+ finally
+ {
+ CryptoPool.Return(rented, clearSize: written);
}
}
}
else
{
- outputData = Array.Empty<byte>();
+#if NETSTANDARD || NETFRAMEWORK || NETCOREAPP3_0
+ byte[] buffer = new byte[inputCount];
+#else
+ byte[] buffer = GC.AllocateUninitializedArray<byte>(inputCount);
+#endif
+ int written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), buffer);
+ Debug.Assert(written == buffer.Length);
+ return buffer;
}
-
- Reset();
- return outputData;
}
protected sealed override void Dispose(bool disposing)
}
/// <summary>
- /// Remove the padding from the last blocks being decrypted
+ /// Gets the length of the padding applied to the block, and validates
+ /// the padding, if possible.
/// </summary>
- private byte[] DepadBlock(byte[] block, int offset, int count)
+ private int GetPaddingLength(ReadOnlySpan<byte> block)
{
- Debug.Assert(block != null && count >= block.Length - offset);
- Debug.Assert(0 <= offset);
- Debug.Assert(0 <= count);
-
int padBytes = 0;
// See PadBlock for a description of the padding modes.
switch (PaddingMode)
{
case PaddingMode.ANSIX923:
- padBytes = block[offset + count - 1];
+ padBytes = block[^1];
// Verify the amount of padding is reasonable
if (padBytes <= 0 || padBytes > InputBlockSize)
}
// Verify that all the padding bytes are 0s
- for (int i = offset + count - padBytes; i < offset + count - 1; i++)
+ for (int i = block.Length - padBytes; i < block.Length - 1; i++)
{
if (block[i] != 0)
{
break;
case PaddingMode.ISO10126:
- padBytes = block[offset + count - 1];
+ padBytes = block[^1];
// Verify the amount of padding is reasonable
if (padBytes <= 0 || padBytes > InputBlockSize)
break;
case PaddingMode.PKCS7:
- padBytes = block[offset + count - 1];
+ padBytes = block[^1];
// Verify the amount of padding is reasonable
if (padBytes <= 0 || padBytes > InputBlockSize)
throw new CryptographicException(SR.Cryptography_InvalidPadding);
// Verify all the padding bytes match the amount of padding
- for (int i = offset + count - padBytes; i < offset + count; i++)
+ for (int i = block.Length - padBytes; i < block.Length - 1; i++)
{
if (block[i] != padBytes)
throw new CryptographicException(SR.Cryptography_InvalidPadding);
throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
}
- // Copy everything but the padding to the output
- byte[] depadded = new byte[count - padBytes];
- Buffer.BlockCopy(block, offset, depadded, 0, depadded.Length);
- return depadded;
+ return block.Length - padBytes;
}
//
{
}
- protected sealed override int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ protected override int UncheckedTransformBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
{
- return BasicSymmetricCipher.Transform(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+ return BasicSymmetricCipher.Transform(inputBuffer, outputBuffer);
}
- protected sealed override unsafe byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ protected override int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
{
- byte[] paddedBlock = PadBlock(inputBuffer, inputOffset, inputCount);
+ // The only caller of this method is the array-allocating overload, outputBuffer is
+ // always new memory, not a user-provided buffer.
+ Debug.Assert(!inputBuffer.Overlaps(outputBuffer));
- fixed (byte* paddedBlockPtr = paddedBlock)
- {
- byte[] output = BasicSymmetricCipher.TransformFinal(paddedBlock, 0, paddedBlock.Length);
+ int padWritten = PadBlock(inputBuffer, outputBuffer);
+ int transformWritten = BasicSymmetricCipher.TransformFinal(outputBuffer.Slice(0, padWritten), outputBuffer);
+
+ // After padding, we should have an even number of blocks, and the same applies
+ // to the transform.
+ Debug.Assert(padWritten == transformWritten);
+
+ return transformWritten;
+ }
+
+ protected override byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ byte[] buffer;
+#if NETSTANDARD || NETFRAMEWORK || NETCOREAPP3_0
+ buffer = new byte[GetCiphertextLength(inputCount)];
+#else
+ buffer = GC.AllocateUninitializedArray<byte>(GetCiphertextLength(inputCount));
+#endif
+ int written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), buffer);
+ Debug.Assert(written == buffer.Length);
+ return buffer;
+ }
+
+ private int GetCiphertextLength(int plaintextLength)
+ {
+ Debug.Assert(plaintextLength >= 0);
- if (paddedBlock != inputBuffer)
- {
- CryptographicOperations.ZeroMemory(paddedBlock);
- }
+ //divisor and factor are same and won't overflow.
+ int wholeBlocks = Math.DivRem(plaintextLength, InputBlockSize, out int remainder) * InputBlockSize;
- return output;
+ switch (PaddingMode)
+ {
+ case PaddingMode.None when (remainder != 0):
+ throw new CryptographicException(SR.Cryptography_PartialBlock);
+ case PaddingMode.None:
+ case PaddingMode.Zeros when (remainder == 0):
+ return plaintextLength;
+ case PaddingMode.Zeros:
+ case PaddingMode.PKCS7:
+ case PaddingMode.ANSIX923:
+ case PaddingMode.ISO10126:
+ return checked(wholeBlocks + InputBlockSize);
+ default:
+ Debug.Fail($"Unknown padding mode {PaddingMode}.");
+ throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
}
}
- private byte[] PadBlock(byte[] block, int offset, int count)
+ private int PadBlock(ReadOnlySpan<byte> block, Span<byte> destination)
{
- byte[] result;
- int padBytes = InputBlockSize - (count % InputBlockSize);
+ int count = block.Length;
+ int paddingRemainder = count % InputBlockSize;
+ int padBytes = InputBlockSize - paddingRemainder;
switch (PaddingMode)
{
+ case PaddingMode.None when (paddingRemainder != 0):
+ throw new CryptographicException(SR.Cryptography_PartialBlock);
+
case PaddingMode.None:
- if (count % InputBlockSize != 0)
- throw new CryptographicException(SR.Cryptography_PartialBlock);
+ if (destination.Length < count)
+ {
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+ }
- result = new byte[count];
- Buffer.BlockCopy(block, offset, result, 0, result.Length);
- break;
+ block.CopyTo(destination);
+ return count;
// ANSI padding fills the blocks with zeros and adds the total number of padding bytes as
// the last pad byte, adding an extra block if the last block is complete.
//
- // x 00 00 00 00 00 00 07
+ // xx 00 00 00 00 00 00 07
case PaddingMode.ANSIX923:
- result = new byte[count + padBytes];
+ int ansiSize = count + padBytes;
- Buffer.BlockCopy(block, offset, result, 0, count);
- result[result.Length - 1] = (byte)padBytes;
+ if (destination.Length < ansiSize)
+ {
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+ }
- break;
+ block.CopyTo(destination);
+ destination.Slice(count, padBytes - 1).Clear();
+ destination[count + padBytes - 1] = (byte)padBytes;
+ return ansiSize;
// ISO padding fills the blocks up with random bytes and adds the total number of padding
// bytes as the last pad byte, adding an extra block if the last block is complete.
//
// xx rr rr rr rr rr rr 07
case PaddingMode.ISO10126:
- result = new byte[count + padBytes];
+ int isoSize = count + padBytes;
- Buffer.BlockCopy(block, offset, result, 0, count);
- RandomNumberGenerator.Fill(result.AsSpan(count + 1, padBytes - 1));
- result[result.Length - 1] = (byte)padBytes;
+ if (destination.Length < isoSize)
+ {
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+ }
- break;
+ block.CopyTo(destination);
+ RandomNumberGenerator.Fill(destination.Slice(count, padBytes - 1));
+ destination[count + padBytes - 1] = (byte)padBytes;
+ return isoSize;
// PKCS padding fills the blocks up with bytes containing the total number of padding bytes
// used, adding an extra block if the last block is complete.
//
// xx xx 06 06 06 06 06 06
case PaddingMode.PKCS7:
- result = new byte[count + padBytes];
- Buffer.BlockCopy(block, offset, result, 0, count);
+ int pkcsSize = count + padBytes;
- for (int i = count; i < result.Length; i++)
+ if (destination.Length < pkcsSize)
{
- result[i] = (byte)padBytes;
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
- break;
+
+ block.CopyTo(destination);
+ destination.Slice(count, padBytes).Fill((byte)padBytes);
+ return pkcsSize;
// Zeros padding fills the last partial block with zeros, and does not add a new block to
// the end if the last block is already complete.
padBytes = 0;
}
- result = new byte[count + padBytes];
- Buffer.BlockCopy(block, offset, result, 0, count);
- break;
+ int zeroSize = count + padBytes;
+
+ if (destination.Length < zeroSize)
+ {
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+ }
+
+ destination.Slice(0, zeroSize).Clear();
+ block.CopyTo(destination);
+ return zeroSize;
default:
throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
}
-
- return result;
}
}
}
}
}
- protected abstract int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
+ protected int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ {
+ return UncheckedTransformBlock(inputBuffer.AsSpan(inputOffset, inputCount), outputBuffer.AsSpan(outputOffset));
+ }
+
+ protected abstract int UncheckedTransformBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer);
+
+ // For final block, encryption and decryption can give better context for the returning byte size, so we
+ // don't provide an implementation here.
protected abstract byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount);
+ protected abstract int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer);
protected PaddingMode PaddingMode { get; private set; }
protected BasicSymmetricCipher BasicSymmetricCipher { get; private set; }
internal static partial class BCrypt
{
// Note: input and output are allowed to be the same buffer. BCryptEncrypt will correctly do the encryption in place according to CNG documentation.
- internal static int BCryptEncrypt(SafeKeyHandle hKey, byte[] input, int inputOffset, int inputCount, byte[]? iv, byte[] output, int outputOffset, int outputCount)
+ internal static int BCryptEncrypt(SafeKeyHandle hKey, ReadOnlySpan<byte> input, byte[]? iv, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(inputCount >= 0);
- Debug.Assert(inputCount <= input.Length - inputOffset);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(outputCount >= 0);
- Debug.Assert(outputCount <= output.Length - outputOffset);
-
unsafe
{
fixed (byte* pbInput = input)
+ fixed (byte* pbOutput = output)
{
- fixed (byte* pbOutput = output)
+ int cbResult;
+ NTSTATUS ntStatus = BCryptEncrypt(hKey, pbInput, input.Length, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput, output.Length, out cbResult, 0);
+
+ if (ntStatus != NTSTATUS.STATUS_SUCCESS)
{
- int cbResult;
- NTSTATUS ntStatus = BCryptEncrypt(hKey, pbInput + inputOffset, inputCount, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput + outputOffset, outputCount, out cbResult, 0);
- if (ntStatus != NTSTATUS.STATUS_SUCCESS)
- throw CreateCryptographicException(ntStatus);
- return cbResult;
+ throw CreateCryptographicException(ntStatus);
}
+
+ return cbResult;
}
}
}
// Note: input and output are allowed to be the same buffer. BCryptDecrypt will correctly do the decryption in place according to CNG documentation.
- internal static int BCryptDecrypt(SafeKeyHandle hKey, byte[] input, int inputOffset, int inputCount, byte[]? iv, byte[] output, int outputOffset, int outputCount)
+ internal static int BCryptDecrypt(SafeKeyHandle hKey, ReadOnlySpan<byte> input, byte[]? iv, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(inputCount >= 0);
- Debug.Assert(inputCount <= input.Length - inputOffset);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(outputCount >= 0);
- Debug.Assert(outputCount <= output.Length - outputOffset);
-
unsafe
{
fixed (byte* pbInput = input)
+ fixed (byte* pbOutput = output)
{
- fixed (byte* pbOutput = output)
+ int cbResult;
+ NTSTATUS ntStatus = BCryptDecrypt(hKey, pbInput, input.Length, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput, output.Length, out cbResult, 0);
+
+ if (ntStatus != NTSTATUS.STATUS_SUCCESS)
{
- int cbResult;
- NTSTATUS ntStatus = BCryptDecrypt(hKey, pbInput + inputOffset, inputCount, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput + outputOffset, outputCount, out cbResult, 0);
- if (ntStatus != NTSTATUS.STATUS_SUCCESS)
- throw CreateCryptographicException(ntStatus);
- return cbResult;
+ throw CreateCryptographicException(ntStatus);
}
+
+ return cbResult;
}
}
}
base.Dispose(disposing);
}
- public override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null, "Expected valid input, got null");
- Debug.Assert(inputOffset >= 0, $"Expected non-negative inputOffset, got {inputOffset}");
- Debug.Assert(count > 0, $"Expected positive count, got {count}");
- Debug.Assert((count % BlockSizeInBytes) == 0, $"Expected count aligned to block size {BlockSizeInBytes}, got {count}");
- Debug.Assert(input.Length - inputOffset >= count, $"Expected valid input length/offset/count triplet, got {input.Length}/{inputOffset}/{count}");
- Debug.Assert(output != null, "Expected valid output, got null");
- Debug.Assert(outputOffset >= 0, $"Expected non-negative outputOffset, got {outputOffset}");
- Debug.Assert(output.Length - outputOffset >= count, $"Expected valid output length/offset/count triplet, got {output.Length}/{outputOffset}/{count}");
-
- return CipherUpdate(input, inputOffset, count, output, outputOffset);
+ Debug.Assert(input.Length > 0);
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+ return CipherUpdate(input, output);
}
- public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+ public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null, "Expected valid input, got null");
- Debug.Assert(inputOffset >= 0, $"Expected non-negative inputOffset, got {inputOffset}");
- Debug.Assert(count >= 0, $"Expected non-negative count, got {count}");
- Debug.Assert((count % BlockSizeInBytes) == 0, $"Expected count aligned to block size {BlockSizeInBytes}, got {count}");
- Debug.Assert(input.Length - inputOffset >= count, $"Expected valid input length/offset/count triplet, got {input.Length}/{inputOffset}/{count}");
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+ Debug.Assert(input.Length <= output.Length);
- byte[] output = ProcessFinalBlock(input, inputOffset, count);
+ int written = ProcessFinalBlock(input, output);
Reset();
- return output;
+ return written;
}
- private unsafe byte[] ProcessFinalBlock(byte[] input, int inputOffset, int count)
+ private unsafe int ProcessFinalBlock(ReadOnlySpan<byte> input, Span<byte> output)
{
- if (count == 0)
+ if (input.Length == 0)
{
- return Array.Empty<byte>();
+ return 0;
}
- byte[] output = new byte[count];
- int outputBytes = CipherUpdate(input, inputOffset, count, output, 0);
+ int outputBytes = CipherUpdate(input, output);
int ret;
int errorCode;
- fixed (byte* outputStart = &output[0])
+ Debug.Assert(output.Length > 0);
+
+ fixed (byte* outputStart = output)
{
byte* outputCurrent = outputStart + outputBytes;
int bytesWritten;
ProcessInteropError(ret, errorCode);
- if (outputBytes == output.Length)
- {
- return output;
- }
-
- if (outputBytes == 0)
- {
- return Array.Empty<byte>();
- }
-
- byte[] userData = new byte[outputBytes];
- Buffer.BlockCopy(output, 0, userData, 0, outputBytes);
- return userData;
+ return outputBytes;
}
- private unsafe int CipherUpdate(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ private unsafe int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output)
{
int ret;
int ccStatus;
int bytesWritten;
- if (count == 0)
+ if (input.Length == 0)
{
return 0;
}
- fixed (byte* inputStart = input)
- fixed (byte* outputStart = output)
+ fixed (byte* pInput = input)
+ fixed (byte* pOutput = output)
{
- byte* inputCurrent = inputStart + inputOffset;
- byte* outputCurrent = outputStart + outputOffset;
-
ret = Interop.AppleCrypto.CryptorUpdate(
_cryptor,
- inputCurrent,
- count,
- outputCurrent,
- output.Length - outputOffset,
+ pInput,
+ input.Length,
+ pOutput,
+ output.Length,
out bytesWritten,
out ccStatus);
}
base.Dispose(disposing);
}
- public override unsafe int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ public override unsafe int Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count > 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(output.Length - outputOffset >= count);
+ Debug.Assert(input.Length > 0);
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
// OpenSSL 1.1 does not allow partial overlap.
- if (input == output && inputOffset != outputOffset)
+ if (input.Overlaps(output, out int overlapOffset) && overlapOffset != 0)
{
- byte[] tmp = CryptoPool.Rent(count);
+ byte[] tmp = CryptoPool.Rent(input.Length);
+ Span<byte> tmpSpan = tmp;
int written = 0;
try
{
- written = CipherUpdate(input, inputOffset, count, tmp, 0);
- Buffer.BlockCopy(tmp, 0, output, outputOffset, written);
+ written = CipherUpdate(input, tmpSpan);
+ tmpSpan.Slice(0, written).CopyTo(output);
return written;
}
finally
}
}
- return CipherUpdate(input, inputOffset, count, output, outputOffset);
+ return CipherUpdate(input, output);
}
- public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+ public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+ Debug.Assert(input.Length <= output.Length);
- byte[] output = ProcessFinalBlock(input, inputOffset, count);
+ int written = ProcessFinalBlock(input, output);
Reset();
- return output;
+ return written;
}
- private byte[] ProcessFinalBlock(byte[] input, int inputOffset, int count)
+ private int ProcessFinalBlock(ReadOnlySpan<byte> input, Span<byte> output)
{
- byte[] output = new byte[count];
- int outputBytes = CipherUpdate(input, inputOffset, count, output, 0);
-
- Span<byte> outputSpan = output.AsSpan(outputBytes);
- int bytesWritten;
- CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out bytesWritten));
- outputBytes += bytesWritten;
-
- if (outputBytes == output.Length)
+ // If input and output overlap but are not the same, we need to use a
+ // temp buffer since openssl doesn't seem to like partial overlaps.
+ if (input.Overlaps(output, out int offset) && offset != 0)
{
- return output;
- }
+ byte[] rented = CryptoPool.Rent(input.Length);
+ int written = 0;
- if (outputBytes == 0)
+ try
+ {
+ written = CipherUpdate(input, rented);
+ Span<byte> outputSpan = rented.AsSpan(written);
+ CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
+ written += finalWritten;
+ rented.AsSpan(0, written).CopyTo(output);
+ return written;
+ }
+ finally
+ {
+ CryptoPool.Return(rented, clearSize: written);
+ }
+ }
+ else
{
- return Array.Empty<byte>();
+ int written = CipherUpdate(input, output);
+ Span<byte> outputSpan = output.Slice(written);
+ CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
+ written += finalWritten;
+ return written;
}
-
- byte[] userData = new byte[outputBytes];
- Buffer.BlockCopy(output, 0, userData, 0, outputBytes);
- return userData;
}
- private int CipherUpdate(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output)
{
- int bytesWritten;
-
- ReadOnlySpan<byte> inputSpan = input.AsSpan(inputOffset, count);
- Span<byte> outputSpan = output.AsSpan(outputOffset);
-
Interop.Crypto.EvpCipherUpdate(
_ctx,
- outputSpan,
- out bytesWritten,
- inputSpan);
+ output,
+ out int bytesWritten,
+ input);
return bytesWritten;
}
Reset();
}
- public sealed override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ public sealed override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count > 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(output.Length - outputOffset >= count);
+ Debug.Assert(input.Length > 0);
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
int numBytesWritten;
ErrorCode errorCode;
using (SafeNCryptKeyHandle keyHandle = _cngKey!.Handle)
{
- var inputSpan = new ReadOnlySpan<byte>(input, inputOffset, count);
- var outputSpan = new Span<byte>(output, outputOffset, count);
unsafe
{
errorCode = _encrypting ?
- Interop.NCrypt.NCryptEncrypt(keyHandle, inputSpan, inputSpan.Length, null, outputSpan, outputSpan.Length, out numBytesWritten, AsymmetricPaddingMode.None) :
- Interop.NCrypt.NCryptDecrypt(keyHandle, inputSpan, inputSpan.Length, null, outputSpan, outputSpan.Length, out numBytesWritten, AsymmetricPaddingMode.None);
+ Interop.NCrypt.NCryptEncrypt(keyHandle, input, input.Length, null, output, output.Length, out numBytesWritten, AsymmetricPaddingMode.None) :
+ Interop.NCrypt.NCryptDecrypt(keyHandle, input, input.Length, null, output, output.Length, out numBytesWritten, AsymmetricPaddingMode.None);
}
}
if (errorCode != ErrorCode.ERROR_SUCCESS)
throw errorCode.ToCryptographicException();
}
- if (numBytesWritten != count)
+ if (numBytesWritten != input.Length)
{
// CNG gives us no way to tell NCryptDecrypt() that we're decrypting the final block, nor is it performing any padding/depadding for us.
// So there's no excuse for a provider to hold back output for "future calls." Though this isn't technically our problem to detect, we might as well
return numBytesWritten;
}
- public sealed override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+ public sealed override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
-
- byte[] output = new byte[count];
- if (count != 0)
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+ int numBytesWritten = 0;
+
+ if (input.Length != 0)
{
- int numBytesWritten = Transform(input, inputOffset, count, output, 0);
- Debug.Assert(numBytesWritten == count); // Our implementation of Transform() guarantees this. See comment above.
+ numBytesWritten = Transform(input, output);
+ Debug.Assert(numBytesWritten == input.Length); // Our implementation of Transform() guarantees this. See comment above.
}
Reset();
- return output;
+ return numBytesWritten;
}
protected sealed override void Dispose(bool disposing)
<data name="Cryptography_InvalidECCharacteristic2Curve" xml:space="preserve">
<value>The specified Characteristic2 curve parameters are not valid. Polynomial, A, B, G.X, G.Y, and Order are required. A, B, G.X, G.Y must be the same length, and the same length as Q.X, Q.Y and D if those are specified. Seed, Cofactor and Hash are optional. Other parameters are not allowed.</value>
</data>
+ <data name="Argument_DestinationTooShort" xml:space="preserve">
+ <value>Destination is too short.</value>
+ </data>
</root>
base.Dispose(disposing);
}
- public override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+ public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
- return Transform(input, inputOffset, count, output, outputOffset, false);
+ return Transform(input, output, false);
}
- public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+ public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
-
- byte[] output = new byte[count];
- if (count != 0)
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+ int numBytesWritten = 0;
+
+ if (input.Length != 0)
{
- int numBytesWritten = Transform(input, inputOffset, count, output, 0, true);
- Debug.Assert(numBytesWritten == count); // Our implementation of Transform() guarantees this.
+ numBytesWritten = Transform(input, output, true);
+ Debug.Assert(numBytesWritten == input.Length); // Our implementation of Transform() guarantees this.
}
Reset();
- return output;
+ return numBytesWritten;
}
private void Reset()
{
// Ensure we've called CryptEncrypt with the final=true flag so the handle is reset property
- EncryptData(_hKey, Array.Empty<byte>(), 0, 0, Array.Empty<byte>(), 0, 0, true);
+ EncryptData(_hKey, default, default, true);
}
- private int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset, bool isFinal)
+ private int Transform(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
{
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(count > 0);
- Debug.Assert((count % BlockSizeInBytes) == 0);
- Debug.Assert(input.Length - inputOffset >= count);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(output.Length - outputOffset >= count);
+ Debug.Assert(input.Length > 0);
+ Debug.Assert((input.Length % BlockSizeInBytes) == 0);
int numBytesWritten;
if (_encrypting)
{
- numBytesWritten = EncryptData(_hKey, input, inputOffset, count, output, outputOffset, output.Length - outputOffset, isFinal);
+ numBytesWritten = EncryptData(_hKey, input, output, isFinal);
}
else
{
- numBytesWritten = DecryptData(_hKey, input, inputOffset, count, output, outputOffset, output.Length - outputOffset);
+ numBytesWritten = DecryptData(_hKey, input, output);
}
return numBytesWritten;
<root>
- <!--
- Microsoft ResX Schema
-
+ <!--
+ Microsoft ResX Schema
+
Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
associated with the data types.
-
+
Example:
-
+
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
-
- There are any number of "resheader" rows that contain simple
+
+ There are any number of "resheader" rows that contain simple
name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
-
+
mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
+ value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
-
+
mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
+ value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
+ value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<data name="Cryptography_PlatformNotSupported" xml:space="preserve">
<value>System.Security.Cryptography is not supported on this platform.</value>
</data>
+ <data name="Argument_DestinationTooShort" xml:space="preserve">
+ <value>Destination is too short.</value>
+ </data>
</root>
Link="Internal\Cryptography\Helpers.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs"
Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\CryptoPool.cs"
+ Link="Common\System\Security\Cryptography\CryptoPool.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<Compile Include="System\Security\Cryptography\CapiHelper.Unix.cs" />
Debug.Assert(encryptedData != null, "Encrypted Data is null");
Debug.Assert(encryptedDataLength >= 0, "Encrypted data length is less than 0");
- byte[] dataTobeDecrypted = new byte[encryptedDataLength];
- Buffer.BlockCopy(encryptedData, 0, dataTobeDecrypted, 0, encryptedDataLength);
- Array.Reverse(dataTobeDecrypted);
+ byte[] dataToBeDecrypted = new byte[encryptedDataLength];
+ Buffer.BlockCopy(encryptedData, 0, dataToBeDecrypted, 0, encryptedDataLength);
+ Array.Reverse(dataToBeDecrypted);
int dwFlags = fOAEP ? (int)Interop.Advapi32.CryptDecryptFlags.CRYPT_OAEP : 0;
int decryptedDataLength = encryptedDataLength;
- if (!Interop.Advapi32.CryptDecrypt(safeKeyHandle, SafeHashHandle.InvalidHandle, true, dwFlags, dataTobeDecrypted, ref decryptedDataLength))
+ if (!Interop.Advapi32.CryptDecrypt(safeKeyHandle, SafeHashHandle.InvalidHandle, true, dwFlags, dataToBeDecrypted, ref decryptedDataLength))
{
int ErrCode = GetErrorCode();
// If we're using OAEP mode and we received an NTE_BAD_FLAGS error, then OAEP is not supported on
decryptedData = new byte[decryptedDataLength];
- Buffer.BlockCopy(dataTobeDecrypted, 0, decryptedData, 0, decryptedDataLength);
+ Buffer.BlockCopy(dataToBeDecrypted, 0, decryptedData, 0, decryptedDataLength);
return;
}
internal static int EncryptData(
SafeKeyHandle hKey,
- byte[] input,
- int inputOffset,
- int inputCount,
- byte[] output,
- int outputOffset,
- int outputCount,
+ ReadOnlySpan<byte> input,
+ Span<byte> output,
bool isFinal)
{
VerifyValidHandle(hKey);
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(inputCount >= 0);
- Debug.Assert(inputCount <= input.Length - inputOffset);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(outputCount >= 0);
- Debug.Assert(outputCount <= output.Length - outputOffset);
- Debug.Assert((inputCount % 8) == 0);
+ Debug.Assert((input.Length % 8) == 0);
// Figure out how big the encrypted data will be
- int cbEncryptedData = inputCount;
+ int cbEncryptedData = input.Length;
if (!Interop.Advapi32.CryptEncrypt(hKey, SafeHashHandle.InvalidHandle, isFinal, 0, null, ref cbEncryptedData, cbEncryptedData))
{
throw GetErrorCode().ToCryptographicException();
// encryptedData is an in/out buffer for CryptEncrypt. Allocate space for the encrypted data, and copy the
// plaintext data into that space. Since encrypted data will have padding applied, the size of the encrypted
// data should always be larger than the plaintext key, so use that to determine the buffer size.
- Debug.Assert(cbEncryptedData >= inputCount);
+ Debug.Assert(cbEncryptedData >= input.Length);
var encryptedData = new byte[cbEncryptedData];
- Buffer.BlockCopy(input, inputOffset, encryptedData, 0, inputCount);
+ input.CopyTo(encryptedData);
// Encrypt for real - the last parameter is the total size of the in/out buffer, while the second to last
// parameter specifies the size of the plaintext to encrypt.
- int encryptedDataLength = inputCount;
+ int encryptedDataLength = input.Length;
if (!Interop.Advapi32.CryptEncrypt(hKey, SafeHashHandle.InvalidHandle, isFinal, 0, encryptedData, ref encryptedDataLength, cbEncryptedData))
{
throw GetErrorCode().ToCryptographicException();
}
- Debug.Assert(encryptedDataLength == cbEncryptedData);
-
- if (isFinal)
- {
- Debug.Assert(outputCount == inputCount);
- }
- else
- {
- Debug.Assert(outputCount >= encryptedDataLength);
- outputCount = encryptedDataLength;
- }
- // If isFinal, padding was added so ignore it by using outputCount as size
- Buffer.BlockCopy(encryptedData, 0, output, outputOffset, outputCount);
+ // If isFinal, padding was added so ignore it by using original input length as size
+ int outputCount = isFinal ? input.Length : encryptedDataLength;
+ Debug.Assert(encryptedDataLength == cbEncryptedData);
+ Debug.Assert(outputCount <= output.Length);
+ encryptedData.AsSpan(0, outputCount).CopyTo(output);
return outputCount;
}
internal static int DecryptData(
SafeKeyHandle hKey,
- byte[] input,
- int inputOffset,
- int inputCount,
- byte[] output,
- int outputOffset,
- int outputCount)
+ ReadOnlySpan<byte> input,
+ Span<byte> output)
{
VerifyValidHandle(hKey);
- Debug.Assert(input != null);
- Debug.Assert(inputOffset >= 0);
- Debug.Assert(inputCount >= 0);
- Debug.Assert(inputCount <= input.Length - inputOffset);
- Debug.Assert(output != null);
- Debug.Assert(outputOffset >= 0);
- Debug.Assert(outputCount >= 0);
- Debug.Assert(outputCount <= output.Length - outputOffset);
- Debug.Assert((inputCount % 8) == 0);
-
- byte[] dataTobeDecrypted = new byte[inputCount];
- Buffer.BlockCopy(input, inputOffset, dataTobeDecrypted, 0, inputCount);
-
- int decryptedDataLength = inputCount;
+ Debug.Assert((input.Length % 8) == 0);
+
+ byte[] dataToBeDecrypted = new byte[input.Length];
+ input.CopyTo(dataToBeDecrypted);
+
+ int decryptedDataLength = input.Length;
+
// Always call decryption with false (not final); deal with padding manually
- if (!Interop.Advapi32.CryptDecrypt(hKey, SafeHashHandle.InvalidHandle, false, 0, dataTobeDecrypted, ref decryptedDataLength))
+ if (!Interop.Advapi32.CryptDecrypt(hKey, SafeHashHandle.InvalidHandle, false, 0, dataToBeDecrypted, ref decryptedDataLength))
{
throw GetErrorCode().ToCryptographicException();
}
- Buffer.BlockCopy(dataTobeDecrypted, 0, output, outputOffset, decryptedDataLength);
-
+ dataToBeDecrypted.AsSpan(0, decryptedDataLength).CopyTo(output);
return decryptedDataLength;
}