private bool VerifyAuthenticationTag(ReadOnlySpan <byte> key, ReadOnlySpan <byte> iv, ReadOnlySpan <byte> associatedData, ReadOnlySpan <byte> ciphertext, ReadOnlySpan <byte> authenticationTag) { int tagSize = GetTagSize(); if (authenticationTag.Length != tagSize) { return(false); } byte[]? byteArrayToReturnToPool = null; int macLength = associatedData.Length + iv.Length + ciphertext.Length + sizeof(long); Span <byte> macBytes = macLength > Constants.MaxStackallocBytes ? (byteArrayToReturnToPool = ArrayPool <byte> .Shared.Rent(macLength)) : stackalloc byte[Constants.MaxStackallocBytes]; try { macBytes = macBytes.Slice(0, macLength); associatedData.CopyTo(macBytes); var bytes = macBytes.Slice(associatedData.Length); iv.CopyTo(bytes); bytes = bytes.Slice(iv.Length); ciphertext.CopyTo(bytes); bytes = bytes.Slice(ciphertext.Length); BinaryPrimitives.WriteInt64BigEndian(bytes, associatedData.Length << 3); Sha2 hashAlgorithm = _encryptionAlgorithm.SignatureAlgorithm.Sha; Span <byte> hmacKey = stackalloc byte[Sha2.BlockSizeStackallocThreshold * 2] .Slice(0, hashAlgorithm.BlockSize * 2); Hmac hmac = new Hmac(hashAlgorithm, key, hmacKey); Span <byte> hash = stackalloc byte[AuthenticatedEncryptor.TagSizeStackallocThreshold] .Slice(0, authenticationTag.Length * 2); hmac.ComputeHash(macBytes, hash); CryptographicOperations.ZeroMemory(hmacKey); return(CryptographicOperations.FixedTimeEquals(authenticationTag, hash.Slice(0, tagSize))); } finally { if (byteArrayToReturnToPool != null) { ArrayPool <byte> .Shared.Return(byteArrayToReturnToPool); } } }
public static void EmptyReturnTrue() { int byteLength = 0; byte[] rented = ArrayPool <byte> .Shared.Rent(byteLength); Span <byte> testSpan = new Span <byte>(rented, 0, byteLength); Fill(rented, 0, byteLength); ReadOnlySpan <byte> emptySpan = ReadOnlySpan <byte> .Empty; bool isEqualA = CryptographicOperations.FixedTimeEquals(testSpan, emptySpan); bool isEqualB = CryptographicOperations.FixedTimeEquals(emptySpan, testSpan); ArrayPool <byte> .Shared.Return(rented); Assert.True(isEqualA, "FixedTimeEquals(testSpan, emptySpan)"); Assert.True(isEqualB, "FixedTimeEquals(emptySpan, testSpan)"); }
public static void EqualReturnsTrue(int byteLength) { byte[] rented = ArrayPool <byte> .Shared.Rent(byteLength); Span <byte> testSpan = new Span <byte>(rented, 0, byteLength); Fill(rented, 0, byteLength); byte[] rented2 = ArrayPool <byte> .Shared.Rent(byteLength); Span <byte> testSpan2 = new Span <byte>(rented2, 0, byteLength); testSpan.CopyTo(testSpan2); bool isEqual = CryptographicOperations.FixedTimeEquals(testSpan, testSpan2); ArrayPool <byte> .Shared.Return(rented); ArrayPool <byte> .Shared.Return(rented2); Assert.True(isEqual); }
public static void DifferentLengthsReturnFalse(int byteLength) { byte[] rented = ArrayPool <byte> .Shared.Rent(byteLength); Span <byte> testSpan = new Span <byte>(rented, 0, byteLength); Fill(rented, 0, byteLength); byte[] rented2 = ArrayPool <byte> .Shared.Rent(byteLength); Span <byte> testSpan2 = new Span <byte>(rented2, 0, byteLength); testSpan.CopyTo(testSpan2); bool isEqualA = CryptographicOperations.FixedTimeEquals(testSpan, testSpan2.Slice(0, byteLength - 1)); bool isEqualB = CryptographicOperations.FixedTimeEquals(testSpan.Slice(0, byteLength - 1), testSpan2); ArrayPool <byte> .Shared.Return(rented); ArrayPool <byte> .Shared.Return(rented2); Assert.False(isEqualA, "value, value missing last byte"); Assert.False(isEqualB, "value missing last byte, value"); }
/// <inheritsdoc /> public override bool TryUnwrapKey(ReadOnlySpan <byte> key, Span <byte> destination, JwtHeaderDocument header, out int bytesWritten) { Debug.Assert(header != null); if (key.IsEmpty) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } if (_disposed) { ThrowHelper.ThrowObjectDisposedException(GetType()); } #if SUPPORT_SPAN_CRYPTO #if !NETCOREAPP return(_rsa.TryDecrypt(key, destination, _padding, out bytesWritten)); #else try { // https://github.com/dotnet/corefx/pull/36601 bool decrypted; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { int keySizeBytes = _rsa.KeySize / 8; // OpenSSL does not take a length value for the destination, so it can write out of bounds. // To prevent the OOB write, decrypt into a temporary buffer. if (destination.Length < keySizeBytes) { Span <byte> tmp = stackalloc byte[0]; byte[]? rent = null; try { // RSA up through 4096 stackalloc if (_rsa.KeySize <= Constants.MaxStackallocBytes * 8 * 2) { tmp = stackalloc byte[Constants.MaxStackallocBytes * 2]; } else { rent = ArrayPool <byte> .Shared.Rent(keySizeBytes); tmp = rent; } decrypted = _rsa.TryDecrypt(key, tmp, _padding, out bytesWritten); if (decrypted) { if (bytesWritten > destination.Length) { decrypted = false; bytesWritten = 0; } else { tmp = tmp.Slice(0, bytesWritten); tmp.CopyTo(destination); } CryptographicOperations.ZeroMemory(tmp); } } finally { if (rent != null) { // Already cleared ArrayPool <byte> .Shared.Return(rent); } } } else { decrypted = _rsa.TryDecrypt(key, destination, _padding, out bytesWritten); } } else { decrypted = _rsa.TryDecrypt(key, destination, _padding, out bytesWritten); } return(decrypted); } catch (CryptographicException) { bytesWritten = 0; return(false); } #endif #else try { var result = _rsa.Decrypt(key.ToArray(), _padding); bytesWritten = result.Length; result.CopyTo(destination); return(true); } catch (CryptographicException) { bytesWritten = 0; return(false); } #endif }
// Taken from https://github.com/dotnet/runtime/blob/master/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs#L516 public static int Transform(ICryptoTransform transform, ReadOnlySpan <byte> input, int inputOffset, int inputLength, Span <byte> output) { byte[] buffer = input.ToArray(); int offset = inputOffset; int count = inputLength; var inputBlockSize = transform.InputBlockSize; var outputBlockSize = transform.OutputBlockSize; var inputBuffer = ArrayPool <byte> .Shared.Rent(inputBlockSize); var outputBuffer = ArrayPool <byte> .Shared.Rent(outputBlockSize); try { int inputBufferIndex = 0; // write <= count bytes to the output stream, transforming as we go. // Basic idea: using bytes in the _InputBuffer first, make whole blocks, // transform them, and write them out. Cache any remaining bytes in the _InputBuffer. int bytesToWrite = count; int currentInputIndex = offset; // if we have some bytes in the _InputBuffer, we have to deal with those first, // so let's try to make an entire block out of it int numOutputBytes; int outputLength = 0; while (bytesToWrite > 0) { if (bytesToWrite >= inputBlockSize) { // We have at least an entire block's worth to transform int numWholeBlocks = bytesToWrite / inputBlockSize; // If the transform will handle multiple blocks at once, do that if (transform.CanTransformMultipleBlocks && numWholeBlocks > 1) { int numWholeBlocksInBytes = numWholeBlocks * inputBlockSize; byte[] tempOutputBuffer = ArrayPool <byte> .Shared.Rent(numWholeBlocksInBytes); Span <byte> outputSpan; try { numOutputBytes = transform.TransformBlock(buffer, currentInputIndex, numWholeBlocksInBytes, tempOutputBuffer, 0); outputSpan = tempOutputBuffer.AsSpan(0, numOutputBytes); outputSpan.CopyTo(output.Slice(outputLength)); outputLength += numOutputBytes; currentInputIndex += numWholeBlocksInBytes; bytesToWrite -= numWholeBlocksInBytes; CryptographicOperations.ZeroMemory(outputSpan); } finally { ArrayPool <byte> .Shared.Return(tempOutputBuffer); } } else { // do it the slow way numOutputBytes = transform.TransformBlock(buffer, currentInputIndex, inputBlockSize, outputBuffer, 0); outputBuffer.AsSpan(0, numOutputBytes).CopyTo(output.Slice(outputLength)); outputLength += numOutputBytes; currentInputIndex += inputBlockSize; bytesToWrite -= inputBlockSize; } } else { // In this case, we don't have an entire block's worth left, so store it up in the // input buffer, which by now must be empty. Buffer.BlockCopy(buffer, currentInputIndex, inputBuffer, 0, bytesToWrite); inputBufferIndex += bytesToWrite; break; } } byte[] finalBytes = transform.TransformFinalBlock(inputBuffer, 0, inputBufferIndex); finalBytes.AsSpan().CopyTo(output.Slice(outputLength)); return(outputLength + finalBytes.Length); } finally { ArrayPool <byte> .Shared.Return(inputBuffer); ArrayPool <byte> .Shared.Return(outputBuffer); } }