From: Günther Foidl Date: Wed, 17 Jul 2019 18:06:17 +0000 (+0200) Subject: Spanify Cryptography.Base64Transform X-Git-Tag: submit/tizen/20210909.063632~11031^2~900 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=15100107427c79021da9a4f973f5076f787ddc45;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Spanify Cryptography.Base64Transform So it can be non-allocating byte[] -> byte[], instead of allocating (temp. buffer) and copying with byte[] -> char[] -> byte[]. Commit migrated from https://github.com/dotnet/corefx/commit/d4a935c526f5f84d6136066bd554f619ed0b7476 --- diff --git a/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj b/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj index 8b6ffd9..922ad06 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj +++ b/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj @@ -1,4 +1,4 @@ - + {AA81E343-5E54-40B0-9381-C459419BE780} System.Security.Cryptography.Encoding @@ -23,6 +23,9 @@ Internal\Cryptography\Helpers.cs + + System\Security\Cryptography\CryptoPool.cs + @@ -98,9 +101,6 @@ Common\System\Memory\PointerMemoryManager.cs - - Common\System\Security\Cryptography\CryptoPool.cs - Common\System\Security\Cryptography\Asn1\DirectoryStringAsn.xml diff --git a/src/libraries/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Base64Transforms.cs b/src/libraries/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Base64Transforms.cs index 4b04bfe..5712a4e 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Base64Transforms.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Base64Transforms.cs @@ -5,7 +5,10 @@ // This file contains two ICryptoTransforms: ToBase64Transform and FromBase64Transform // they may be attached to a CryptoStream in either read or write mode -using System.Text; +using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Security.Cryptography { @@ -25,18 +28,33 @@ namespace System.Security.Cryptography public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { - ValidateTransformBlock(inputBuffer, inputOffset, inputCount); + // inputCount < InputBlockSize is not allowed + ThrowHelper.ValidateTransformBlock(inputBuffer, inputOffset, inputCount, InputBlockSize); + + if (outputBuffer == null) + ThrowHelper.ThrowArgumentNull(ThrowHelper.ExceptionArgument.outputBuffer); // For now, only convert 3 bytes to 4 - byte[] tempBytes = ConvertToBase64(inputBuffer, inputOffset, 3); + Span input = inputBuffer.AsSpan(inputOffset, InputBlockSize); + Span output = outputBuffer.AsSpan(outputOffset, OutputBlockSize); + + OperationStatus status = Base64.EncodeToUtf8(input, output, out int consumed, out int written, isFinalBlock: false); + + if (written != OutputBlockSize) + { + ThrowHelper.ThrowCryptographicException(); + } + + Debug.Assert(status == OperationStatus.NeedMoreData); + Debug.Assert(consumed == InputBlockSize); - Buffer.BlockCopy(tempBytes, 0, outputBuffer, outputOffset, tempBytes.Length); - return tempBytes.Length; + return written; } public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) { - ValidateTransformBlock(inputBuffer, inputOffset, inputCount); + // inputCount <= InputBlockSize is allowed + ThrowHelper.ValidateTransformBlock(inputBuffer, inputOffset, inputCount); // Convert.ToBase64CharArray already does padding, so all we have to check is that // the inputCount wasn't 0 @@ -44,28 +62,26 @@ namespace System.Security.Cryptography { return Array.Empty(); } + else if (inputCount > InputBlockSize) + { + ThrowHelper.ThrowArgumentOutOfRange(ThrowHelper.ExceptionArgument.inputCount); + } // Again, for now only a block at a time - return ConvertToBase64(inputBuffer, inputOffset, inputCount); - } + Span input = inputBuffer.AsSpan(inputOffset, inputCount); + byte[] output = new byte[OutputBlockSize]; - private byte[] ConvertToBase64(byte[] inputBuffer, int inputOffset, int inputCount) - { - char[] temp = new char[4]; - Convert.ToBase64CharArray(inputBuffer, inputOffset, inputCount, temp, 0); - byte[] tempBytes = Encoding.ASCII.GetBytes(temp); - if (tempBytes.Length != 4) - throw new CryptographicException(SR.Cryptography_SSE_InvalidDataSize); + OperationStatus status = Base64.EncodeToUtf8(input, output, out int consumed, out int written, isFinalBlock: true); - return tempBytes; - } + if (written != OutputBlockSize) + { + ThrowHelper.ThrowCryptographicException(); + } - private static void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - if (inputBuffer == null) throw new ArgumentNullException(nameof(inputBuffer)); - if (inputOffset < 0) throw new ArgumentOutOfRangeException(nameof(inputOffset), SR.ArgumentOutOfRange_NeedNonNegNum); - if (inputCount < 0 || (inputCount > inputBuffer.Length)) throw new ArgumentException(SR.Argument_InvalidValue); - if ((inputBuffer.Length - inputCount) < inputOffset) throw new ArgumentException(SR.Argument_InvalidOffLen); + Debug.Assert(status == OperationStatus.Done); + Debug.Assert(consumed == inputCount); + + return output; } // Must implement IDisposable, but in this case there's nothing to do. @@ -96,16 +112,18 @@ namespace System.Security.Cryptography { private byte[] _inputBuffer = new byte[4]; private int _inputIndex; - private FromBase64TransformMode _whitespaces; + private readonly FromBase64TransformMode _whitespaces; public FromBase64Transform() : this(FromBase64TransformMode.IgnoreWhiteSpaces) { } public FromBase64Transform(FromBase64TransformMode whitespaces) { _whitespaces = whitespaces; - _inputIndex = 0; } // Converting from Base64 generates 3 bytes output from each 4 bytes input block + private const int Base64InputBlockSize = 4; + // A buffer with size 32 is stack allocated, to cover common cases and benefit from JIT's optimizations. + private const int StackAllocSize = 32; public int InputBlockSize => 1; public int OutputBlockSize => 3; public bool CanTransformMultipleBlocks => false; @@ -113,126 +131,210 @@ namespace System.Security.Cryptography public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { - ValidateTransformBlock(inputBuffer, inputOffset, inputCount); - if (_inputBuffer == null) throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + // inputCount != InputBlockSize is allowed + ThrowHelper.ValidateTransformBlock(inputBuffer, inputOffset, inputCount); + + if (_inputBuffer == null) + ThrowHelper.ThrowObjectDisposed(); + + if (outputBuffer == null) + ThrowHelper.ThrowArgumentNull(ThrowHelper.ExceptionArgument.outputBuffer); - int effectiveCount; - byte[] temp = GetTempBuffer(inputBuffer, inputOffset, inputCount, out effectiveCount); + // The common case is inputCount = InputBlockSize + byte[] tmpBufferArray = null; + Span tmpBuffer = stackalloc byte[StackAllocSize]; + if (inputCount > StackAllocSize) + { + tmpBuffer = tmpBufferArray = CryptoPool.Rent(inputCount); + } - if (effectiveCount + _inputIndex < 4) + tmpBuffer = GetTempBuffer(inputBuffer.AsSpan(inputOffset, inputCount), tmpBuffer); + int bytesToTransform = _inputIndex + tmpBuffer.Length; + + // Too little data to decode: save data to _inputBuffer, so it can be transformed later + if (bytesToTransform < Base64InputBlockSize) { - Buffer.BlockCopy(temp, 0, _inputBuffer, _inputIndex, effectiveCount); - _inputIndex += effectiveCount; + tmpBuffer.CopyTo(_inputBuffer.AsSpan(_inputIndex)); + + _inputIndex = bytesToTransform; + + ReturnToCryptoPool(tmpBufferArray, tmpBuffer.Length); + return 0; } - byte[] result = ConvertFromBase64(temp, effectiveCount); + ConvertFromBase64(tmpBuffer, outputBuffer.AsSpan(outputOffset), out _, out int written); - Buffer.BlockCopy(result, 0, outputBuffer, outputOffset, result.Length); + ReturnToCryptoPool(tmpBufferArray, tmpBuffer.Length); - return result.Length; + return written; } public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) { - ValidateTransformBlock(inputBuffer, inputOffset, inputCount); - if (_inputBuffer == null) throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + // inputCount != InputBlockSize is allowed + ThrowHelper.ValidateTransformBlock(inputBuffer, inputOffset, inputCount); - int effectiveCount; - byte[] temp = GetTempBuffer(inputBuffer, inputOffset, inputCount, out effectiveCount); + if (_inputBuffer == null) + { + ThrowHelper.ThrowObjectDisposed(); + } + + if (inputCount == 0) + { + return Array.Empty(); + } - if (effectiveCount + _inputIndex < 4) + // The common case is inputCount <= Base64InputBlockSize + byte[] tmpBufferArray = null; + Span tmpBuffer = stackalloc byte[StackAllocSize]; + if (inputCount > StackAllocSize) { + tmpBuffer = tmpBufferArray = CryptoPool.Rent(inputCount); + } + + tmpBuffer = GetTempBuffer(inputBuffer.AsSpan(inputOffset, inputCount), tmpBuffer); + int bytesToTransform = _inputIndex + tmpBuffer.Length; + + // Too little data to decode + if (bytesToTransform < Base64InputBlockSize) + { + // reinitialize the transform Reset(); + + ReturnToCryptoPool(tmpBufferArray, tmpBuffer.Length); + return Array.Empty(); } - byte[] result = ConvertFromBase64(temp, effectiveCount); + int outputSize = GetOutputSize(bytesToTransform, tmpBuffer); + byte[] output = new byte[outputSize]; + + ConvertFromBase64(tmpBuffer, output, out int consumed, out int written); + Debug.Assert(written == outputSize); + + ReturnToCryptoPool(tmpBufferArray, tmpBuffer.Length); // reinitialize the transform Reset(); - return result; + return output; } - private byte[] GetTempBuffer(byte[] inputBuffer, int inputOffset, int inputCount, out int effectiveCount) + private Span GetTempBuffer(Span inputBuffer, Span tmpBuffer) { - byte[] temp; - - if (_whitespaces == FromBase64TransformMode.IgnoreWhiteSpaces) + if (_whitespaces == FromBase64TransformMode.DoNotIgnoreWhiteSpaces) { - temp = DiscardWhiteSpaces(inputBuffer, inputOffset, inputCount); - effectiveCount = temp.Length; + return inputBuffer; } - else + + return DiscardWhiteSpaces(inputBuffer, tmpBuffer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Span DiscardWhiteSpaces(Span inputBuffer, Span tmpBuffer) + { + int count = 0; + + for (int i = 0; i < inputBuffer.Length; i++) { - temp = new byte[inputCount]; - Buffer.BlockCopy(inputBuffer, inputOffset, temp, 0, inputCount); - effectiveCount = inputCount; + if (!IsWhitespace(inputBuffer[i])) + { + tmpBuffer[count++] = inputBuffer[i]; + } } - return temp; + return tmpBuffer.Slice(0, count); } - private byte[] ConvertFromBase64(byte[] temp, int effectiveCount) + private static bool IsWhitespace(byte value) { - // Get the number of 4 bytes blocks to transform - int numBlocks = (effectiveCount + _inputIndex) / 4; - - byte[] transformBuffer = new byte[_inputIndex + effectiveCount]; - Buffer.BlockCopy(_inputBuffer, 0, transformBuffer, 0, _inputIndex); - Buffer.BlockCopy(temp, 0, transformBuffer, _inputIndex, effectiveCount); + // We assume ASCII encoded data. If there is any non-ASCII char, it is invalid + // Base64 and will be caught during decoding. - _inputIndex = (effectiveCount + _inputIndex) % 4; - Buffer.BlockCopy(temp, effectiveCount - _inputIndex, _inputBuffer, 0, _inputIndex); + // SPACE 32 + // TAB 9 + // LF 10 + // VTAB 11 + // FORM FEED 12 + // CR 13 - char[] tempChar = Encoding.ASCII.GetChars(transformBuffer, 0, 4 * numBlocks); - byte[] tempBytes = Convert.FromBase64CharArray(tempChar, 0, 4 * numBlocks); - return tempBytes; + return value == 32 || ((uint)value - 9 <= (13 - 9)); } - private byte[] DiscardWhiteSpaces(byte[] inputBuffer, int inputOffset, int inputCount) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetOutputSize(int bytesToTransform, Span tmpBuffer) { - int i, iCount = 0; - for (i = 0; i < inputCount; i++) + int outputSize = Base64.GetMaxDecodedFromUtf8Length(bytesToTransform); + + const byte padding = (byte)'='; + int len = tmpBuffer.Length; + + // In Base64 there are maximum 2 padding chars + + if (tmpBuffer[len - 2] == padding) { - if (char.IsWhiteSpace((char)inputBuffer[inputOffset + i])) iCount++; + outputSize--; } - // If there's nothing to do, leave early - if (iCount == 0 && inputOffset == 0 && - inputCount == inputBuffer.Length) + if (tmpBuffer[len - 1] == padding) { - return inputBuffer; + outputSize--; } - byte[] rgbOut = new byte[inputCount - iCount]; - iCount = 0; - for (i = 0; i < inputCount; i++) + return outputSize; + } + + private void ConvertFromBase64(Span tmpBuffer, Span outputBuffer, out int consumed, out int written) + { + int bytesToTransform = _inputIndex + tmpBuffer.Length; + Debug.Assert(bytesToTransform >= 4); + + byte[] transformBufferArray = null; + Span transformBuffer = stackalloc byte[StackAllocSize]; + if (bytesToTransform > StackAllocSize) { - if (!char.IsWhiteSpace((char)inputBuffer[inputOffset + i])) - { - rgbOut[iCount++] = inputBuffer[inputOffset + i]; - } + transformBuffer = transformBufferArray = CryptoPool.Rent(bytesToTransform); + } + + // Copy _inputBuffer to transformBuffer and append tmpBuffer + Debug.Assert(_inputIndex < _inputBuffer.Length); + _inputBuffer.AsSpan(0, _inputIndex).CopyTo(transformBuffer); + tmpBuffer.CopyTo(transformBuffer.Slice(_inputIndex)); + + // Save data that won't be transformed to _inputBuffer, so it can be transformed later + _inputIndex = bytesToTransform & 3; // bit hack for % 4 + bytesToTransform -= _inputIndex; // only transform up to the next multiple of 4 + Debug.Assert(_inputIndex < _inputBuffer.Length); + tmpBuffer.Slice(tmpBuffer.Length - _inputIndex).CopyTo(_inputBuffer); + + transformBuffer = transformBuffer.Slice(0, bytesToTransform); + OperationStatus status = Base64.DecodeFromUtf8(transformBuffer, outputBuffer, out consumed, out written); + + if (status == OperationStatus.Done) + { + Debug.Assert(consumed == bytesToTransform); + } + else + { + Debug.Assert(status == OperationStatus.InvalidData); + ThrowHelper.ThrowBase64FormatException(); } - return rgbOut; + ReturnToCryptoPool(transformBufferArray, transformBuffer.Length); } - private static void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount) + private void ReturnToCryptoPool(byte[] array, int clearSize) { - if (inputBuffer == null) throw new ArgumentNullException(nameof(inputBuffer)); - if (inputOffset < 0) throw new ArgumentOutOfRangeException(nameof(inputOffset), SR.ArgumentOutOfRange_NeedNonNegNum); - if (inputCount < 0 || (inputCount > inputBuffer.Length)) throw new ArgumentException(SR.Argument_InvalidValue); - if ((inputBuffer.Length - inputCount) < inputOffset) throw new ArgumentException(SR.Argument_InvalidOffLen); + if (array != null) + { + CryptoPool.Return(array, clearSize); + } } - // must implement IDisposable, which in this case means clearing the input buffer - - public void Dispose() + public void Clear() { - Dispose(true); - GC.SuppressFinalize(this); + Dispose(); } // Reset the state of the transform so it can be used again @@ -241,9 +343,12 @@ namespace System.Security.Cryptography _inputIndex = 0; } - public void Clear() + // must implement IDisposable, which in this case means clearing the input buffer + + public void Dispose() { - Dispose(); + Dispose(true); + GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) @@ -252,9 +357,12 @@ namespace System.Security.Cryptography if (disposing) { if (_inputBuffer != null) - Array.Clear(_inputBuffer, 0, _inputBuffer.Length); - _inputBuffer = null; - _inputIndex = 0; + { + CryptographicOperations.ZeroMemory(_inputBuffer); + _inputBuffer = null; + } + + Reset(); } } @@ -263,4 +371,46 @@ namespace System.Security.Cryptography Dispose(false); } } + + internal class ThrowHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + if (inputBuffer == null) + ThrowArgumentNull(ExceptionArgument.inputBuffer); + + if ((uint)inputCount > inputBuffer.Length) + ThrowArgumentOutOfRange(ExceptionArgument.inputCount); + + if (inputOffset < 0) + ThrowArgumentOutOfRange(ExceptionArgument.inputOffset); + + if ((inputBuffer.Length - inputCount) < inputOffset) + ThrowInvalidOffLen(); + } + + public static void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, int inputBlockSize) + { + ValidateTransformBlock(inputBuffer, inputOffset, inputCount); + + if (inputCount < inputBlockSize) + ThrowArgumentOutOfRange(ExceptionArgument.inputCount); + } + + public static void ThrowArgumentNull(ExceptionArgument argument) => throw new ArgumentNullException(argument.ToString()); + public static void ThrowArgumentOutOfRange(ExceptionArgument argument) => throw new ArgumentOutOfRangeException(argument.ToString(), SR.ArgumentOutOfRange_NeedNonNegNum); + public static void ThrowInvalidOffLen() => throw new ArgumentException(SR.Argument_InvalidOffLen); + public static void ThrowObjectDisposed() => throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + public static void ThrowCryptographicException() => throw new CryptographicException(SR.Cryptography_SSE_InvalidDataSize); + public static void ThrowBase64FormatException() => throw new FormatException(); + + public enum ExceptionArgument + { + inputBuffer, + outputBuffer, + inputOffset, + inputCount + } + } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Base64TransformsTests.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Base64TransformsTests.cs index 0e8d6d1..d23a9ef 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Base64TransformsTests.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Base64TransformsTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; -using Test.Cryptography; using Xunit; namespace System.Security.Cryptography.Encoding.Tests @@ -39,7 +38,9 @@ namespace System.Security.Cryptography.Encoding.Tests public static IEnumerable TestData_Ascii_Whitespace() { + yield return new object[] { "fo", "\tZ\tm8=\r" }; yield return new object[] { "fo", "\tZ\tm8=\n" }; + yield return new object[] { "fo", "\tZ\tm8=\r\n" }; yield return new object[] { "foo", " Z m 9 v" }; } @@ -49,6 +50,7 @@ namespace System.Security.Cryptography.Encoding.Tests yield return new object[] { "Zm9v////", 0, 4, "foo" }; yield return new object[] { "////Zm9v", 4, 4, "foo" }; yield return new object[] { "////Zm9v////", 4, 4, "foo" }; + yield return new object[] { "Zm9vYmFyYm", 0, 10, "foobar" }; } [Fact] @@ -61,7 +63,7 @@ namespace System.Security.Cryptography.Encoding.Tests InvalidInput_Base64Transform(transform); // These exceptions only thrown in ToBase - AssertExtensions.Throws("offsetOut", () => transform.TransformFinalBlock(data_5bytes, 0, 5)); + AssertExtensions.Throws("inputCount", () => transform.TransformFinalBlock(data_5bytes, 0, 5)); } } @@ -85,8 +87,8 @@ namespace System.Security.Cryptography.Encoding.Tests AssertExtensions.Throws("inputBuffer", () => transform.TransformBlock(null, 0, 0, null, 0)); AssertExtensions.Throws("inputOffset", () => transform.TransformBlock(Array.Empty(), -1, 0, null, 0)); - AssertExtensions.Throws("dst", () => transform.TransformBlock(data_4bytes, 0, 4, null, 0)); - AssertExtensions.Throws(null, () => transform.TransformBlock(Array.Empty(), 0, 1, null, 0)); + AssertExtensions.Throws("outputBuffer", () => transform.TransformBlock(data_4bytes, 0, 4, null, 0)); + AssertExtensions.Throws("inputCount", () => transform.TransformBlock(Array.Empty(), 0, 1, null, 0)); AssertExtensions.Throws(null, () => transform.TransformBlock(Array.Empty(), 1, 0, null, 0)); AssertExtensions.Throws("inputBuffer", () => transform.TransformFinalBlock(null, 0, 0)); @@ -147,16 +149,16 @@ namespace System.Security.Cryptography.Encoding.Tests Assert.True(inputBytes.Length > 4); // Test passing blocks > 4 characters to TransformFinalBlock (not supported) - AssertExtensions.Throws("offsetOut", () => transform.TransformFinalBlock(inputBytes, 0, inputBytes.Length)); + AssertExtensions.Throws("inputCount", () => transform.TransformFinalBlock(inputBytes, 0, inputBytes.Length)); } } [Theory, MemberData(nameof(TestData_LongBlock_Ascii))] - public static void ValidateFromBase64TransformFinalBlock(string expected, string encoding) + public static void ValidateFromBase64TransformFinalBlock(string expected, string encoded) { using (var transform = new FromBase64Transform()) { - byte[] inputBytes = Text.Encoding.ASCII.GetBytes(encoding); + byte[] inputBytes = Text.Encoding.ASCII.GetBytes(encoded); Assert.True(inputBytes.Length > 4); // Test passing blocks > 4 characters to TransformFinalBlock (supported) @@ -166,6 +168,22 @@ namespace System.Security.Cryptography.Encoding.Tests } } + [Theory, MemberData(nameof(TestData_LongBlock_Ascii))] + public static void ValidateFromBase64TransformBlock(string expected, string encoded) + { + using (var transform = new FromBase64Transform()) + { + byte[] inputBytes = Text.Encoding.ASCII.GetBytes(encoded); + Assert.True(inputBytes.Length > 4); + + byte[] outputBytes = new byte[100]; + int bytesWritten = transform.TransformBlock(inputBytes, 0, inputBytes.Length, outputBytes, 0); + string outputText = Text.Encoding.ASCII.GetString(outputBytes, 0, bytesWritten); + + Assert.Equal(expected, outputText); + } + } + [Theory, MemberData(nameof(TestData_Ascii_NoPadding))] public static void ValidateFromBase64_NoPadding(string data) { @@ -208,7 +226,7 @@ namespace System.Security.Cryptography.Encoding.Tests byte[] outputBytes = new byte[100]; // Verify default of FromBase64TransformMode.IgnoreWhiteSpaces - using (var base64Transform = new FromBase64Transform()) + using (var base64Transform = new FromBase64Transform()) using (var ms = new MemoryStream(inputBytes)) using (var cs = new CryptoStream(ms, base64Transform, CryptoStreamMode.Read)) {