Skip to content

Commit

Permalink
Add one-shot ECB methods
Browse files Browse the repository at this point in the history
This change adds SymmetricAlgorithm.EncryptEcb, SymmetricAlgorithm.DecryptEcb, their
respective Try- and -Core methods, derived type implementations thereof, and tests.

There's an open question of should these members on on the base class throw or
"succeed if the Mode property is in agreement with the algorithm".  While the latter is "nicer",
just throwing  is easier to reason about, and that's the current behavior.

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
  • Loading branch information
vcsjones and bartonjs committed Jun 28, 2021
1 parent daeee34 commit 6b5dbf6
Show file tree
Hide file tree
Showing 48 changed files with 3,580 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,27 @@ public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
Debug.Assert(input.Length > 0);
Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

int numBytesWritten;
int numBytesWritten = 0;

if (_encrypting)
// BCryptEncrypt and BCryptDecrypt can do in place encryption, but if the buffers overlap
// the offset must be zero. In that case, we need to copy to a temporary location.
if (input.Overlaps(output, out int offset) && offset != 0)
{
numBytesWritten = Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output);
byte[] rented = CryptoPool.Rent(output.Length);

try
{
numBytesWritten = BCryptTransform(input, rented);
rented.AsSpan(0, numBytesWritten).CopyTo(output);
}
finally
{
CryptoPool.Return(rented, clearSize: numBytesWritten);
}
}
else
{
numBytesWritten = Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
numBytesWritten = BCryptTransform(input, output);
}

if (numBytesWritten != input.Length)
Expand All @@ -84,6 +96,13 @@ public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
}

return numBytesWritten;

int BCryptTransform(ReadOnlySpan<byte> input, Span<byte> output)
{
return _encrypting ?
Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output) :
Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
}
}

public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/Common/src/Internal/Cryptography/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ internal static partial class Helpers
return (byte[])(src.Clone());
}

public static int GetPaddingSize(this SymmetricAlgorithm algorithm)
public static int GetPaddingSize(this SymmetricAlgorithm algorithm, CipherMode mode, int feedbackSizeBits)
{
// CFB8 does not require any padding at all
// otherwise, it is always required to pad for block size
if (algorithm.Mode == CipherMode.CFB && algorithm.FeedbackSize == 8)
if (mode == CipherMode.CFB && feedbackSizeBits == 8)
return 1;

return algorithm.BlockSize / 8;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,62 @@ protected sealed override void Dispose(bool disposing)
base.Dispose(disposing);
}

public override unsafe bool TransformOneShot(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten)
{
if (input.Length % PaddingSizeBytes != 0)
throw new CryptographicException(SR.Cryptography_PartialBlock);

// If there is no padding that needs to be removed, and the output buffer is large enough to hold
// the resulting plaintext, we can decrypt directly in to the output buffer.
// We do not do this for modes that require padding removal.
//
// This is not done for padded ciphertexts because we don't know if the padding is valid
// until it's been decrypted. We don't want to decrypt in to a user-supplied buffer and then throw
// a padding exception after we've already filled the user buffer with plaintext. We should only
// release the plaintext to the caller once we know the padding is valid.
if (!DepaddingRequired)
{
if (output.Length >= input.Length)
{
bytesWritten = BasicSymmetricCipher.TransformFinal(input, output);
return true;
}

// If no padding is going to be removed, we know the buffer is too small and we can bail out.
bytesWritten = 0;
return false;
}

byte[] rentedBuffer = CryptoPool.Rent(input.Length);
Span<byte> buffer = rentedBuffer.AsSpan(0, input.Length);
Span<byte> decryptedBuffer = default;

fixed (byte* pBuffer = buffer)
{
try
{
int transformWritten = BasicSymmetricCipher.TransformFinal(input, buffer);
decryptedBuffer = buffer.Slice(0, transformWritten);
int unpaddedLength = GetPaddingLength(decryptedBuffer); // validates padding

if (unpaddedLength > output.Length)
{
bytesWritten = 0;
return false;
}

decryptedBuffer.Slice(0, unpaddedLength).CopyTo(output);
bytesWritten = unpaddedLength;
return true;
}
finally
{
CryptographicOperations.ZeroMemory(decryptedBuffer);
CryptoPool.Return(rentedBuffer, clearSize: 0); // ZeroMemory clears the part of the buffer that was written to.
}
}
}

private void Reset()
{
if (_heldoverCipher != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ protected override byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int i
return buffer;
}

public override bool TransformOneShot(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten)
{
int ciphertextLength = GetCiphertextLength(input.Length);

if (output.Length < ciphertextLength)
{
bytesWritten = 0;
return false;
}

// Copy the input to the output, and apply padding if required. This will not throw since the
// output length has already been checked, and PadBlock will not copy from input to output
// until it has checked that it will be able to apply padding correctly.
int padWritten = PadBlock(input, output);

// Do an in-place encrypt. All of our implementations support this, either natively
// or making a temporary buffer themselves if in-place is not supported by the native
// implementation.
Span<byte> paddedOutput = output.Slice(0, padWritten);
bytesWritten = BasicSymmetricCipher.TransformFinal(paddedOutput, paddedOutput);

// After padding, we should have an even number of blocks, and the same applies
// to the transform.
Debug.Assert(padWritten == bytesWritten);
return true;
}

private int GetCiphertextLength(int plaintextLength)
{
Debug.Assert(plaintextLength >= 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Internal.Cryptography
//
internal abstract class UniversalCryptoTransform : ICryptoTransform
{
public static ICryptoTransform Create(
public static UniversalCryptoTransform Create(
PaddingMode paddingMode,
BasicSymmetricCipher cipher,
bool encrypting)
Expand Down Expand Up @@ -108,6 +108,8 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input
return output;
}

public abstract bool TransformOneShot(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten);

protected virtual void Dispose(bool disposing)
{
if (disposing)
Expand Down
Loading

0 comments on commit 6b5dbf6

Please sign in to comment.