From 4424bfffd7712518e3cd91cab891398645f70272 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Tue, 23 Jun 2026 09:12:38 +1000 Subject: [PATCH] CryptographicOperations --- apiCount.include.md | 44 +- api_list.include.md | 36 +- assemblySize.include.md | 80 +-- readme.md | 164 +++--- src/ApiBuilderTests/BuildApiTest.cs | 1 + src/Consume/Consume.cs | 47 ++ src/Polyfill/CryptographicOperations.cs | 531 ++++++++++++++++++ .../CryptographicOperationsExtensions.cs | 461 +++++++++++++++ .../CryptographicOperationsExtensions.cs | 131 +++++ src/Split/net10.0/TypeForwardeds.cs | 1 + src/Split/net11.0/TypeForwardeds.cs | 1 + src/Split/net461/CryptographicOperations.cs | 420 ++++++++++++++ src/Split/net462/CryptographicOperations.cs | 420 ++++++++++++++ src/Split/net47/CryptographicOperations.cs | 420 ++++++++++++++ src/Split/net471/CryptographicOperations.cs | 420 ++++++++++++++ src/Split/net472/CryptographicOperations.cs | 420 ++++++++++++++ src/Split/net48/CryptographicOperations.cs | 420 ++++++++++++++ src/Split/net481/CryptographicOperations.cs | 420 ++++++++++++++ .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/net5.0/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/net6.0/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/net7.0/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/net8.0/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 131 +++++ src/Split/net9.0/TypeForwardeds.cs | 1 + .../netcoreapp2.0/CryptographicOperations.cs | 420 ++++++++++++++ .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/netcoreapp2.1/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/netcoreapp2.2/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/netcoreapp3.0/TypeForwardeds.cs | 1 + .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/netcoreapp3.1/TypeForwardeds.cs | 1 + .../netstandard2.0/CryptographicOperations.cs | 420 ++++++++++++++ .../CryptographicOperationsExtensions.cs | 352 ++++++++++++ src/Split/netstandard2.1/TypeForwardeds.cs | 1 + src/Split/uap10.0/CryptographicOperations.cs | 420 ++++++++++++++ .../CryptographicOperationsPolyfillTests.cs | 255 +++++++++ 42 files changed, 9134 insertions(+), 127 deletions(-) create mode 100644 src/Polyfill/CryptographicOperations.cs create mode 100644 src/Polyfill/CryptographicOperationsExtensions.cs create mode 100644 src/Split/net10.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/net461/CryptographicOperations.cs create mode 100644 src/Split/net462/CryptographicOperations.cs create mode 100644 src/Split/net47/CryptographicOperations.cs create mode 100644 src/Split/net471/CryptographicOperations.cs create mode 100644 src/Split/net472/CryptographicOperations.cs create mode 100644 src/Split/net48/CryptographicOperations.cs create mode 100644 src/Split/net481/CryptographicOperations.cs create mode 100644 src/Split/net5.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/net6.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/net7.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/net8.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/net9.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/netcoreapp2.0/CryptographicOperations.cs create mode 100644 src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs create mode 100644 src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs create mode 100644 src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs create mode 100644 src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs create mode 100644 src/Split/netstandard2.0/CryptographicOperations.cs create mode 100644 src/Split/netstandard2.1/CryptographicOperationsExtensions.cs create mode 100644 src/Split/uap10.0/CryptographicOperations.cs create mode 100644 src/Tests/CryptographicOperationsPolyfillTests.cs diff --git a/apiCount.include.md b/apiCount.include.md index f4671527..4ab7fbf5 100644 --- a/apiCount.include.md +++ b/apiCount.include.md @@ -1,28 +1,28 @@ -**API count: 988** +**API count: 1015** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 953 | -| `net462` | 953 | -| `net47` | 952 | -| `net471` | 951 | -| `net472` | 947 | -| `net48` | 947 | -| `net481` | 947 | -| `netstandard2.0` | 949 | -| `netstandard2.1` | 778 | -| `netcoreapp2.0` | 870 | -| `netcoreapp2.1` | 789 | -| `netcoreapp2.2` | 789 | -| `netcoreapp3.0` | 736 | -| `netcoreapp3.1` | 735 | -| `net5.0` | 607 | -| `net6.0` | 512 | -| `net7.0` | 359 | -| `net8.0` | 240 | -| `net9.0` | 164 | -| `net10.0` | 111 | +| `net461` | 962 | +| `net462` | 962 | +| `net47` | 961 | +| `net471` | 960 | +| `net472` | 956 | +| `net48` | 956 | +| `net481` | 956 | +| `netstandard2.0` | 958 | +| `netstandard2.1` | 811 | +| `netcoreapp2.0` | 879 | +| `netcoreapp2.1` | 822 | +| `netcoreapp2.2` | 822 | +| `netcoreapp3.0` | 769 | +| `netcoreapp3.1` | 768 | +| `net5.0` | 640 | +| `net6.0` | 545 | +| `net7.0` | 392 | +| `net8.0` | 265 | +| `net9.0` | 179 | +| `net10.0` | 126 | | `net11.0` | 58 | -| `uap10.0` | 937 | +| `uap10.0` | 948 | diff --git a/api_list.include.md b/api_list.include.md index 75f75244..20b6a82a 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -806,8 +806,8 @@ * Note: No-op on older targets; the BCL grows the backing storage. * `void TrimExcess(int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trimexcess?view=net-11.0#system-collections-generic-queue-1-trimexcess(system-int32)) * Note: No-op on older targets; the BCL shrinks the backing storage. - * `bool TryPeek(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trypeek?view=net-11.0) * `bool TryDequeue(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trydequeue?view=net-11.0) + * `bool TryPeek(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trypeek?view=net-11.0) #### Random @@ -1459,6 +1459,40 @@ * Note: When growing, new elements are default(T); the BCL exposes uninitialized data. +#### CryptographicOperations + + * `bool FixedTimeEquals(ReadOnlySpan, byte)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte)) + * Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + * `bool FixedTimeEquals(ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + * `byte[] HashData(HashAlgorithmName, byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte())) + * `int HashData(HashAlgorithmName, ReadOnlySpan, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte)))) + * `byte[] HashData(HashAlgorithmName, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte)))) + * `int HashData(HashAlgorithmName, Stream, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte)))) + * `byte[] HashData(HashAlgorithmName, Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream)) + * `ValueTask HashDataAsync(HashAlgorithmName, Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HashDataAsync(HashAlgorithmName, Stream, Memory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken)) + * `byte[] HmacData(HashAlgorithmName, byte[], byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte())) + * `byte[] HmacData(HashAlgorithmName, byte[], Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream)) + * `int HmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte)))) + * `byte[] HmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * `int HmacData(HashAlgorithmName, ReadOnlySpan, Stream, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte)))) + * `byte[] HmacData(HashAlgorithmName, ReadOnlySpan, Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream)) + * `ValueTask HmacDataAsync(HashAlgorithmName, byte[], Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HmacDataAsync(HashAlgorithmName, ReadOnlyMemory, Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HmacDataAsync(HashAlgorithmName, ReadOnlyMemory, Stream, Memory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken)) + * `bool TryHashData(HashAlgorithmName, ReadOnlySpan, Span, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0) + * `bool TryHmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, Span, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0) + * `bool VerifyHmac(HashAlgorithmName, byte[], byte[], byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte())) + * `bool VerifyHmac(HashAlgorithmName, byte[], Stream, byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte())) + * `bool VerifyHmac(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * `bool VerifyHmac(HashAlgorithmName, ReadOnlySpan, Stream, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte)))) + * `ValueTask VerifyHmacAsync(HashAlgorithmName, byte[], Stream, byte[], CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken)) + * `ValueTask VerifyHmacAsync(HashAlgorithmName, ReadOnlyMemory, Stream, ReadOnlyMemory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken)) + * `void ZeroMemory(Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.zeromemory?view=net-11.0) + * Note: Best-effort zeroing; unlike the BCL intrinsic the JIT may elide the clear if the buffer is not observed afterwards. + + #### Utf16 * `int IndexOfInvalidSubsequence(ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.utf16.indexofinvalidsubsequence?view=net-11.0) diff --git a/assemblySize.include.md b/assemblySize.include.md index 644d918d..eee74f09 100644 --- a/assemblySize.include.md +++ b/assemblySize.include.md @@ -2,26 +2,26 @@ | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 330.0KB | +322.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netstandard2.1 | 8.5KB | 283.5KB | +275.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net461 | 8.5KB | 328.5KB | +320.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net462 | 7.0KB | 332.0KB | +325.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net47 | 7.0KB | 332.0KB | +325.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net471 | 8.5KB | 331.0KB | +322.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net472 | 8.5KB | 330.0KB | +321.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | -| net48 | 8.5KB | 330.0KB | +321.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | -| net481 | 8.5KB | 330.0KB | +321.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | -| netcoreapp2.0 | 9.0KB | 306.0KB | +297.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 286.0KB | +277.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp2.2 | 9.0KB | 286.0KB | +277.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp3.0 | 9.5KB | 278.5KB | +269.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.1 | 9.5KB | 276.5KB | +267.0KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | -| net5.0 | 9.5KB | 240.0KB | +230.5KB | +9.5KB | +6.5KB | +9.5KB | +14.0KB | -| net6.0 | 10.0KB | 181.5KB | +171.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | -| net7.0 | 10.0KB | 144.0KB | +134.0KB | +9.0KB | +5.0KB | +512bytes | +3.0KB | -| net8.0 | 9.5KB | 115.0KB | +105.5KB | +8.5KB | | +512bytes | +3.5KB | -| net9.0 | 9.5KB | 72.0KB | +62.5KB | +8.5KB | | +512bytes | +3.5KB | -| net10.0 | 10.0KB | 48.5KB | +38.5KB | +9.0KB | | +1.0KB | +3.5KB | +| netstandard2.0 | 8.0KB | 342.0KB | +334.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netstandard2.1 | 8.5KB | 297.0KB | +288.5KB | +9.0KB | +6.0KB | +9.0KB | +13.5KB | +| net461 | 8.5KB | 341.0KB | +332.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net462 | 7.0KB | 344.5KB | +337.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net47 | 7.0KB | 344.5KB | +337.5KB | +8.5KB | +6.0KB | +9.0KB | +13.5KB | +| net471 | 8.5KB | 343.5KB | +335.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net472 | 8.5KB | 342.0KB | +333.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net48 | 8.5KB | 342.0KB | +333.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net481 | 8.5KB | 342.0KB | +333.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.0 | 9.0KB | 318.0KB | +309.0KB | +9.0KB | +6.5KB | +9.5KB | +13.5KB | +| netcoreapp2.1 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.2 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.0 | 9.5KB | 292.0KB | +282.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.1 | 9.5KB | 290.0KB | +280.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net5.0 | 9.5KB | 254.0KB | +244.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| net6.0 | 10.0KB | 195.0KB | +185.0KB | +10.0KB | +7.0KB | +1.0KB | +3.5KB | +| net7.0 | 10.0KB | 157.5KB | +147.5KB | +9.5KB | +5.5KB | +512bytes | +3.5KB | +| net8.0 | 9.5KB | 127.5KB | +118.0KB | +8.0KB | | +512bytes | +3.0KB | +| net9.0 | 9.5KB | 82.5KB | +73.0KB | +8.5KB | | +512bytes | +3.5KB | +| net10.0 | 10.0KB | 60.0KB | +50.0KB | +9.0KB | | +512bytes | +3.5KB | | net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB | @@ -29,24 +29,24 @@ | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 481.4KB | +473.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netstandard2.1 | 8.5KB | 409.6KB | +401.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net461 | 8.5KB | 480.9KB | +472.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net462 | 7.0KB | 484.4KB | +477.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net47 | 7.0KB | 484.2KB | +477.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net471 | 8.5KB | 482.8KB | +474.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net472 | 8.5KB | 480.8KB | +472.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | -| net48 | 8.5KB | 480.8KB | +472.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | -| net481 | 8.5KB | 480.8KB | +472.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | -| netcoreapp2.0 | 9.0KB | 447.0KB | +438.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 415.9KB | +406.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp2.2 | 9.0KB | 415.9KB | +406.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp3.0 | 9.5KB | 399.6KB | +390.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.1 | 9.5KB | 397.6KB | +388.1KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net5.0 | 9.5KB | 342.9KB | +333.4KB | +17.2KB | +8.2KB | +14.4KB | +19.4KB | -| net6.0 | 10.0KB | 264.4KB | +254.4KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | -| net7.0 | 10.0KB | 208.3KB | +198.3KB | +16.6KB | +6.4KB | +1.1KB | +3.7KB | -| net8.0 | 9.5KB | 164.0KB | +154.5KB | +16.0KB | +299bytes | +1.1KB | +4.2KB | -| net9.0 | 9.5KB | 100.7KB | +91.2KB | +16.0KB | | +1.1KB | +4.2KB | -| net10.0 | 10.0KB | 68.3KB | +58.3KB | +16.5KB | | +1.6KB | +4.2KB | +| netstandard2.0 | 8.0KB | 497.7KB | +489.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netstandard2.1 | 8.5KB | 427.1KB | +418.6KB | +16.7KB | +7.7KB | +13.9KB | +18.9KB | +| net461 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net462 | 7.0KB | 501.2KB | +494.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net47 | 7.0KB | 500.9KB | +493.9KB | +16.2KB | +7.7KB | +13.9KB | +18.9KB | +| net471 | 8.5KB | 499.6KB | +491.1KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net472 | 8.5KB | 497.0KB | +488.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net48 | 8.5KB | 497.0KB | +488.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net481 | 8.5KB | 497.0KB | +488.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.0 | 9.0KB | 463.1KB | +454.1KB | +16.7KB | +8.2KB | +14.4KB | +18.9KB | +| netcoreapp2.1 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.2 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.0 | 9.5KB | 417.0KB | +407.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.1 | 9.5KB | 415.0KB | +405.5KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net5.0 | 9.5KB | 360.8KB | +351.3KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| net6.0 | 10.0KB | 281.8KB | +271.8KB | +17.7KB | +8.7KB | +1.6KB | +4.2KB | +| net7.0 | 10.0KB | 225.8KB | +215.8KB | +17.1KB | +6.9KB | +1.1KB | +4.2KB | +| net8.0 | 9.5KB | 180.5KB | +171.0KB | +15.5KB | +299bytes | +1.1KB | +3.7KB | +| net9.0 | 9.5KB | 114.7KB | +105.2KB | +16.0KB | | +1.1KB | +4.2KB | +| net10.0 | 10.0KB | 83.9KB | +73.9KB | +16.5KB | | +1.1KB | +4.2KB | | net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB | diff --git a/readme.md b/readme.md index 93424ad5..456d682c 100644 --- a/readme.md +++ b/readme.md @@ -13,34 +13,34 @@ The package targets `netstandard2.0` and is designed to support the following ru * `uap10` -**API count: 986** +**API count: 1015** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 951 | -| `net462` | 951 | -| `net47` | 950 | -| `net471` | 949 | -| `net472` | 945 | -| `net48` | 945 | -| `net481` | 945 | -| `netstandard2.0` | 947 | -| `netstandard2.1` | 778 | -| `netcoreapp2.0` | 870 | -| `netcoreapp2.1` | 789 | -| `netcoreapp2.2` | 789 | -| `netcoreapp3.0` | 736 | -| `netcoreapp3.1` | 735 | -| `net5.0` | 607 | -| `net6.0` | 512 | -| `net7.0` | 359 | -| `net8.0` | 240 | -| `net9.0` | 164 | -| `net10.0` | 111 | +| `net461` | 962 | +| `net462` | 962 | +| `net47` | 961 | +| `net471` | 960 | +| `net472` | 956 | +| `net48` | 956 | +| `net481` | 956 | +| `netstandard2.0` | 958 | +| `netstandard2.1` | 811 | +| `netcoreapp2.0` | 879 | +| `netcoreapp2.1` | 822 | +| `netcoreapp2.2` | 822 | +| `netcoreapp3.0` | 769 | +| `netcoreapp3.1` | 768 | +| `net5.0` | 640 | +| `net6.0` | 545 | +| `net7.0` | 392 | +| `net8.0` | 265 | +| `net9.0` | 179 | +| `net10.0` | 126 | | `net11.0` | 58 | -| `uap10.0` | 937 | +| `uap10.0` | 948 | @@ -96,26 +96,26 @@ This project uses features from the newest stable SDK and C# language. As such c | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 330.0KB | +322.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netstandard2.1 | 8.5KB | 283.5KB | +275.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net461 | 8.5KB | 328.5KB | +320.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net462 | 7.0KB | 332.0KB | +325.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net47 | 7.0KB | 332.0KB | +325.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net471 | 8.5KB | 331.0KB | +322.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net472 | 8.5KB | 330.0KB | +321.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | -| net48 | 8.5KB | 330.0KB | +321.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | -| net481 | 8.5KB | 330.0KB | +321.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | -| netcoreapp2.0 | 9.0KB | 306.0KB | +297.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 286.0KB | +277.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp2.2 | 9.0KB | 286.0KB | +277.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp3.0 | 9.5KB | 278.5KB | +269.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.1 | 9.5KB | 276.5KB | +267.0KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | -| net5.0 | 9.5KB | 240.0KB | +230.5KB | +9.5KB | +6.5KB | +9.5KB | +14.0KB | -| net6.0 | 10.0KB | 181.5KB | +171.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | -| net7.0 | 10.0KB | 144.0KB | +134.0KB | +9.0KB | +5.0KB | +512bytes | +3.0KB | -| net8.0 | 9.5KB | 115.0KB | +105.5KB | +8.5KB | | +512bytes | +3.5KB | -| net9.0 | 9.5KB | 72.0KB | +62.5KB | +8.5KB | | +512bytes | +3.5KB | -| net10.0 | 10.0KB | 48.5KB | +38.5KB | +9.0KB | | +1.0KB | +3.5KB | +| netstandard2.0 | 8.0KB | 342.0KB | +334.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netstandard2.1 | 8.5KB | 297.0KB | +288.5KB | +9.0KB | +6.0KB | +9.0KB | +13.5KB | +| net461 | 8.5KB | 341.0KB | +332.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net462 | 7.0KB | 344.5KB | +337.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net47 | 7.0KB | 344.5KB | +337.5KB | +8.5KB | +6.0KB | +9.0KB | +13.5KB | +| net471 | 8.5KB | 343.5KB | +335.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net472 | 8.5KB | 342.0KB | +333.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net48 | 8.5KB | 342.0KB | +333.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net481 | 8.5KB | 342.0KB | +333.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.0 | 9.0KB | 318.0KB | +309.0KB | +9.0KB | +6.5KB | +9.5KB | +13.5KB | +| netcoreapp2.1 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.2 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.0 | 9.5KB | 292.0KB | +282.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.1 | 9.5KB | 290.0KB | +280.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net5.0 | 9.5KB | 254.0KB | +244.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| net6.0 | 10.0KB | 195.0KB | +185.0KB | +10.0KB | +7.0KB | +1.0KB | +3.5KB | +| net7.0 | 10.0KB | 157.5KB | +147.5KB | +9.5KB | +5.5KB | +512bytes | +3.5KB | +| net8.0 | 9.5KB | 127.5KB | +118.0KB | +8.0KB | | +512bytes | +3.0KB | +| net9.0 | 9.5KB | 82.5KB | +73.0KB | +8.5KB | | +512bytes | +3.5KB | +| net10.0 | 10.0KB | 60.0KB | +50.0KB | +9.0KB | | +512bytes | +3.5KB | | net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB | @@ -123,26 +123,26 @@ This project uses features from the newest stable SDK and C# language. As such c | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 481.4KB | +473.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netstandard2.1 | 8.5KB | 409.6KB | +401.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net461 | 8.5KB | 480.9KB | +472.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net462 | 7.0KB | 484.4KB | +477.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net47 | 7.0KB | 484.2KB | +477.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net471 | 8.5KB | 482.8KB | +474.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net472 | 8.5KB | 480.8KB | +472.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | -| net48 | 8.5KB | 480.8KB | +472.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | -| net481 | 8.5KB | 480.8KB | +472.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | -| netcoreapp2.0 | 9.0KB | 447.0KB | +438.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 415.9KB | +406.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp2.2 | 9.0KB | 415.9KB | +406.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp3.0 | 9.5KB | 399.6KB | +390.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.1 | 9.5KB | 397.6KB | +388.1KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net5.0 | 9.5KB | 342.9KB | +333.4KB | +17.2KB | +8.2KB | +14.4KB | +19.4KB | -| net6.0 | 10.0KB | 264.4KB | +254.4KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | -| net7.0 | 10.0KB | 208.3KB | +198.3KB | +16.6KB | +6.4KB | +1.1KB | +3.7KB | -| net8.0 | 9.5KB | 164.0KB | +154.5KB | +16.0KB | +299bytes | +1.1KB | +4.2KB | -| net9.0 | 9.5KB | 100.7KB | +91.2KB | +16.0KB | | +1.1KB | +4.2KB | -| net10.0 | 10.0KB | 68.3KB | +58.3KB | +16.5KB | | +1.6KB | +4.2KB | +| netstandard2.0 | 8.0KB | 497.7KB | +489.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netstandard2.1 | 8.5KB | 427.1KB | +418.6KB | +16.7KB | +7.7KB | +13.9KB | +18.9KB | +| net461 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net462 | 7.0KB | 501.2KB | +494.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net47 | 7.0KB | 500.9KB | +493.9KB | +16.2KB | +7.7KB | +13.9KB | +18.9KB | +| net471 | 8.5KB | 499.6KB | +491.1KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net472 | 8.5KB | 497.0KB | +488.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net48 | 8.5KB | 497.0KB | +488.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net481 | 8.5KB | 497.0KB | +488.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.0 | 9.0KB | 463.1KB | +454.1KB | +16.7KB | +8.2KB | +14.4KB | +18.9KB | +| netcoreapp2.1 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.2 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.0 | 9.5KB | 417.0KB | +407.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.1 | 9.5KB | 415.0KB | +405.5KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net5.0 | 9.5KB | 360.8KB | +351.3KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| net6.0 | 10.0KB | 281.8KB | +271.8KB | +17.7KB | +8.7KB | +1.6KB | +4.2KB | +| net7.0 | 10.0KB | 225.8KB | +215.8KB | +17.1KB | +6.9KB | +1.1KB | +4.2KB | +| net8.0 | 9.5KB | 180.5KB | +171.0KB | +15.5KB | +299bytes | +1.1KB | +3.7KB | +| net9.0 | 9.5KB | 114.7KB | +105.2KB | +16.0KB | | +1.1KB | +4.2KB | +| net10.0 | 10.0KB | 83.9KB | +73.9KB | +16.5KB | | +1.1KB | +4.2KB | | net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB | @@ -1337,6 +1337,8 @@ The class `Polyfill` includes the following extension methods: * Note: No-op on older targets; the BCL grows the backing storage. * `void TrimExcess(int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trimexcess?view=net-11.0#system-collections-generic-queue-1-trimexcess(system-int32)) * Note: No-op on older targets; the BCL shrinks the backing storage. + * `bool TryDequeue(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trydequeue?view=net-11.0) + * `bool TryPeek(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1.trypeek?view=net-11.0) #### Random @@ -1988,6 +1990,40 @@ The class `Polyfill` includes the following extension methods: * Note: When growing, new elements are default(T); the BCL exposes uninitialized data. +#### CryptographicOperations + + * `bool FixedTimeEquals(ReadOnlySpan, byte)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte)) + * Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + * `bool FixedTimeEquals(ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + * `byte[] HashData(HashAlgorithmName, byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte())) + * `int HashData(HashAlgorithmName, ReadOnlySpan, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte)))) + * `byte[] HashData(HashAlgorithmName, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte)))) + * `int HashData(HashAlgorithmName, Stream, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte)))) + * `byte[] HashData(HashAlgorithmName, Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream)) + * `ValueTask HashDataAsync(HashAlgorithmName, Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HashDataAsync(HashAlgorithmName, Stream, Memory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken)) + * `byte[] HmacData(HashAlgorithmName, byte[], byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte())) + * `byte[] HmacData(HashAlgorithmName, byte[], Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream)) + * `int HmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte)))) + * `byte[] HmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * `int HmacData(HashAlgorithmName, ReadOnlySpan, Stream, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte)))) + * `byte[] HmacData(HashAlgorithmName, ReadOnlySpan, Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream)) + * `ValueTask HmacDataAsync(HashAlgorithmName, byte[], Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HmacDataAsync(HashAlgorithmName, ReadOnlyMemory, Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HmacDataAsync(HashAlgorithmName, ReadOnlyMemory, Stream, Memory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken)) + * `bool TryHashData(HashAlgorithmName, ReadOnlySpan, Span, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0) + * `bool TryHmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, Span, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0) + * `bool VerifyHmac(HashAlgorithmName, byte[], byte[], byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte())) + * `bool VerifyHmac(HashAlgorithmName, byte[], Stream, byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte())) + * `bool VerifyHmac(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * `bool VerifyHmac(HashAlgorithmName, ReadOnlySpan, Stream, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte)))) + * `ValueTask VerifyHmacAsync(HashAlgorithmName, byte[], Stream, byte[], CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken)) + * `ValueTask VerifyHmacAsync(HashAlgorithmName, ReadOnlyMemory, Stream, ReadOnlyMemory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken)) + * `void ZeroMemory(Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.zeromemory?view=net-11.0) + * Note: Best-effort zeroing; unlike the BCL intrinsic the JIT may elide the clear if the buffer is not observed afterwards. + + #### Utf16 * `int IndexOfInvalidSubsequence(ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.utf16.indexofinvalidsubsequence?view=net-11.0) @@ -2195,7 +2231,7 @@ void ObjectDisposedExceptionExample(bool isDisposed) ObjectDisposedException.ThrowIf(isDisposed, typeof(Consume)); } ``` -snippet source | anchor +snippet source | anchor @@ -2214,7 +2250,7 @@ void EnsureExample(Order order, Customer customer, string customerId, string ema this.quantity = Ensure.NotNegativeOrZero(quantity); } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/ApiBuilderTests/BuildApiTest.cs b/src/ApiBuilderTests/BuildApiTest.cs index cb3c6f5e..047b78a3 100644 --- a/src/ApiBuilderTests/BuildApiTest.cs +++ b/src/ApiBuilderTests/BuildApiTest.cs @@ -19,6 +19,7 @@ public Task RunWithRoslyn() WriteHelper("Lock", writer, ref count); WriteHelper(nameof(KeyValuePair), writer, ref count); WriteHelper("CollectionsMarshal", writer, ref count); + WriteHelper("CryptographicOperations", writer, ref count); WriteHelper("Utf16", writer, ref count); WriteType(nameof(TaskCompletionSource), writer, ref count); WriteType(nameof(UnreachableException), writer, ref count); diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index ba6ab4a7..6449776f 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -416,6 +416,53 @@ void MD5Usage() MD5.HashDataAsync(source: null!, destination: memory); MD5.HashDataAsync(source: null!, destination: memory, cancellationToken: CancellationToken.None); #endif +#endif + } + + void CryptographicOperationsUsage() + { + var name = HashAlgorithmName.SHA256; + + CryptographicOperations.HashData(name, (byte[]) null!); + CryptographicOperations.HashData(name, (Stream) null!); + CryptographicOperations.HmacData(name, (byte[]) null!, (byte[]) null!); + CryptographicOperations.HmacData(name, (byte[]) null!, (Stream) null!); + CryptographicOperations.VerifyHmac(name, (byte[]) null!, (byte[]) null!, (byte[]) null!); + CryptographicOperations.VerifyHmac(name, (byte[]) null!, (Stream) null!, (byte[]) null!); +#if FeatureValueTask + CryptographicOperations.HashDataAsync(name, (Stream) null!, CancellationToken.None); + CryptographicOperations.HmacDataAsync(name, (byte[]) null!, (Stream) null!, CancellationToken.None); + CryptographicOperations.VerifyHmacAsync(name, (byte[]) null!, (Stream) null!, (byte[]) null!, CancellationToken.None); +#endif +#if FeatureMemory + Span span = default; + ReadOnlySpan readOnlySpan = default; + Memory memory = default; + ReadOnlyMemory readOnlyMemory = default; + + CryptographicOperations.FixedTimeEquals(readOnlySpan, readOnlySpan); + CryptographicOperations.FixedTimeEquals(readOnlySpan, (byte) 0); + CryptographicOperations.ZeroMemory(span); + CryptographicOperations.HashData(name, readOnlySpan); + CryptographicOperations.HashData(name, readOnlySpan, span); + CryptographicOperations.HashData(name, (Stream) null!, span); + CryptographicOperations.TryHashData(name, readOnlySpan, span, out _); + CryptographicOperations.HmacData(name, readOnlySpan, readOnlySpan); + CryptographicOperations.HmacData(name, readOnlySpan, readOnlySpan, span); + CryptographicOperations.HmacData(name, readOnlySpan, (Stream) null!); + CryptographicOperations.HmacData(name, readOnlySpan, (Stream) null!, span); + CryptographicOperations.TryHmacData(name, readOnlySpan, readOnlySpan, span, out _); + CryptographicOperations.VerifyHmac(name, readOnlySpan, readOnlySpan, readOnlySpan); + CryptographicOperations.VerifyHmac(name, readOnlySpan, (Stream) null!, readOnlySpan); +#if FeatureValueTask + CryptographicOperations.HashDataAsync(name, (Stream) null!, memory); + CryptographicOperations.HashDataAsync(name, (Stream) null!, memory, CancellationToken.None); + CryptographicOperations.HmacDataAsync(name, readOnlyMemory, (Stream) null!); + CryptographicOperations.HmacDataAsync(name, readOnlyMemory, (Stream) null!, CancellationToken.None); + CryptographicOperations.HmacDataAsync(name, readOnlyMemory, (Stream) null!, memory, CancellationToken.None); + CryptographicOperations.VerifyHmacAsync(name, readOnlyMemory, (Stream) null!, readOnlyMemory); + CryptographicOperations.VerifyHmacAsync(name, readOnlyMemory, (Stream) null!, readOnlyMemory, CancellationToken.None); +#endif #endif } diff --git a/src/Polyfill/CryptographicOperations.cs b/src/Polyfill/CryptographicOperations.cs new file mode 100644 index 00000000..5f5694c0 --- /dev/null +++ b/src/Polyfill/CryptographicOperations.cs @@ -0,0 +1,531 @@ +#pragma warning disable + +// CryptographicOperations was introduced in netcoreapp2.1/netstandard2.1. +// For targets where the type is entirely absent it is recreated here. +// For targets where the type exists but is missing members (members were added +// in net8/net9/net10) the missing members are added via extension in +// CryptographicOperationsExtensions.cs. +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +namespace System.Security.Cryptography; + +// ReSharper disable RedundantUsingDirective +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +// ReSharper restore RedundantUsingDirective +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +//Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations?view=net-11.0 +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte()) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + +#if FeatureValueTask + + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } + +#endif + +#if FeatureMemory + + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + //Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + + return accum == 0; + } + + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte) + //Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + + return accum == 0; + } + + /// + /// Fills the provided buffer with zeros. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.zeromemory?view=net-11.0 + //Note: Best-effort zeroing; unlike the BCL intrinsic the JIT may elide the clear if the buffer is not observed afterwards. + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + hash.CopyTo(destination); + return hash.Length; + } + + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0 + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + mac.CopyTo(destination); + return mac.Length; + } + + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0 + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } + +#if FeatureValueTask + + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + +#endif +#endif + + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + + return accum == 0; + } +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] +#endif diff --git a/src/Polyfill/CryptographicOperationsExtensions.cs b/src/Polyfill/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..8d074045 --- /dev/null +++ b/src/Polyfill/CryptographicOperationsExtensions.cs @@ -0,0 +1,461 @@ +#pragma warning disable + +// Fills in members missing from the BCL CryptographicOperations on targets where +// the type exists (netcoreapp2.1/netstandard2.1 onwards) but predates the member: +// HashData family added in net8, HmacData family in net9, VerifyHmac family and +// FixedTimeEquals(ReadOnlySpan, byte) in net10. +// For targets where the type is entirely absent the full surface is recreated in +// CryptographicOperations.cs instead. Span/ValueTask/ReadOnlyMemory are intrinsic +// to every target in this window, so no Feature* gates are needed here. +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + +namespace Polyfills; + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ + extension(CryptographicOperations) + { +#if !NET9_0_OR_GREATER + + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte()) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + hash.CopyTo(destination); + return hash.Length; + } + + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0 + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + +#endif + +#if !NET9_0_OR_GREATER + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + mac.CopyTo(destination); + return mac.Length; + } + + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0 + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + +#endif + +#if !NET11_0_OR_GREATER + + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte) + //Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + + return accum == 0; + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + +#endif + } + +#if !NET9_0_OR_GREATER + + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + +#endif + +#if !NET11_0_OR_GREATER + + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + +#endif +} + +#endif diff --git a/src/Split/net10.0/CryptographicOperationsExtensions.cs b/src/Split/net10.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..83fc0dc3 --- /dev/null +++ b/src/Split/net10.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,131 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net10.0/TypeForwardeds.cs b/src/Split/net10.0/TypeForwardeds.cs index b95ad748..8cfaee14 100644 --- a/src/Split/net10.0/TypeForwardeds.cs +++ b/src/Split/net10.0/TypeForwardeds.cs @@ -9,6 +9,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] diff --git a/src/Split/net11.0/TypeForwardeds.cs b/src/Split/net11.0/TypeForwardeds.cs index 22e9145a..ac920b51 100644 --- a/src/Split/net11.0/TypeForwardeds.cs +++ b/src/Split/net11.0/TypeForwardeds.cs @@ -9,6 +9,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] diff --git a/src/Split/net461/CryptographicOperations.cs b/src/Split/net461/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net461/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net462/CryptographicOperations.cs b/src/Split/net462/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net462/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net47/CryptographicOperations.cs b/src/Split/net47/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net47/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net471/CryptographicOperations.cs b/src/Split/net471/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net471/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net472/CryptographicOperations.cs b/src/Split/net472/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net472/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net48/CryptographicOperations.cs b/src/Split/net48/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net48/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net481/CryptographicOperations.cs b/src/Split/net481/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net481/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net5.0/CryptographicOperationsExtensions.cs b/src/Split/net5.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net5.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net5.0/TypeForwardeds.cs b/src/Split/net5.0/TypeForwardeds.cs index d28d5c38..d5dc2b38 100644 --- a/src/Split/net5.0/TypeForwardeds.cs +++ b/src/Split/net5.0/TypeForwardeds.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/net6.0/CryptographicOperationsExtensions.cs b/src/Split/net6.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net6.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net6.0/TypeForwardeds.cs b/src/Split/net6.0/TypeForwardeds.cs index 00408b26..f04df8cf 100644 --- a/src/Split/net6.0/TypeForwardeds.cs +++ b/src/Split/net6.0/TypeForwardeds.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/net7.0/CryptographicOperationsExtensions.cs b/src/Split/net7.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net7.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net7.0/TypeForwardeds.cs b/src/Split/net7.0/TypeForwardeds.cs index acbb458e..780f168c 100644 --- a/src/Split/net7.0/TypeForwardeds.cs +++ b/src/Split/net7.0/TypeForwardeds.cs @@ -5,6 +5,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] diff --git a/src/Split/net8.0/CryptographicOperationsExtensions.cs b/src/Split/net8.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net8.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net8.0/TypeForwardeds.cs b/src/Split/net8.0/TypeForwardeds.cs index d3b8dcb1..b511da9e 100644 --- a/src/Split/net8.0/TypeForwardeds.cs +++ b/src/Split/net8.0/TypeForwardeds.cs @@ -7,6 +7,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ExperimentalAttribute))] diff --git a/src/Split/net9.0/CryptographicOperationsExtensions.cs b/src/Split/net9.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..83fc0dc3 --- /dev/null +++ b/src/Split/net9.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,131 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net9.0/TypeForwardeds.cs b/src/Split/net9.0/TypeForwardeds.cs index dc347bc9..db89f0fa 100644 --- a/src/Split/net9.0/TypeForwardeds.cs +++ b/src/Split/net9.0/TypeForwardeds.cs @@ -8,6 +8,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] diff --git a/src/Split/netcoreapp2.0/CryptographicOperations.cs b/src/Split/netcoreapp2.0/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/netcoreapp2.0/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp2.1/TypeForwardeds.cs b/src/Split/netcoreapp2.1/TypeForwardeds.cs index 14b514da..e66754ff 100644 --- a/src/Split/netcoreapp2.1/TypeForwardeds.cs +++ b/src/Split/netcoreapp2.1/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp2.2/TypeForwardeds.cs b/src/Split/netcoreapp2.2/TypeForwardeds.cs index 14b514da..e66754ff 100644 --- a/src/Split/netcoreapp2.2/TypeForwardeds.cs +++ b/src/Split/netcoreapp2.2/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp3.0/TypeForwardeds.cs b/src/Split/netcoreapp3.0/TypeForwardeds.cs index d2422ae3..98847ca2 100644 --- a/src/Split/netcoreapp3.0/TypeForwardeds.cs +++ b/src/Split/netcoreapp3.0/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp3.1/TypeForwardeds.cs b/src/Split/netcoreapp3.1/TypeForwardeds.cs index d2422ae3..98847ca2 100644 --- a/src/Split/netcoreapp3.1/TypeForwardeds.cs +++ b/src/Split/netcoreapp3.1/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netstandard2.0/CryptographicOperations.cs b/src/Split/netstandard2.0/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/netstandard2.0/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs b/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netstandard2.1/TypeForwardeds.cs b/src/Split/netstandard2.1/TypeForwardeds.cs index f65793fe..2c58215a 100644 --- a/src/Split/netstandard2.1/TypeForwardeds.cs +++ b/src/Split/netstandard2.1/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/uap10.0/CryptographicOperations.cs b/src/Split/uap10.0/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/uap10.0/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Tests/CryptographicOperationsPolyfillTests.cs b/src/Tests/CryptographicOperationsPolyfillTests.cs new file mode 100644 index 00000000..1147cae9 --- /dev/null +++ b/src/Tests/CryptographicOperationsPolyfillTests.cs @@ -0,0 +1,255 @@ +using System.Security.Cryptography; + +public class CryptographicOperationsPolyfillTests +{ + static byte[] data = [1, 2, 3, 4, 5]; + static byte[] key = [10, 20, 30, 40, 50, 60]; + + static byte[] ExpectedSha256() + { + using var sha = SHA256.Create(); + return sha.ComputeHash(data); + } + + static byte[] ExpectedHmacSha256() + { + using var hmac = new HMACSHA256(key); + return hmac.ComputeHash(data); + } + + [Test] + public async Task HashData_ByteArray() + { + var actual = CryptographicOperations.HashData(HashAlgorithmName.SHA256, data); + await Assert.That(actual.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HashData_ByteArray_Null_Throws() => + await Assert.That(() => CryptographicOperations.HashData(HashAlgorithmName.SHA256, (byte[]) null!)) + .Throws(); + + [Test] + public async Task HashData_Stream() + { + using var stream = new MemoryStream(data); + var actual = CryptographicOperations.HashData(HashAlgorithmName.SHA256, stream); + await Assert.That(actual.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HmacData_ByteArray() + { + var actual = CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key, data); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task HmacData_Stream() + { + using var stream = new MemoryStream(data); + var actual = CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key, stream); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmac_ByteArray_Valid() + { + var mac = ExpectedHmacSha256(); + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, data, mac)).IsTrue(); + } + + [Test] + public async Task VerifyHmac_ByteArray_Tampered() + { + var mac = ExpectedHmacSha256(); + mac[0] ^= 0xFF; + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, data, mac)).IsFalse(); + } + + [Test] + public async Task VerifyHmac_Stream_Valid() + { + using var stream = new MemoryStream(data); + var mac = ExpectedHmacSha256(); + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, stream, mac)).IsTrue(); + } + +#if FeatureValueTask + [Test] + public async Task HashDataAsync_Stream() + { + using var stream = new MemoryStream(data); + var actual = await CryptographicOperations.HashDataAsync(HashAlgorithmName.SHA256, stream); + await Assert.That(actual.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HmacDataAsync_ByteArray_Stream() + { + using var stream = new MemoryStream(data); + var actual = await CryptographicOperations.HmacDataAsync(HashAlgorithmName.SHA256, key, stream); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmacAsync_ByteArray_Stream() + { + using var stream = new MemoryStream(data); + var mac = ExpectedHmacSha256(); + var result = await CryptographicOperations.VerifyHmacAsync(HashAlgorithmName.SHA256, key, stream, mac); + await Assert.That(result).IsTrue(); + } +#endif + +#if FeatureMemory + [Test] + public async Task FixedTimeEquals_Equal() + { + var left = new byte[] {1, 2, 3, 4}; + var right = new byte[] {1, 2, 3, 4}; + await Assert.That(CryptographicOperations.FixedTimeEquals(left, right)).IsTrue(); + } + + [Test] + public async Task FixedTimeEquals_NotEqual() + { + var left = new byte[] {1, 2, 3, 4}; + var right = new byte[] {1, 2, 3, 5}; + await Assert.That(CryptographicOperations.FixedTimeEquals(left, right)).IsFalse(); + } + + [Test] + public async Task FixedTimeEquals_DifferentLength() + { + var left = new byte[] {1, 2, 3, 4}; + var right = new byte[] {1, 2, 3}; + await Assert.That(CryptographicOperations.FixedTimeEquals(left, right)).IsFalse(); + } + + [Test] + public async Task FixedTimeEquals_SingleByte_AllMatch() + { + var source = new byte[] {7, 7, 7, 7}; + await Assert.That(CryptographicOperations.FixedTimeEquals(source, (byte) 7)).IsTrue(); + } + + [Test] + public async Task FixedTimeEquals_SingleByte_Mismatch() + { + var source = new byte[] {7, 7, 8, 7}; + await Assert.That(CryptographicOperations.FixedTimeEquals(source, (byte) 7)).IsFalse(); + } + + [Test] + public async Task ZeroMemory_Clears() + { + var buffer = new byte[] {1, 2, 3, 4, 5}; + CryptographicOperations.ZeroMemory(buffer); + await Assert.That(buffer.All(_ => _ == 0)).IsTrue(); + } + + [Test] + public async Task HashData_Span() + { + var destination = new byte[32]; + var written = CryptographicOperations.HashData(HashAlgorithmName.SHA256, data.AsSpan(), destination); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HashData_Span_DestinationTooShort() + { + var exception = await Assert.That(() => + { + var destination = new byte[8]; + CryptographicOperations.HashData(HashAlgorithmName.SHA256, data.AsSpan(), destination); + }) + .Throws(); + await Assert.That(exception!.Message.StartsWith("Destination is too short.")).IsTrue(); + } + + [Test] + public async Task TryHashData_Valid() + { + var destination = new byte[32]; + var result = CryptographicOperations.TryHashData(HashAlgorithmName.SHA256, data.AsSpan(), destination, out var written); + await Assert.That(result).IsTrue(); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task TryHashData_TooShort() + { + var destination = new byte[8]; + var result = CryptographicOperations.TryHashData(HashAlgorithmName.SHA256, data.AsSpan(), destination, out var written); + await Assert.That(result).IsFalse(); + await Assert.That(written).IsEqualTo(0); + } + + [Test] + public async Task HmacData_Span() + { + var actual = CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan()); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task TryHmacData_Valid() + { + var destination = new byte[32]; + var result = CryptographicOperations.TryHmacData(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), destination, out var written); + await Assert.That(result).IsTrue(); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmac_Span_Valid() + { + var mac = ExpectedHmacSha256(); + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), mac.AsSpan())).IsTrue(); + } + + [Test] + public async Task VerifyHmac_Span_Tampered() + { + var mac = ExpectedHmacSha256(); + mac[0] ^= 0xFF; + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), mac.AsSpan())).IsFalse(); + } + +#if FeatureValueTask + [Test] + public async Task HashDataAsync_Memory() + { + using var stream = new MemoryStream(data); + Memory destination = new byte[32]; + var written = await CryptographicOperations.HashDataAsync(HashAlgorithmName.SHA256, stream, destination); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.ToArray().SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HmacDataAsync_Memory() + { + using var stream = new MemoryStream(data); + ReadOnlyMemory keyMemory = key; + var actual = await CryptographicOperations.HmacDataAsync(HashAlgorithmName.SHA256, keyMemory, stream); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmacAsync_Memory() + { + using var stream = new MemoryStream(data); + ReadOnlyMemory keyMemory = key; + ReadOnlyMemory macMemory = ExpectedHmacSha256(); + var result = await CryptographicOperations.VerifyHmacAsync(HashAlgorithmName.SHA256, keyMemory, stream, macMemory); + await Assert.That(result).IsTrue(); + } +#endif +#endif +}