Define an async API surface to handle the async nature of the Web Crypto.
The current API surface only defines support for synchronous platform integration.
Original background issue https://github.com/dotnet/runtime/issues/40074
Relevant documentation can be found at https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto.
Note: MD5 is not supported by the browser API so even though it may be referenced below it will continue to throw a PNSE on browser.
SHA-1
SHA-256
SHA-384
SHA-512
The purpose is to supply some input data, run it through the hashing function, and get a hash code back using an Async API surface instead of the synchronous method calls.
namespace System.Security.Cryptography
{
public abstract partial class HashAlgorithm : System.IDisposable, System.Security.Cryptography.ICryptoTransform
{
public System.Threading.Tasks.Task<byte[]> ComputeHashAsync(byte[] buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task<byte[]> ComputeHashAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
protected abstract System.Threading.Tasks.Task HashCoreAsync(byte[] array, int ibStart, int cbSize, System.Threading.CancellationToken cancellationToken);
protected abstract System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken);
}
}
public abstract partial class SHA1 : System.Security.Cryptography.HashAlgorithm
{
public static System.Threading.Tasks.Task<byte[]> HashDataAsync(byte[] source, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<int> HashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<(bool IsSuccess, int BytesWritten)> TryHashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed partial class SHA1Managed : System.Security.Cryptography.SHA1
{
protected sealed override System.Threading.Tasks.Task HashCoreAsync(byte[] array, int ibStart, int cbSize, System.Threading.CancellationToken cancellationToken) { throw null; }
protected sealed override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public abstract partial class SHA256 : System.Security.Cryptography.HashAlgorithm
{
public static System.Threading.Tasks.Task<byte[]> HashDataAsync(byte[] source, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<int> HashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<(bool IsSuccess, int BytesWritten)> TryHashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed partial class SHA256Managed : System.Security.Cryptography.SHA256
{
protected sealed override System.Threading.Tasks.Task HashCoreAsync(byte[] array, int ibStart, int cbSize, System.Threading.CancellationToken cancellationToken) { throw null; }
protected sealed override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public abstract partial class SHA384 : System.Security.Cryptography.HashAlgorithm
{
public static System.Threading.Tasks.Task<byte[]> HashDataAsync(byte[] source, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<int> HashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<(bool IsSuccess, int BytesWritten)> TryHashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed partial class SHA384Managed : System.Security.Cryptography.SHA384
{
protected sealed override System.Threading.Tasks.Task HashCoreAsync(byte[] array, int ibStart, int cbSize, System.Threading.CancellationToken cancellationToken) { throw null; }
protected sealed override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public abstract partial class SHA512 : System.Security.Cryptography.HashAlgorithm
{
public static System.Threading.Tasks.Task<byte[]> HashDataAsync(byte[] source, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<int> HashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
public static System.Threading.Tasks.Task<(bool IsSuccess, int BytesWritten)> TryHashDataAsync(byte[] source, byte[] destination, System.Threading.CancellationToken cancellationToken = default) { throw null; }
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed partial class SHA512Managed : System.Security.Cryptography.SHA512
{
protected sealed override System.Threading.Tasks.Task HashCoreAsync(byte[] array, int ibStart, int cbSize, System.Threading.CancellationToken cancellationToken) { throw null; }
protected sealed override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
```C#
public partial class HMACMD5 : System.Security.Cryptography.HMAC
{
protected override System.Threading.Tasks.Task HashCoreAsync(byte[] rgb, int ib, int cb, System.Threading.CancellationToken cancellationToken) { throw null; }
protected override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public partial class HMACSHA1 : System.Security.Cryptography.HMAC
{
protected override System.Threading.Tasks.Task HashCoreAsync(byte[] rgb, int ib, int cb, System.Threading.CancellationToken cancellationToken) { throw null; }
protected override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public partial class HMACSHA256 : System.Security.Cryptography.HMAC
{
protected override System.Threading.Tasks.Task HashCoreAsync(byte[] rgb, int ib, int cb, System.Threading.CancellationToken cancellationToken) { throw null; }
protected override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public partial class HMACSHA384 : System.Security.Cryptography.HMAC
{
protected override System.Threading.Tasks.Task HashCoreAsync(byte[] rgb, int ib, int cb, System.Threading.CancellationToken cancellationToken) { throw null; }
protected override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
public partial class HMACSHA512 : System.Security.Cryptography.HMAC
{
protected override System.Threading.Tasks.Task HashCoreAsync(byte[] rgb, int ib, int cb, System.Threading.CancellationToken cancellationToken) { throw null; }
protected override System.Threading.Tasks.Task<byte[]> HashFinalAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
## Usage Examples
HashAlgorithm
``` C#
public static Task<byte[]> ComputeHashSha256Async(byte[] toBeHashed)
{
using (var sha256 = SHA256.Create())
{
return sha256.ComputeHashAsync(toBeHashed, CancellationToken.None);
}
}
var sha256HashedMessage = await ComputeHashSha256Async(
Encoding.UTF8.GetBytes(originalMessage)).ConfigureAwait(false);
Async HashData usage
```C#
sha1HashedMessage = await SHA1.HashDataAsync(Encoding.UTF8.GetBytes(originalMessage)).ConfigureAwait(false);
Console.WriteLine("Message 1 HashDataAsync = " +
Convert.ToBase64String(sha1HashedMessage));
byte[] destination = new byte[20];
int written = await SHA1.HashDataAsync(Encoding.UTF8.GetBytes(originalMessage), destination).ConfigureAwait(false);
sha1HashedMessage = destination;
Console.WriteLine($"Message 1 HashDataAsync {written} with destination = " +
Convert.ToBase64String(sha1HashedMessage, 0, written));
Authentication Hashing
```C#
public static Task<byte[]> ComputeHmacsha1Async(byte[] toBeHashed, byte[] key, CancellationToken cancellationToken = default)
{
using (var hmac = new HMACSHA1(key))
{
return hmac.ComputeHashAsync(toBeHashed, cancellationToken);
}
}
var hmacSHA1Message = await Hmac.ComputeHmacsha1Async(
Encoding.UTF8.GetBytes(originalMessage), key).ConfigureAwait(false);
public static Task<byte[]> ComputeHmacsha256Async(byte[] toBeHashed, byte[] key, CancellationToken cancellationToken = default)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHashAsync(toBeHashed, 5, 8, cancellationToken);
}
}
var hmacSHA256Message = await ComputeHmacsha256Async(
Encoding.UTF8.GetBytes(originalMessage), key).ConfigureAwait(false);
Other options would be shipping OpenSSL for Browser which should be avoided. Shipping OpenSSL for Browser presents a whole other set of problem areas. See original issue for more detailed information : https://github.com/dotnet/runtime/issues/40074
Risks are a change to the existing library implementations to include the async api calls for browser support.
The browser API can not support synchronous calls so will throw PNSE.
The non browser implementations will not at the beginning define implementations for the asynchronous calls so will throw PNSE.
Synchronous Hashing functions for browser will now throw PNSE but one alternative would be to leave the managed implementation of the sync call in as well. This has been externally commented on and should be taken into account here as well to mitigate API Risks.
Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.
Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq, @jeffhandley
See info in area-owners.md if you want to be subscribed.
I am __strongly against__ the API as proposed here. We have evidence in other threads that people want to take their existing code and port it to these environments, and that they're unable to make changes to these existing code paths because it would require asyncifying their code in unnatural ways. The current plan is to remove entirely the managed implementations in the .NET 6 time frame.
I would instead prefer the following. Either:
This feels like polluting/hobbling the greater framework because of the sins of javascript and SubtleCrypto.
This, combined with the fact that the only reason managed crypto made it back was to be a stopga for WASM until we can remove it again, breaking the approach we've had for .NET Core to not implement crypto in the stack makes this a big non-starter.
Hey everyone.
Right now there really is not a way to do sync over async as webassembly does not support threads at this time and is planned for .NET 7 time frame from my understanding.
Not sure I would classify these as sins of javascript and SubtleCrypto but the way they implemented them could be thought of as keeping up with with the times of bringing an async API to something that can be potentially heavy processing.
_Putting the async APIs in a wasm-specific namespace and tell developers that these are the only APIs that will be supported in wasm_ is an option that can be considered.
There is also a precedent for async within the API surface.
Right now there really is not a way to do sync over async as webassembly does not support threads at this time and is planned for .NET 7 time frame from my understanding.
What then would be an eventual end goal with all cryptographic primitives? I would imagine if folks want hashing, they are also going to want symmetric / asymmetric encryption, signing, KDFs, etc. This moves toward having many new API additions for all cryptographic primitive operations.
There is also a precedent for async within the API surface.
That, to me, is different than an async cryptographic primitive. That does async IO to update a hash.
Putting the async APIs in a wasm-specific namespace and tell developers that these are the only APIs that will be supported in wasm
I'm rather uneducated about blazor, but I thought to task: do new APIs need to exist in this case? Isn't there a mechanism to invoke browser JS from Blazor WASM via IJSRuntime or similar?
Right now there really is not a way to do sync over async as webassembly does not support threads at this time and is planned for .NET 7 time frame from my understanding.
Is there any way at all to mimic sync-over-async in wasm? Our security signoff of this feature in .NET 5 was contingent on the managed implementation being __completely removed__ in the .NET 6 timeframe. This was listed as an explicit goal for the .NET 6 timeframe, and it's what allowed our exception to go through.
Not sure I would classify these as sins of javascript and SubtleCrypto but the way they implemented them could be thought of as keeping up with with the times of bringing an async API to something that can be potentially heavy processing.
Aside from the PFX library, we don't normally provide async overloads of methods known to be CPU-bound. Async methods are really meant for things like non-blocking i/o. For CPU-bound work, the normal pattern is to provide a sync-only overload, and the developer can Task.Run it if needed.
Now, if you have a _key handle_ instead of raw key material, that's a different matter, since the processing might actually be done off-box. But the APIs we're talking about here take their key material as raw bytes, which means they're all going to be processed in-proc using the local CPU.
Is there any way at all to mimic sync-over-async in wasm? Our security signoff of this feature in .NET 5 was contingent on the managed implementation being completely removed in the .NET 6 timeframe. This was listed as an explicit goal for the .NET 6 timeframe, and it's what allowed our exception to go through.
Not at this time due to threads and that support is, again from my understanding, scheduled for .NET 7.
_Putting the async APIs in an async-specific namespace and tell developers that these are the only APIs that will be supported in wasm_
is an option that is being discussed.
We can't even do something na茂ve like kick off the async work, then spin the original thread waiting for the promise to finish?
Most helpful comment
I am __strongly against__ the API as proposed here. We have evidence in other threads that people want to take their existing code and port it to these environments, and that they're unable to make changes to these existing code paths because it would require asyncifying their code in unnatural ways. The current plan is to remove entirely the managed implementations in the .NET 6 time frame.
I would instead prefer the following. Either: