From 6ae5b380082dbbb98a7acfaabef10894c08b1123 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 16 Jul 2020 12:21:09 -0400 Subject: [PATCH] Reduce allocations and P/Invokes in one-shot hashes --- .../Windows/BCrypt/Interop.BCryptHash.cs | 16 ++++ .../HashProviderDispenser.Unix.cs | 16 +++- .../HashProviderDispenser.Windows.cs | 93 ++++++++++++++++++- ...em.Security.Cryptography.Algorithms.csproj | 2 + 4 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptHash.cs diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptHash.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptHash.cs new file mode 100644 index 0000000000000..f7384436f6943 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptHash.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +internal partial class Interop +{ + internal partial class BCrypt + { + [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)] + internal static unsafe extern NTSTATUS BCryptHash(SafeBCryptAlgorithmHandle hAlgorithm, byte* pbSecret, int cbSecret, byte* pbInput, int cbInput, byte* pbOutput, int cbOutput); + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs index 8d496d18bf678..a67223334986f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs @@ -11,6 +11,12 @@ namespace Internal.Cryptography { internal static partial class HashProviderDispenser { + private static volatile IntPtr s_evpMd5; + private static volatile IntPtr s_evpSha1; + private static volatile IntPtr s_evpSha256; + private static volatile IntPtr s_evpSha384; + private static volatile IntPtr s_evpSha512; + public static HashProvider CreateHashProvider(string hashAlgorithmId) { IntPtr evpType = HashAlgorithmToEvp(hashAlgorithmId); @@ -24,11 +30,11 @@ public static HashProvider CreateMacProvider(string hashAlgorithmId, ReadOnlySpa } private static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch { - HashAlgorithmNames.SHA1 => Interop.Crypto.EvpSha1(), - HashAlgorithmNames.SHA256 => Interop.Crypto.EvpSha256(), - HashAlgorithmNames.SHA384 => Interop.Crypto.EvpSha384(), - HashAlgorithmNames.SHA512 => Interop.Crypto.EvpSha512(), - HashAlgorithmNames.MD5 => Interop.Crypto.EvpMd5(), + HashAlgorithmNames.SHA1 => s_evpSha1 == IntPtr.Zero ? (s_evpSha1 = Interop.Crypto.EvpSha1()) : s_evpSha1, + HashAlgorithmNames.SHA256 => s_evpSha256 == IntPtr.Zero ? (s_evpSha256 = Interop.Crypto.EvpSha256()) : s_evpSha256, + HashAlgorithmNames.SHA384 => s_evpSha384 == IntPtr.Zero ? (s_evpSha384 = Interop.Crypto.EvpSha384()) : s_evpSha384, + HashAlgorithmNames.SHA512 => s_evpSha512 == IntPtr.Zero ? (s_evpSha512 = Interop.Crypto.EvpSha512()) : s_evpSha512, + HashAlgorithmNames.MD5 => s_evpMd5 == IntPtr.Zero ? (s_evpMd5 = Interop.Crypto.EvpMd5()) : s_evpMd5, _ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)) }; diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Windows.cs index e685ab4ca20a3..8592dd1454762 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Windows.cs @@ -4,6 +4,11 @@ using System; using System.Diagnostics; using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; +using NTSTATUS = Interop.BCrypt.NTSTATUS; +using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags; +using BCryptCreateHashFlags = Interop.BCrypt.BCryptCreateHashFlags; +using BCryptAlgorithmCache = Interop.BCrypt.BCryptAlgorithmCache; namespace Internal.Cryptography { @@ -24,12 +29,94 @@ public static HashProvider CreateMacProvider(string hashAlgorithmId, ReadOnlySpa public static class OneShotHashProvider { + private static volatile bool s_useCompatOneShot; + public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { - using (HashProviderCng hashProvider = new HashProviderCng(hashAlgorithmId, null)) + // Shared handle, no using or dispose. + SafeBCryptAlgorithmHandle cachedAlgorithmHandle = BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle( + hashAlgorithmId, + BCryptOpenAlgorithmProviderFlags.None); + + int hashSize; + + NTSTATUS ntStatus = Interop.BCrypt.BCryptGetProperty( + cachedAlgorithmHandle, + Interop.BCrypt.BCryptPropertyStrings.BCRYPT_HASH_LENGTH, + &hashSize, + sizeof(int), + out _, + 0); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } + + if (destination.Length < hashSize) { - hashProvider.AppendHashData(source); - return hashProvider.FinalizeHashAndReset(destination); + throw new CryptographicException(); + } + + if (!s_useCompatOneShot) + { + try + { + fixed (byte* pSource = source) + fixed (byte* pDestination = destination) + { + ntStatus = Interop.BCrypt.BCryptHash(cachedAlgorithmHandle, null, 0, pSource, source.Length, pDestination, hashSize); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } + } + + return hashSize; + } + catch (EntryPointNotFoundException) + { + s_useCompatOneShot = true; + } + } + + Debug.Assert(s_useCompatOneShot); + HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, source, destination); + + return hashSize; + } + + private static void HashUpdateAndFinish( + SafeBCryptAlgorithmHandle algHandle, + int hashSize, + ReadOnlySpan source, + Span destination) + { + NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash( + algHandle, + out SafeBCryptHashHandle hHash, + IntPtr.Zero, + 0, + default, + 0, + BCryptCreateHashFlags.None); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } + + using (hHash) + { + ntStatus = Interop.BCrypt.BCryptHashData(hHash, source, source.Length, 0); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } + + Interop.BCrypt.BCryptFinishHash(hHash, destination, hashSize, 0); } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index edad8a8284328..3484688c02d7f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -323,6 +323,8 @@ Link="Common\Interop\Windows\BCrypt\Interop.BCryptDestroyHash.cs" /> +