From 3188dcd23a34304a45d310ae867f6fe90dbe8ce2 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 4 Jun 2021 15:57:12 -0400 Subject: [PATCH] Add static one-shot methods for HMAC algorithms --- .../Interop.Hmac.cs | 25 +++ .../Interop.Hmac.cs | 11 ++ .../Interop.Hmac.cs | 25 +++ .../BCrypt/Interop.BCryptAlgPseudoHandle.cs | 5 + .../tests/TestUtilities/System/AssertExtensions.cs | 19 +- .../pal_hmac.c | 26 +++ .../pal_hmac.h | 7 + .../entrypoints.c | 1 + .../pal_hmac.c | 27 ++- .../pal_hmac.h | 14 ++ .../entrypoints.c | 1 + .../opensslshim.h | 2 + .../System.Security.Cryptography.Native/pal_hmac.c | 33 ++++ .../System.Security.Cryptography.Native/pal_hmac.h | 12 ++ .../ref/System.Security.Cryptography.Algorithms.cs | 20 +++ .../Cryptography/HashProviderDispenser.Browser.cs | 9 + .../Cryptography/HashProviderDispenser.OSX.cs | 34 ++++ .../Cryptography/HashProviderDispenser.Unix.cs | 22 +++ .../Cryptography/HashProviderDispenser.Windows.cs | 78 +++++++-- .../src/System/Security/Cryptography/HMACMD5.cs | 88 ++++++++++ .../src/System/Security/Cryptography/HMACSHA1.cs | 88 ++++++++++ .../src/System/Security/Cryptography/HMACSHA256.cs | 90 +++++++++- .../src/System/Security/Cryptography/HMACSHA384.cs | 90 +++++++++- .../src/System/Security/Cryptography/HMACSHA512.cs | 89 +++++++++- .../tests/HmacMD5Tests.cs | 51 ++++-- .../tests/HmacSha1Tests.cs | 51 ++++-- .../tests/HmacSha256Tests.cs | 52 ++++-- .../tests/HmacSha384Tests.cs | 52 ++++-- .../tests/HmacSha512Tests.cs | 52 ++++-- .../tests/HmacTests.cs | 194 ++++++++++++++++++--- .../tests/Rfc2202HmacTests.cs | 4 +- .../tests/Rfc4231HmacTests.cs | 4 +- 32 files changed, 1151 insertions(+), 125 deletions(-) diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Hmac.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Hmac.cs index f332946..38ed534 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Hmac.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Hmac.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -29,5 +30,29 @@ internal static partial class Interop [DllImport(Libraries.AndroidCryptoNative, EntryPoint = "CryptoNative_HmacCurrent")] internal static extern int HmacCurrent(SafeHmacCtxHandle ctx, ref byte data, ref int len); + + [DllImport(Libraries.AndroidCryptoNative, EntryPoint = "CryptoNative_HmacOneShot")] + private static unsafe extern int HmacOneShot(IntPtr type, byte* key, int keySize, byte* source, int sourceSize, byte* md, ref int mdSize); + + internal static unsafe int HmacOneShot(IntPtr type, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + int size = destination.Length; + const int Success = 1; + + fixed (byte* pKey = key) + fixed (byte* pSource = source) + fixed (byte* pDestination = destination) + { + int result = HmacOneShot(type, pKey, key.Length, pSource, source.Length, pDestination, ref size); + + if (result != Success) + { + Debug.Assert(result == 0); + throw CreateOpenSslCryptographicException(); + } + } + + return size; + } } } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs index 9898f58..7cedae4 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs @@ -35,6 +35,17 @@ internal static partial class Interop [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_HmacCurrent")] private static extern int HmacCurrent(SafeHmacHandle ctx, ref byte pbOutput, int cbOutput); + + [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_HmacOneShot")] + internal static unsafe extern int HmacOneShot( + PAL_HashAlgorithm algorithm, + byte* pKey, + int cbKey, + byte* pData, + int cbData, + byte* pOutput, + int cbOutput, + out int cbDigest); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs index 5638d20..6074351 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -29,5 +30,29 @@ internal static partial class Interop [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_HmacCurrent")] internal static extern int HmacCurrent(SafeHmacCtxHandle ctx, ref byte data, ref int len); + + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_HmacOneShot")] + private static unsafe extern int HmacOneShot(IntPtr type, byte* key, int keySize, byte* source, int sourceSize, byte* md, ref int mdSize); + + internal static unsafe int HmacOneShot(IntPtr type, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + int size = destination.Length; + const int Success = 1; + + fixed (byte* pKey = key) + fixed (byte* pSource = source) + fixed (byte* pDestination = destination) + { + int result = HmacOneShot(type, pKey, key.Length, pSource, source.Length, pDestination, ref size); + + if (result != Success) + { + Debug.Assert(result == 0); + throw CreateOpenSslCryptographicException(); + } + } + + return size; + } } } diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs index 9886f2b..433e6f5 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs @@ -17,6 +17,11 @@ internal static partial class Interop BCRYPT_SHA256_ALG_HANDLE = 0x00000041, BCRYPT_SHA384_ALG_HANDLE = 0x00000051, BCRYPT_SHA512_ALG_HANDLE = 0x00000061, + BCRYPT_HMAC_MD5_ALG_HANDLE = 0x00000091, + BCRYPT_HMAC_SHA1_ALG_HANDLE = 0x000000a1, + BCRYPT_HMAC_SHA256_ALG_HANDLE = 0x000000b1, + BCRYPT_HMAC_SHA384_ALG_HANDLE = 0x000000c1, + BCRYPT_HMAC_SHA512_ALG_HANDLE = 0x000000d1, BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331, } diff --git a/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs b/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs index 940c4de..f2fb788 100644 --- a/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs +++ b/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs @@ -403,7 +403,7 @@ namespace System { actualItemCountMapping[actualItem] = new ItemCount(1, 1); } - + actualCount++; } @@ -431,7 +431,7 @@ namespace System countInfo.Remain--; } } - + /// /// Validates that the actual span is equal to the expected span. /// If this fails, determine where the differences are and create an exception with that information. @@ -475,6 +475,19 @@ namespace System } } + public static void FilledWith(T expected, ReadOnlySpan actual) + { + EqualityComparer comparer = EqualityComparer.Default; + + for (int i = 0; i < actual.Length; i++) + { + if (!comparer.Equals(expected, actual[i])) + { + throw new XunitException($"Expected {expected?.ToString() ?? "null"} at position {i}"); + } + } + } + public static void SequenceEqual(Span expected, Span actual) where T : IEquatable => SequenceEqual((ReadOnlySpan)expected, (ReadOnlySpan)actual); public static void SequenceEqual(T[] expected, T[] actual) where T : IEquatable => SequenceEqual(expected.AsSpan(), actual.AsSpan()); @@ -556,7 +569,7 @@ namespace System Assert.Equal(expectedParamName, exception.ParamName); return exception; } - + private class ItemCount { public int Original { get; set; } diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.c index 4fd381d..7632eed 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.c @@ -136,3 +136,29 @@ void CryptoNative_HmacDestroy(jobject ctx) { ReleaseGRef(GetJNIEnv(), ctx); } + +int32_t CryptoNative_HmacOneShot(intptr_t type, + uint8_t* key, + int32_t keyLen, + uint8_t* source, + int32_t sourceLen, + uint8_t* md, + int32_t* mdSize) +{ + jobject hmacCtx = CryptoNative_HmacCreate(key, keyLen, type); + + if (hmacCtx == FAIL) + return FAIL; + + int32_t ret = sourceLen == 0 ? SUCCESS : CryptoNative_HmacUpdate(hmacCtx, source, sourceLen); + + if (ret != SUCCESS) + { + CryptoNative_HmacDestroy(hmacCtx); + return ret; + } + + ret = CryptoNative_HmacFinal(hmacCtx, md, mdSize); + CryptoNative_HmacDestroy(hmacCtx); + return ret; +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.h index e44aa5d..671fc6f 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_hmac.h @@ -11,3 +11,10 @@ PALEXPORT int32_t CryptoNative_HmacUpdate(jobject ctx, uint8_t* data, int32_t le PALEXPORT int32_t CryptoNative_HmacFinal(jobject ctx, uint8_t* md, int32_t* len); PALEXPORT int32_t CryptoNative_HmacCurrent(jobject ctx, uint8_t* md, int32_t* len); PALEXPORT void CryptoNative_HmacDestroy(jobject ctx); +PALEXPORT int32_t CryptoNative_HmacOneShot(intptr_t type, + uint8_t* key, + int32_t keyLen, + uint8_t* source, + int32_t sourceLen, + uint8_t* md, + int32_t* mdSize); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c index 90dd2ab..ff4df6f 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -38,6 +38,7 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_HmacUpdate) DllImportEntry(AppleCryptoNative_HmacFinal) DllImportEntry(AppleCryptoNative_HmacCurrent) + DllImportEntry(AppleCryptoNative_HmacOneShot) DllImportEntry(AppleCryptoNative_SecKeychainItemCopyKeychain) DllImportEntry(AppleCryptoNative_SecKeychainCreate) DllImportEntry(AppleCryptoNative_SecKeychainDelete) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.c index 25632ea..4ffbc2f 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.c @@ -116,7 +116,32 @@ int32_t AppleCryptoNative_HmacCurrent(const HmacCtx* ctx, uint8_t* pbOutput) { if (ctx == NULL || pbOutput == NULL) return 0; - + HmacCtx dup = *ctx; return AppleCryptoNative_HmacFinal(&dup, pbOutput); } + +int32_t AppleCryptoNative_HmacOneShot(PAL_HashAlgorithm algorithm, + const uint8_t* pKey, + int32_t cbKey, + const uint8_t* pBuf, + int32_t cbBuf, + uint8_t* pOutput, + int32_t cbOutput, + int32_t* pcbDigest) +{ + if (pOutput == NULL || cbOutput <= 0 || pcbDigest == NULL) + return -1; + + CCHmacAlgorithm ccAlgorithm = PalAlgorithmToAppleAlgorithm(algorithm); + *pcbDigest = GetHmacOutputSize(algorithm); + + if (ccAlgorithm == UINT_MAX) + return -1; + + if (cbOutput < *pcbDigest) + return -1; + + CCHmac(ccAlgorithm, pKey, cbKey, pBuf, cbBuf, pOutput); + return 1; +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.h index 86d05cc..2889165 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_hmac.h @@ -51,3 +51,17 @@ Computes the HMAC of the accumulated data in ctx without resetting the state. Returns 1 on success, 0 on error. */ PALEXPORT int32_t AppleCryptoNative_HmacCurrent(const HmacCtx* ctx, uint8_t* pbOutput); + +/* +Computes the HMAC of data with a key in to the pOutput buffer in one step. + +Return 1 on success, 0 on error, and negative values for invalid input. +*/ +PALEXPORT int32_t AppleCryptoNative_HmacOneShot(PAL_HashAlgorithm algorithm, + const uint8_t* pKey, + int32_t cbKey, + const uint8_t* pBuf, + int32_t cbBuf, + uint8_t* pOutput, + int32_t cbOutput, + int32_t* pcbDigest); diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c index 3ab9ccf..9969217 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c @@ -202,6 +202,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_HmacCurrent) DllImportEntry(CryptoNative_HmacDestroy) DllImportEntry(CryptoNative_HmacFinal) + DllImportEntry(CryptoNative_HmacOneShot) DllImportEntry(CryptoNative_HmacReset) DllImportEntry(CryptoNative_HmacUpdate) DllImportEntry(CryptoNative_LookupFriendlyNameByOid) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index 8bf1bef..09d8351 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -362,6 +362,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(EVP_sha512) \ REQUIRED_FUNCTION(EXTENDED_KEY_USAGE_free) \ REQUIRED_FUNCTION(GENERAL_NAMES_free) \ + REQUIRED_FUNCTION(HMAC) \ LEGACY_FUNCTION(HMAC_CTX_cleanup) \ REQUIRED_FUNCTION(HMAC_CTX_copy) \ FALLBACK_FUNCTION(HMAC_CTX_free) \ @@ -797,6 +798,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_sha512 EVP_sha512_ptr #define EXTENDED_KEY_USAGE_free EXTENDED_KEY_USAGE_free_ptr #define GENERAL_NAMES_free GENERAL_NAMES_free_ptr +#define HMAC HMAC_ptr #define HMAC_CTX_cleanup HMAC_CTX_cleanup_ptr #define HMAC_CTX_copy HMAC_CTX_copy_ptr #define HMAC_CTX_free HMAC_CTX_free_ptr diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.c index 8871eac..96b0085 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.c @@ -130,3 +130,36 @@ int32_t CryptoNative_HmacCurrent(const HMAC_CTX* ctx, uint8_t* md, int32_t* len) return 0; } + +int32_t CryptoNative_HmacOneShot(const EVP_MD* type, + const uint8_t* key, + int32_t keySize, + const uint8_t* source, + int32_t sourceSize, + uint8_t* md, + int32_t* mdSize) +{ + assert(mdSize != NULL && type != NULL && md != NULL && mdSize != NULL); + assert(keySize >= 0 && *mdSize >= 0); + assert(key != NULL || keySize == 0); + assert(source != NULL || sourceSize == 0); + + uint8_t empty = 0; + + if (key == NULL) + { + if (keySize != 0) + { + return -1; + } + + key = ∅ + } + + unsigned int unsignedSource = Int32ToUint32(sourceSize); + unsigned int unsignedSize = Int32ToUint32(*mdSize); + unsigned char* result = HMAC(type, key, keySize, source, unsignedSource, md, &unsignedSize); + *mdSize = Uint32ToInt32(unsignedSize); + + return result == NULL ? 0 : 1; +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.h index e310c05..9f89895 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_hmac.h @@ -69,3 +69,15 @@ PALEXPORT int32_t CryptoNative_HmacFinal(HMAC_CTX* ctx, uint8_t* md, int32_t* le * Returns 1 for success or 0 for failure. */ PALEXPORT int32_t CryptoNative_HmacCurrent(const HMAC_CTX* ctx, uint8_t* md, int32_t* len); + +/** + * Computes the HMAC of data using a key in a single operation. + * Returns -1 on invalid input, 0 on failure, and 1 on success. + */ +PALEXPORT int32_t CryptoNative_HmacOneShot(const EVP_MD* type, + const uint8_t* key, + int32_t keySize, + const uint8_t* source, + int32_t sourceSize, + uint8_t* md, + int32_t* mdSize); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs index a12fae4..3239668 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs @@ -469,8 +469,12 @@ namespace System.Security.Cryptography protected override void Dispose(bool disposing) { } protected override void HashCore(byte[] rgb, int ib, int cb) { } protected override void HashCore(System.ReadOnlySpan source) { } + public static byte[] HashData(byte[] key, byte[] source) { throw null; } + public static byte[] HashData(System.ReadOnlySpan key, System.ReadOnlySpan source) { throw null; } + public static int HashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination) { throw null; } protected override byte[] HashFinal() { throw null; } public override void Initialize() { } + public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -484,8 +488,12 @@ namespace System.Security.Cryptography protected override void Dispose(bool disposing) { } protected override void HashCore(byte[] rgb, int ib, int cb) { } protected override void HashCore(System.ReadOnlySpan source) { } + public static byte[] HashData(byte[] key, byte[] source) { throw null; } + public static byte[] HashData(System.ReadOnlySpan key, System.ReadOnlySpan source) { throw null; } + public static int HashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination) { throw null; } protected override byte[] HashFinal() { throw null; } public override void Initialize() { } + public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -497,8 +505,12 @@ namespace System.Security.Cryptography protected override void Dispose(bool disposing) { } protected override void HashCore(byte[] rgb, int ib, int cb) { } protected override void HashCore(System.ReadOnlySpan source) { } + public static byte[] HashData(byte[] key, byte[] source) { throw null; } + public static byte[] HashData(System.ReadOnlySpan key, System.ReadOnlySpan source) { throw null; } + public static int HashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination) { throw null; } protected override byte[] HashFinal() { throw null; } public override void Initialize() { } + public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -511,8 +523,12 @@ namespace System.Security.Cryptography protected override void Dispose(bool disposing) { } protected override void HashCore(byte[] rgb, int ib, int cb) { } protected override void HashCore(System.ReadOnlySpan source) { } + public static byte[] HashData(byte[] key, byte[] source) { throw null; } + public static byte[] HashData(System.ReadOnlySpan key, System.ReadOnlySpan source) { throw null; } + public static int HashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination) { throw null; } protected override byte[] HashFinal() { throw null; } public override void Initialize() { } + public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -525,8 +541,12 @@ namespace System.Security.Cryptography protected override void Dispose(bool disposing) { } protected override void HashCore(byte[] rgb, int ib, int cb) { } protected override void HashCore(System.ReadOnlySpan source) { } + public static byte[] HashData(byte[] key, byte[] source) { throw null; } + public static byte[] HashData(System.ReadOnlySpan key, System.ReadOnlySpan source) { throw null; } + public static int HashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination) { throw null; } protected override byte[] HashFinal() { throw null; } public override void Initialize() { } + public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } } public sealed partial class IncrementalHash : System.IDisposable diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Browser.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Browser.cs index 17d2292..cc0dc6f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Browser.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Browser.cs @@ -26,6 +26,15 @@ namespace Internal.Cryptography public static class OneShotHashProvider { + public static unsafe int MacData( + string hashAlgorithmId, + ReadOnlySpan key, + ReadOnlySpan source, + Span destination) + { + throw new PlatformNotSupportedException(SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported); + } + public static int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { HashProvider provider = HashProviderDispenser.CreateHashProvider(hashAlgorithmId); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.OSX.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.OSX.cs index bb14bc8..3bc72d0 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.OSX.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.OSX.cs @@ -33,6 +33,40 @@ namespace Internal.Cryptography internal static class OneShotHashProvider { + public static unsafe int MacData( + string hashAlgorithmId, + ReadOnlySpan key, + ReadOnlySpan source, + Span destination) + { + Interop.AppleCrypto.PAL_HashAlgorithm algorithm = HashAlgorithmToPal(hashAlgorithmId); + + fixed (byte* pKey = key) + fixed (byte* pSource = source) + fixed (byte* pDestination = destination) + { + int ret = Interop.AppleCrypto.HmacOneShot( + algorithm, + pKey, + key.Length, + pSource, + source.Length, + pDestination, + destination.Length, + out int digestSize); + + if (ret != 1) + { + Debug.Fail($"MacData return value {ret} was not 1"); + throw new CryptographicException(); + } + + Debug.Assert(digestSize <= destination.Length); + + return digestSize; + } + } + public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { Interop.AppleCrypto.PAL_HashAlgorithm algorithm = HashAlgorithmToPal(hashAlgorithmId); 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 af984e5..3a7b797 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 @@ -25,6 +25,28 @@ namespace Internal.Cryptography internal static class OneShotHashProvider { + public static int MacData( + string hashAlgorithmId, + ReadOnlySpan key, + ReadOnlySpan source, + Span destination) + { + IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(hashAlgorithmId); + Debug.Assert(evpType != IntPtr.Zero); + + int hashSize = Interop.Crypto.EvpMdSize(evpType); + + if (hashSize <= 0 || destination.Length < hashSize) + { + Debug.Fail("Destination length or hash size not valid."); + throw new CryptographicException(); + } + + int written = Interop.Crypto.HmacOneShot(evpType, key, source, destination); + Debug.Assert(written == hashSize); + return written; + } + public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { IntPtr evpType = Interop.Crypto.HashAlgorithmToEvp(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 4fa9607..88dd4da 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 @@ -30,14 +30,48 @@ namespace Internal.Cryptography public static class OneShotHashProvider { + public static unsafe int MacData( + string hashAlgorithmId, + ReadOnlySpan key, + ReadOnlySpan source, + Span destination) + { + int hashSize; // in bytes + + // Use a pseudo-handle if available. + if (Interop.BCrypt.PseudoHandlesSupported) + { + HashDataUsingPseudoHandle(hashAlgorithmId, source, key, isHmac: true, destination, out hashSize); + return hashSize; + } + else + { + // Pseudo-handle not available. Fall back to a shared handle with no using or dispose. + SafeBCryptAlgorithmHandle cachedAlgorithmHandle = BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle( + hashAlgorithmId, + BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG, + out hashSize); + + if (destination.Length < hashSize) + { + Debug.Fail("Caller should have checked length."); + throw new CryptographicException(); + } + + HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, key, source, destination); + + return hashSize; + } + } + public static unsafe int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { int hashSize; // in bytes - // Try using a pseudo-handle if available. + // Use a pseudo-handle if available. if (Interop.BCrypt.PseudoHandlesSupported) { - HashDataUsingPseudoHandle(hashAlgorithmId, source, destination, out hashSize); + HashDataUsingPseudoHandle(hashAlgorithmId, source, key: default, isHmac : false, destination, out hashSize); return hashSize; } else @@ -54,42 +88,60 @@ namespace Internal.Cryptography throw new CryptographicException(); } - HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, source, destination); + HashUpdateAndFinish(cachedAlgorithmHandle, hashSize, key: default, source, destination); return hashSize; } } - private static unsafe void HashDataUsingPseudoHandle(string hashAlgorithmId, ReadOnlySpan source, Span destination, out int hashSize) + private static unsafe void HashDataUsingPseudoHandle( + string hashAlgorithmId, + ReadOnlySpan source, + ReadOnlySpan key, + bool isHmac, + Span destination, + out int hashSize) { hashSize = default; + Debug.Assert(isHmac ? true : key.IsEmpty); + Interop.BCrypt.BCryptAlgPseudoHandle algHandle; int digestSizeInBytes; if (hashAlgorithmId == HashAlgorithmNames.MD5) { - algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_MD5_ALG_HANDLE; + algHandle = isHmac ? + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_MD5_ALG_HANDLE : + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_MD5_ALG_HANDLE; digestSizeInBytes = 128 / 8; } else if (hashAlgorithmId == HashAlgorithmNames.SHA1) { - algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA1_ALG_HANDLE; + algHandle = isHmac ? + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA1_ALG_HANDLE : + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA1_ALG_HANDLE; digestSizeInBytes = 160 / 8; } else if (hashAlgorithmId == HashAlgorithmNames.SHA256) { - algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA256_ALG_HANDLE; + algHandle = isHmac ? + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA256_ALG_HANDLE : + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA256_ALG_HANDLE; digestSizeInBytes = 256 / 8; } else if (hashAlgorithmId == HashAlgorithmNames.SHA384) { - algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA384_ALG_HANDLE; + algHandle = isHmac ? + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA384_ALG_HANDLE : + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA384_ALG_HANDLE; digestSizeInBytes = 384 / 8; } else if (hashAlgorithmId == HashAlgorithmNames.SHA512) { - algHandle = Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA512_ALG_HANDLE; + algHandle = isHmac ? + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_HMAC_SHA512_ALG_HANDLE : + Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA512_ALG_HANDLE; digestSizeInBytes = 512 / 8; } else @@ -104,10 +156,11 @@ namespace Internal.Cryptography throw new CryptographicException(); } + fixed (byte* pKey = &MemoryMarshal.GetReference(key)) fixed (byte* pSrc = &MemoryMarshal.GetReference(source)) fixed (byte* pDest = &MemoryMarshal.GetReference(destination)) { - NTSTATUS ntStatus = Interop.BCrypt.BCryptHash((uint)algHandle, pbSecret: null, cbSecret: 0, pSrc, source.Length, pDest, digestSizeInBytes); + NTSTATUS ntStatus = Interop.BCrypt.BCryptHash((uint)algHandle, pKey, key.Length, pSrc, source.Length, pDest, digestSizeInBytes); if (ntStatus != NTSTATUS.STATUS_SUCCESS) { @@ -121,6 +174,7 @@ namespace Internal.Cryptography private static void HashUpdateAndFinish( SafeBCryptAlgorithmHandle algHandle, int hashSize, + ReadOnlySpan key, ReadOnlySpan source, Span destination) { @@ -129,8 +183,8 @@ namespace Internal.Cryptography out SafeBCryptHashHandle hHash, IntPtr.Zero, 0, - default, - 0, + key, + key.Length, BCryptCreateHashFlags.None); if (ntStatus != NTSTATUS.STATUS_SUCCESS) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACMD5.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACMD5.cs index ad65257..c66fcaa 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACMD5.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACMD5.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.Versioning; using Internal.Cryptography; @@ -14,6 +15,9 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("browser")] public class HMACMD5 : HMAC { + private const int HmacSizeBits = 128; + private const int HmacSizeBytes = HmacSizeBits / 8; + public HMACMD5() : this(RandomNumberGenerator.GetBytes(BlockSize)) { @@ -33,6 +37,7 @@ namespace System.Security.Cryptography // we just want to be explicit in all HMAC extended classes BlockSizeValue = BlockSize; HashSizeValue = _hMacCommon.HashSizeInBits; + Debug.Assert(HashSizeValue == HmacSizeBits); } public override byte[] Key @@ -67,6 +72,89 @@ namespace System.Security.Cryptography public override void Initialize() => _hMacCommon.Reset(); + /// + /// Computes the HMAC of data using the MD5 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + /// + /// or is . + /// + public static byte[] HashData(byte[] key, byte[] source) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return HashData(new ReadOnlySpan(key), new ReadOnlySpan(source)); + } + + /// + /// Computes the HMAC of data using the MD5 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + public static byte[] HashData(ReadOnlySpan key, ReadOnlySpan source) + { + byte[] buffer = new byte[HmacSizeBytes]; + + int written = HashData(key, source, buffer.AsSpan()); + Debug.Assert(written == buffer.Length); + + return buffer; + } + + /// + /// Computes the HMAC of data using the MD5 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// The total number of bytes written to . + /// + /// The buffer in is too small to hold the calculated hash + /// size. The MD5 algorithm always produces a 128-bit HMAC, or 16 bytes. + /// + public static int HashData(ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHashData(key, source, destination, out int bytesWritten)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Attempts to compute the HMAC of data using the MD5 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// + /// When this method returns, the total number of bytes written into . + /// + /// + /// if is too small to hold the + /// calculated hash, otherwise. + /// + public static bool TryHashData(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < HmacSizeBytes) + { + bytesWritten = 0; + return false; + } + + bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.MD5, key, source, destination); + Debug.Assert(bytesWritten == HmacSizeBytes); + + return true; + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA1.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA1.cs index 1d090d1..39e57c3 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA1.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA1.cs @@ -3,6 +3,7 @@ using Internal.Cryptography; using System.ComponentModel; +using System.Diagnostics; using System.Runtime.Versioning; namespace System.Security.Cryptography @@ -15,6 +16,9 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("browser")] public class HMACSHA1 : HMAC { + private const int HmacSizeBits = 160; + private const int HmacSizeBytes = HmacSizeBits / 8; + public HMACSHA1() : this(RandomNumberGenerator.GetBytes(BlockSize)) { @@ -34,6 +38,7 @@ namespace System.Security.Cryptography // we just want to be explicit in all HMAC extended classes BlockSizeValue = BlockSize; HashSizeValue = _hMacCommon.HashSizeInBits; + Debug.Assert(HashSizeValue == HmacSizeBits); } [EditorBrowsable(EditorBrowsableState.Never)] @@ -74,6 +79,89 @@ namespace System.Security.Cryptography public override void Initialize() => _hMacCommon.Reset(); + /// + /// Computes the HMAC of data using the SHA1 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + /// + /// or is . + /// + public static byte[] HashData(byte[] key, byte[] source) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return HashData(new ReadOnlySpan(key), new ReadOnlySpan(source)); + } + + /// + /// Computes the HMAC of data using the SHA1 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + public static byte[] HashData(ReadOnlySpan key, ReadOnlySpan source) + { + byte[] buffer = new byte[HmacSizeBytes]; + + int written = HashData(key, source, buffer.AsSpan()); + Debug.Assert(written == buffer.Length); + + return buffer; + } + + /// + /// Computes the HMAC of data using the SHA1 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// The total number of bytes written to . + /// + /// The buffer in is too small to hold the calculated hash + /// size. The SHA1 algorithm always produces a 160-bit HMAC, or 20 bytes. + /// + public static int HashData(ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHashData(key, source, destination, out int bytesWritten)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Attempts to compute the HMAC of data using the SHA1 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// + /// When this method returns, the total number of bytes written into . + /// + /// + /// if is too small to hold the + /// calculated hash, otherwise. + /// + public static bool TryHashData(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < HmacSizeBytes) + { + bytesWritten = 0; + return false; + } + + bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA1, key, source, destination); + Debug.Assert(bytesWritten == HmacSizeBytes); + + return true; + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA256.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA256.cs index 0bc1109..598c4c2 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA256.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA256.cs @@ -1,11 +1,10 @@ // 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 Internal.Cryptography; using System.Diagnostics; using System.Runtime.Versioning; using System.Security.Cryptography; -using Internal.Cryptography; namespace System.Security.Cryptography { @@ -17,6 +16,9 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("browser")] public class HMACSHA256 : HMAC { + private const int HmacSizeBits = 256; + private const int HmacSizeBytes = HmacSizeBits / 8; + public HMACSHA256() : this(RandomNumberGenerator.GetBytes(BlockSize)) { @@ -36,6 +38,7 @@ namespace System.Security.Cryptography // we just want to be explicit in all HMAC extended classes BlockSizeValue = BlockSize; HashSizeValue = _hMacCommon.HashSizeInBits; + Debug.Assert(HashSizeValue == HmacSizeBits); } public override byte[] Key @@ -70,6 +73,89 @@ namespace System.Security.Cryptography public override void Initialize() => _hMacCommon.Reset(); + /// + /// Computes the HMAC of data using the SHA256 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + /// + /// or is . + /// + public static byte[] HashData(byte[] key, byte[] source) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return HashData(new ReadOnlySpan(key), new ReadOnlySpan(source)); + } + + /// + /// Computes the HMAC of data using the SHA256 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + public static byte[] HashData(ReadOnlySpan key, ReadOnlySpan source) + { + byte[] buffer = new byte[HmacSizeBytes]; + + int written = HashData(key, source, buffer.AsSpan()); + Debug.Assert(written == buffer.Length); + + return buffer; + } + + /// + /// Computes the HMAC of data using the SHA256 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// The total number of bytes written to . + /// + /// The buffer in is too small to hold the calculated hash + /// size. The SHA256 algorithm always produces a 256-bit HMAC, or 32 bytes. + /// + public static int HashData(ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHashData(key, source, destination, out int bytesWritten)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Attempts to compute the HMAC of data using the SHA256 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// + /// When this method returns, the total number of bytes written into . + /// + /// + /// if is too small to hold the + /// calculated hash, otherwise. + /// + public static bool TryHashData(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < HmacSizeBytes) + { + bytesWritten = 0; + return false; + } + + bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA256, key, source, destination); + Debug.Assert(bytesWritten == HmacSizeBytes); + + return true; + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA384.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA384.cs index 05548b0..bf16716 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA384.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA384.cs @@ -1,11 +1,10 @@ // 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 Internal.Cryptography; using System.Diagnostics; using System.Runtime.Versioning; using System.Security.Cryptography; -using Internal.Cryptography; namespace System.Security.Cryptography { @@ -17,6 +16,9 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("browser")] public class HMACSHA384 : HMAC { + private const int HmacSizeBits = 384; + private const int HmacSizeBytes = HmacSizeBits / 8; + public HMACSHA384() : this(RandomNumberGenerator.GetBytes(BlockSize)) { @@ -35,6 +37,7 @@ namespace System.Security.Cryptography // change the default value of BlockSizeValue to 128 instead of 64 BlockSizeValue = BlockSize; HashSizeValue = _hMacCommon.HashSizeInBits; + Debug.Assert(HashSizeValue == HmacSizeBits); } public bool ProduceLegacyHmacValues @@ -86,6 +89,89 @@ namespace System.Security.Cryptography public override void Initialize() => _hMacCommon.Reset(); + /// + /// Computes the HMAC of data using the SHA384 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + /// + /// or is . + /// + public static byte[] HashData(byte[] key, byte[] source) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return HashData(new ReadOnlySpan(key), new ReadOnlySpan(source)); + } + + /// + /// Computes the HMAC of data using the SHA384 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + public static byte[] HashData(ReadOnlySpan key, ReadOnlySpan source) + { + byte[] buffer = new byte[HmacSizeBytes]; + + int written = HashData(key, source, buffer.AsSpan()); + Debug.Assert(written == buffer.Length); + + return buffer; + } + + /// + /// Computes the HMAC of data using the SHA384 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// The total number of bytes written to . + /// + /// The buffer in is too small to hold the calculated hash + /// size. The SHA384 algorithm always produces a 384-bit HMAC, or 48 bytes. + /// + public static int HashData(ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHashData(key, source, destination, out int bytesWritten)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Attempts to compute the HMAC of data using the SHA384 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// + /// When this method returns, the total number of bytes written into . + /// + /// + /// if is too small to hold the + /// calculated hash, otherwise. + /// + public static bool TryHashData(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < HmacSizeBytes) + { + bytesWritten = 0; + return false; + } + + bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA384, key, source, destination); + Debug.Assert(bytesWritten == HmacSizeBytes); + + return true; + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA512.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA512.cs index 556dd54..9b25cdc 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA512.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/HMACSHA512.cs @@ -1,11 +1,10 @@ // 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 Internal.Cryptography; using System.Diagnostics; using System.Runtime.Versioning; using System.Security.Cryptography; -using Internal.Cryptography; namespace System.Security.Cryptography { @@ -17,6 +16,9 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("browser")] public class HMACSHA512 : HMAC { + private const int HmacSizeBits = 512; + private const int HmacSizeBytes = HmacSizeBits / 8; + public HMACSHA512() : this(RandomNumberGenerator.GetBytes(BlockSize)) { @@ -84,6 +86,89 @@ namespace System.Security.Cryptography public override void Initialize() => _hMacCommon.Reset(); + /// + /// Computes the HMAC of data using the SHA512 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + /// + /// or is . + /// + public static byte[] HashData(byte[] key, byte[] source) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return HashData(new ReadOnlySpan(key), new ReadOnlySpan(source)); + } + + /// + /// Computes the HMAC of data using the SHA512 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC of the data. + public static byte[] HashData(ReadOnlySpan key, ReadOnlySpan source) + { + byte[] buffer = new byte[HmacSizeBytes]; + + int written = HashData(key, source, buffer.AsSpan()); + Debug.Assert(written == buffer.Length); + + return buffer; + } + + /// + /// Computes the HMAC of data using the SHA512 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// The total number of bytes written to . + /// + /// The buffer in is too small to hold the calculated hash + /// size. The SHA512 algorithm always produces a 512-bit HMAC, or 64 bytes. + /// + public static int HashData(ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHashData(key, source, destination, out int bytesWritten)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Attempts to compute the HMAC of data using the SHA512 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The buffer to receive the HMAC value. + /// + /// When this method returns, the total number of bytes written into . + /// + /// + /// if is too small to hold the + /// calculated hash, otherwise. + /// + public static bool TryHashData(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < HmacSizeBytes) + { + bytesWritten = 0; + return false; + } + + bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(HashAlgorithmNames.SHA512, key, source, destination); + Debug.Assert(bytesWritten == HmacSizeBytes); + + return true; + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacMD5Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacMD5Tests.cs index c3cc9ea..0c6cb58 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacMD5Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacMD5Tests.cs @@ -21,63 +21,80 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests ByteUtils.RepeatByte(0xaa, 80), }; - public HmacMD5Tests() - : base(s_testKeys2202) + private static readonly byte[][] s_testMacs2202 = { - } + null, + ByteUtils.HexToByteArray("9294727a3638bb1c13f48ef8158bfc9d"), + ByteUtils.HexToByteArray("750c783e6ab0b503eaa86e310a5db738"), + ByteUtils.HexToByteArray("56be34521d144c88dbb8c733f0e8b3f6"), + ByteUtils.HexToByteArray("697eaf0aca3a3aea3a75164746ffaa79"), + ByteUtils.HexToByteArray("56461ef2342edc00f9bab995690efd4c"), + ByteUtils.HexToByteArray("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd"), + ByteUtils.HexToByteArray("6f630fad67cda0ee1fb1f562db3aa53e"), + }; - protected override HMAC Create() + public HmacMD5Tests() + : base(s_testKeys2202, s_testMacs2202) { - return new HMACMD5(); } - protected override HashAlgorithm CreateHashAlgorithm() - { - return MD5.Create(); - } + protected override int BlockSize => 64; + protected override int MacSize => 16; + + protected override HMAC Create() => new HMACMD5(); + protected override HashAlgorithm CreateHashAlgorithm() => MD5.Create(); + protected override byte[] HashDataOneShot(byte[] key, byte[] source) => + HMACMD5.HashData(key, source); + + protected override byte[] HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source) => + HMACMD5.HashData(key, source); + + protected override int HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination) => + HMACMD5.HashData(key, source, destination); - protected override int BlockSize { get { return 64; } } + protected override bool TryHashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int written) => + HMACMD5.TryHashData(key, source, destination, out written); [Fact] public void HmacMD5_Rfc2202_1() { - VerifyHmac(1, "9294727a3638bb1c13f48ef8158bfc9d"); + VerifyHmac(1, s_testMacs2202[1]); } [Fact] public void HmacMD5_Rfc2202_2() { - VerifyHmac(2, "750c783e6ab0b503eaa86e310a5db738"); + VerifyHmac(2, s_testMacs2202[2]); } [Fact] public void HmacMD5_Rfc2202_3() { - VerifyHmac(3, "56be34521d144c88dbb8c733f0e8b3f6"); + VerifyHmac(3, s_testMacs2202[3]); } [Fact] public void HmacMD5_Rfc2202_4() { - VerifyHmac(4, "697eaf0aca3a3aea3a75164746ffaa79"); + VerifyHmac(4, s_testMacs2202[4]); } [Fact] public void HmacMD5_Rfc2202_5() { - VerifyHmac(5, "56461ef2342edc00f9bab995690efd4c"); + VerifyHmac(5, s_testMacs2202[5]); } [Fact] public void HmacMD5_Rfc2202_6() { - VerifyHmac(6, "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd"); + VerifyHmac(6, s_testMacs2202[6]); } [Fact] public void HmacMD5_Rfc2202_7() { - VerifyHmac(7, "6f630fad67cda0ee1fb1f562db3aa53e"); + VerifyHmac(7, s_testMacs2202[7]); } [Fact] diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha1Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha1Tests.cs index 4cda1cc..49a9d6d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha1Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha1Tests.cs @@ -21,22 +21,39 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests ByteUtils.RepeatByte(0xaa, 80), }; - public HmacSha1Tests() - : base(s_testKeys2202) + private static readonly byte[][] s_testMacs2202 = { - } + null, + ByteUtils.HexToByteArray("b617318655057264e28bc0b6fb378c8ef146be00"), + ByteUtils.HexToByteArray("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"), + ByteUtils.HexToByteArray("125d7342b9ac11cd91a39af48aa17b4f63f175d3"), + ByteUtils.HexToByteArray("4c9007f4026250c6bc8414f9bf50c86c2d7235da"), + ByteUtils.HexToByteArray("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"), + ByteUtils.HexToByteArray("aa4ae5e15272d00e95705637ce8a3b55ed402112"), + ByteUtils.HexToByteArray("e8e99d0f45237d786d6bbaa7965c7808bbff1a91"), + }; - protected override HMAC Create() + public HmacSha1Tests() + : base(s_testKeys2202, s_testMacs2202) { - return new HMACSHA1(); } - protected override HashAlgorithm CreateHashAlgorithm() - { - return SHA1.Create(); - } + protected override int BlockSize => 64; + protected override int MacSize => 20; + + protected override HMAC Create() => new HMACSHA1(); + protected override HashAlgorithm CreateHashAlgorithm() => SHA1.Create(); + protected override byte[] HashDataOneShot(byte[] key, byte[] source) => + HMACSHA1.HashData(key, source); + + protected override byte[] HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source) => + HMACSHA1.HashData(key, source); + + protected override int HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination) => + HMACSHA1.HashData(key, source, destination); - protected override int BlockSize { get { return 64; } } + protected override bool TryHashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int written) => + HMACSHA1.TryHashData(key, source, destination, out written); [Fact] public void HmacSha1_Byte_Constructors() @@ -69,43 +86,43 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests [Fact] public void HmacSha1_Rfc2202_1() { - VerifyHmac(1, "b617318655057264e28bc0b6fb378c8ef146be00"); + VerifyHmac(1, s_testMacs2202[1]); } [Fact] public void HmacSha1_Rfc2202_2() { - VerifyHmac(2, "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"); + VerifyHmac(2, s_testMacs2202[2]); } [Fact] public void HmacSha1_Rfc2202_3() { - VerifyHmac(3, "125d7342b9ac11cd91a39af48aa17b4f63f175d3"); + VerifyHmac(3, s_testMacs2202[3]); } [Fact] public void HmacSha1_Rfc2202_4() { - VerifyHmac(4, "4c9007f4026250c6bc8414f9bf50c86c2d7235da"); + VerifyHmac(4, s_testMacs2202[4]); } [Fact] public void HmacSha1_Rfc2202_5() { - VerifyHmac(5, "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"); + VerifyHmac(5, s_testMacs2202[5]); } [Fact] public void HmacSha1_Rfc2202_6() { - VerifyHmac(6, "aa4ae5e15272d00e95705637ce8a3b55ed402112"); + VerifyHmac(6, s_testMacs2202[6]); } [Fact] public void HmacSha1_Rfc2202_7() { - VerifyHmac(7, "e8e99d0f45237d786d6bbaa7965c7808bbff1a91"); + VerifyHmac(7, s_testMacs2202[7]); } [Fact] diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha256Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha256Tests.cs index a9d12e5..ae9004e 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha256Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha256Tests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Test.Cryptography; using Xunit; namespace System.Security.Cryptography.Hashing.Algorithms.Tests @@ -8,59 +9,80 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class HmacSha256Tests : Rfc4231HmacTests { - protected override HMAC Create() + protected override int BlockSize => 64; + protected override int MacSize => 32; + + protected override HMAC Create() => new HMACSHA256(); + protected override HashAlgorithm CreateHashAlgorithm() => SHA256.Create(); + protected override byte[] HashDataOneShot(byte[] key, byte[] source) => + HMACSHA256.HashData(key, source); + + protected override byte[] HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source) => + HMACSHA256.HashData(key, source); + + protected override int HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination) => + HMACSHA256.HashData(key, source, destination); + + protected override bool TryHashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int written) => + HMACSHA256.TryHashData(key, source, destination, out written); + + private static byte[][] s_testMacs4231 = { - return new HMACSHA256(); - } + null, + ByteUtils.HexToByteArray("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"), + ByteUtils.HexToByteArray("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"), + ByteUtils.HexToByteArray("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"), + ByteUtils.HexToByteArray("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"), + // RFC 4231 only defines the first 128 bits of this value. + ByteUtils.HexToByteArray("a3b6167473100ee06e0c796c2955552b"), + ByteUtils.HexToByteArray("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"), + ByteUtils.HexToByteArray("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"), + }; - protected override HashAlgorithm CreateHashAlgorithm() + public HmacSha256Tests() : base(s_testMacs4231) { - return SHA256.Create(); } - protected override int BlockSize { get { return 64; } } - [Fact] public void HmacSha256_Rfc4231_1() { - VerifyHmac(1, "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"); + VerifyHmac(1, s_testMacs4231[1]); } [Fact] public void HmacSha256_Rfc4231_2() { - VerifyHmac(2, "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"); + VerifyHmac(2, s_testMacs4231[2]); } [Fact] public void HmacSha256_Rfc4231_3() { - VerifyHmac(3, "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"); + VerifyHmac(3, s_testMacs4231[3]); } [Fact] public void HmacSha256_Rfc4231_4() { - VerifyHmac(4, "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"); + VerifyHmac(4, s_testMacs4231[4]); } [Fact] public void HmacSha256_Rfc4231_5() { - // RFC 4231 only defines the first 128 bits of this value. - VerifyHmac(5, "a3b6167473100ee06e0c796c2955552b", 128 / 8); + VerifyHmac(5, s_testMacs4231[5]); } [Fact] public void HmacSha256_Rfc4231_6() { - VerifyHmac(6, "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"); + VerifyHmac(6, s_testMacs4231[6]); } [Fact] public void HmacSha256_Rfc4231_7() { - VerifyHmac(7, "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"); + VerifyHmac(7, s_testMacs4231[7]); } [Fact] diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha384Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha384Tests.cs index 5a5f069..76325f1 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha384Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha384Tests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Test.Cryptography; using Xunit; namespace System.Security.Cryptography.Hashing.Algorithms.Tests @@ -8,18 +9,40 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class HmacSha384Tests : Rfc4231HmacTests { - protected override HMAC Create() + protected override int BlockSize => 128; + protected override int MacSize => 48; + + protected override HMAC Create() => new HMACSHA384(); + protected override HashAlgorithm CreateHashAlgorithm() => SHA384.Create(); + protected override byte[] HashDataOneShot(byte[] key, byte[] source) => + HMACSHA384.HashData(key, source); + + protected override byte[] HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source) => + HMACSHA384.HashData(key, source); + + protected override int HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination) => + HMACSHA384.HashData(key, source, destination); + + protected override bool TryHashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int written) => + HMACSHA384.TryHashData(key, source, destination, out written); + + private static byte[][] s_testMacs4231 = { - return new HMACSHA384(); - } + null, + ByteUtils.HexToByteArray("afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"), + ByteUtils.HexToByteArray("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649"), + ByteUtils.HexToByteArray("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27"), + ByteUtils.HexToByteArray("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb"), + // RFC 4231 only defines the first 128 bits of this value. + ByteUtils.HexToByteArray("3abf34c3503b2a23a46efc619baef897"), + ByteUtils.HexToByteArray("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952"), + ByteUtils.HexToByteArray("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e"), + }; - protected override HashAlgorithm CreateHashAlgorithm() + public HmacSha384Tests() : base(s_testMacs4231) { - return SHA384.Create(); } - protected override int BlockSize { get { return 128; } } - [Fact] public void ProduceLegacyHmacValues() { @@ -34,44 +57,43 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests [Fact] public void HmacSha384_Rfc4231_1() { - VerifyHmac(1, "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"); + VerifyHmac(1, s_testMacs4231[1]); } [Fact] public void HmacSha384_Rfc4231_2() { - VerifyHmac(2, "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649"); + VerifyHmac(2, s_testMacs4231[2]); } [Fact] public void HmacSha384_Rfc4231_3() { - VerifyHmac(3, "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27"); + VerifyHmac(3, s_testMacs4231[3]); } [Fact] public void HmacSha384_Rfc4231_4() { - VerifyHmac(4, "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb"); + VerifyHmac(4, s_testMacs4231[4]); } [Fact] public void HmacSha384_Rfc4231_5() { - // RFC 4231 only defines the first 128 bits of this value. - VerifyHmac(5, "3abf34c3503b2a23a46efc619baef897", 128 / 8); + VerifyHmac(5, s_testMacs4231[5]); } [Fact] public void HmacSha384_Rfc4231_6() { - VerifyHmac(6, "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952"); + VerifyHmac(6, s_testMacs4231[6]); } [Fact] public void HmacSha384_Rfc4231_7() { - VerifyHmac(7, "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e"); + VerifyHmac(7, s_testMacs4231[7]); } [Fact] diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha512Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha512Tests.cs index 3136ffd..4da1884 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha512Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacSha512Tests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Test.Cryptography; using Xunit; namespace System.Security.Cryptography.Hashing.Algorithms.Tests @@ -8,18 +9,40 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class HmacSha512Tests : Rfc4231HmacTests { - protected override HMAC Create() + protected override int BlockSize => 128; + protected override int MacSize => 64; + + protected override HMAC Create() => new HMACSHA512(); + protected override HashAlgorithm CreateHashAlgorithm() => SHA512.Create(); + protected override byte[] HashDataOneShot(byte[] key, byte[] source) => + HMACSHA512.HashData(key, source); + + protected override byte[] HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source) => + HMACSHA512.HashData(key, source); + + protected override int HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination) => + HMACSHA512.HashData(key, source, destination); + + protected override bool TryHashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int written) => + HMACSHA512.TryHashData(key, source, destination, out written); + + private static byte[][] s_testMacs4231 = { - return new HMACSHA512(); - } + null, + ByteUtils.HexToByteArray("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"), + ByteUtils.HexToByteArray("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"), + ByteUtils.HexToByteArray("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"), + ByteUtils.HexToByteArray("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"), + // RFC 4231 only defines the first 128 bits of this value. + ByteUtils.HexToByteArray("415fad6271580a531d4179bc891d87a6"), + ByteUtils.HexToByteArray("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"), + ByteUtils.HexToByteArray("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"), + }; - protected override HashAlgorithm CreateHashAlgorithm() + public HmacSha512Tests() : base(s_testMacs4231) { - return SHA512.Create(); } - protected override int BlockSize { get { return 128; } } - [Fact] public void ProduceLegacyHmacValues() { @@ -34,44 +57,43 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests [Fact] public void HmacSha512_Rfc4231_1() { - VerifyHmac(1, "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"); + VerifyHmac(1, s_testMacs4231[1]); } [Fact] public void HmacSha512_Rfc4231_2() { - VerifyHmac(2, "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"); + VerifyHmac(2, s_testMacs4231[2]); } [Fact] public void HmacSha512_Rfc4231_3() { - VerifyHmac(3, "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"); + VerifyHmac(3, s_testMacs4231[3]); } [Fact] public void HmacSha512_Rfc4231_4() { - VerifyHmac(4, "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"); + VerifyHmac(4, s_testMacs4231[4]); } [Fact] public void HmacSha512_Rfc4231_5() { - // RFC 4231 only defines the first 128 bits of this value. - VerifyHmac(5, "415fad6271580a531d4179bc891d87a6", 128 / 8); + VerifyHmac(5, s_testMacs4231[5]); } [Fact] public void HmacSha512_Rfc4231_6() { - VerifyHmac(6, "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"); + VerifyHmac(6, s_testMacs4231[6]); } [Fact] public void HmacSha512_Rfc4231_7() { - VerifyHmac(7, "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"); + VerifyHmac(7, s_testMacs4231[7]); } [Fact] diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacTests.cs index 13a2468..510f58d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/HmacTests.cs @@ -15,31 +15,36 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests // They share the same datasets for cases 1-5, but cases 6 and 7 differ. private readonly byte[][] _testKeys; private readonly byte[][] _testData; + private readonly byte[][] _testMacs; - protected HmacTests(byte[][] testKeys, byte[][] testData) + protected HmacTests(byte[][] testKeys, byte[][] testData, byte[][] testMacs) { _testKeys = testKeys; _testData = testData; + _testMacs = testMacs; } protected abstract HMAC Create(); protected abstract HashAlgorithm CreateHashAlgorithm(); + protected abstract byte[] HashDataOneShot(byte[] key, byte[] source); + protected abstract byte[] HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source); + protected abstract int HashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination); + protected abstract bool TryHashDataOneShot(ReadOnlySpan key, ReadOnlySpan source, Span destination, out int written); protected abstract int BlockSize { get; } + protected abstract int MacSize { get; } - protected void VerifyHmac( - int testCaseId, - string digest, - int truncateSize = -1) + protected void VerifyHmac(int testCaseId, byte[] digestBytes) { - byte[] digestBytes = ByteUtils.HexToByteArray(digest); byte[] data = _testData[testCaseId]; byte[] computedDigest; + int truncateSize = digestBytes.Length; + AssertExtensions.LessThanOrEqualTo(truncateSize, MacSize); using (HMAC hmac = Create()) { - Assert.True(hmac.HashSize > 0); + Assert.Equal(MacSize, hmac.HashSize / 8); byte[] key = (byte[])_testKeys[testCaseId].Clone(); hmac.Key = key; @@ -55,13 +60,7 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests computedDigest = hmac.ComputeHash(data); } - if (truncateSize != -1) - { - byte[] tmp = new byte[truncateSize]; - Array.Copy(computedDigest, tmp, truncateSize); - computedDigest = tmp; - } - + computedDigest = Truncate(computedDigest, truncateSize); Assert.Equal(digestBytes, computedDigest); using (HMAC hmac = Create()) @@ -76,14 +75,22 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests computedDigest = hmac.Hash; } - if (truncateSize != -1) - { - byte[] tmp = new byte[truncateSize]; - Array.Copy(computedDigest, tmp, truncateSize); - computedDigest = tmp; - } + computedDigest = Truncate(computedDigest, truncateSize); + Assert.Equal(digestBytes, computedDigest); + + // One shot - allocating and byte array inputs + computedDigest = HashDataOneShot(_testKeys[testCaseId], data); + computedDigest = Truncate(computedDigest, truncateSize); Assert.Equal(digestBytes, computedDigest); + + static byte[] Truncate(byte[] digest, int truncateSize) + { + if (truncateSize == -1) + return digest; + + return digest.AsSpan(0, truncateSize).ToArray(); + } } protected void VerifyHmac_KeyAlreadySet( @@ -232,5 +239,152 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests AssertExtensions.Throws("value", () => hash.Key = null); } } + + [Fact] + public void OneShot_NullKey_ArgumentNullException() + { + AssertExtensions.Throws("key", () => + HashDataOneShot(key: (byte[])null, source: Array.Empty())); + } + + [Fact] + public void OneShot_NullSource_ArgumentNullException() + { + AssertExtensions.Throws("source", () => + HashDataOneShot(key: Array.Empty(), source: (byte[])null)); + } + + [Fact] + public void OneShot_ExistingBuffer_TooSmall() + { + byte[] buffer = new byte[MacSize - 1]; + byte[] key = _testKeys[1]; + byte[] data = _testData[1]; + + AssertExtensions.Throws("destination", () => + HashDataOneShot(key, data, buffer)); + + AssertExtensions.FilledWith(0, buffer); + } + + [Fact] + public void OneShot_TryExistingBuffer_TooSmall() + { + byte[] buffer = new byte[MacSize - 1]; + byte[] key = _testKeys[1]; + byte[] data = _testData[1]; + + Assert.False(TryHashDataOneShot(key, data, buffer, out int written)); + Assert.Equal(0, written); + AssertExtensions.FilledWith(0, buffer); + } + + [Fact] + public void OneShot_TryExistingBuffer_Exact() + { + for (int caseId = 1; caseId <= 7; caseId++) + { + byte[] buffer = new byte[MacSize]; + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + + Assert.True(TryHashDataOneShot(key, data, buffer, out int written)); + Assert.Equal(MacSize, written); + + ReadOnlySpan expectedMac = _testMacs[caseId]; + Span truncatedBuffer = buffer.AsSpan(0, expectedMac.Length); + AssertExtensions.SequenceEqual(expectedMac, truncatedBuffer); + } + } + + [Fact] + public void OneShot_TryExistingBuffer_Larger() + { + for (int caseId = 1; caseId <= 7; caseId++) + { + Span buffer = new byte[MacSize + 20]; + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + + buffer.Fill(0xCC); + Span writeBuffer = buffer.Slice(10, MacSize); + + Assert.True(TryHashDataOneShot(key, data, writeBuffer, out int written)); + Assert.Equal(MacSize, written); + + ReadOnlySpan expectedMac = _testMacs[caseId]; + Span truncatedWriteBuffer = writeBuffer.Slice(0, expectedMac.Length); + AssertExtensions.SequenceEqual(expectedMac, truncatedWriteBuffer); + AssertExtensions.FilledWith(0xCC, buffer[..10]); + AssertExtensions.FilledWith(0xCC, buffer[^10..]); + } + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 10)] + [InlineData(10, 0)] + [InlineData(10, 20)] + public void OneShot_TryExistingBuffer_OverlapsKey(int keyOffset, int bufferOffset) + { + for (int caseId = 1; caseId <= 7; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + Span buffer = new byte[Math.Max(key.Length, MacSize) + Math.Max(keyOffset, bufferOffset)]; + + Span writeBuffer = buffer.Slice(bufferOffset, MacSize); + Span keyBuffer = buffer.Slice(keyOffset, key.Length); + key.AsSpan().CopyTo(keyBuffer); + + Assert.True(TryHashDataOneShot(keyBuffer, data, writeBuffer, out int written)); + Assert.Equal(MacSize, written); + + ReadOnlySpan expectedMac = _testMacs[caseId]; + Span truncatedWriteBuffer = writeBuffer.Slice(0, expectedMac.Length); + AssertExtensions.SequenceEqual(expectedMac, truncatedWriteBuffer); + } + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 10)] + [InlineData(10, 0)] + [InlineData(10, 20)] + public void OneShot_TryExistingBuffer_OverlapsSource(int sourceOffset, int bufferOffset) + { + for (int caseId = 1; caseId <= 7; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + Span buffer = new byte[Math.Max(data.Length, MacSize) + Math.Max(sourceOffset, bufferOffset)]; + + Span writeBuffer = buffer.Slice(bufferOffset, MacSize); + Span dataBuffer = buffer.Slice(sourceOffset, data.Length); + data.AsSpan().CopyTo(dataBuffer); + + Assert.True(TryHashDataOneShot(key, dataBuffer, writeBuffer, out int written)); + Assert.Equal(MacSize, written); + + ReadOnlySpan expectedMac = _testMacs[caseId]; + Span truncatedWriteBuffer = writeBuffer.Slice(0, expectedMac.Length); + AssertExtensions.SequenceEqual(expectedMac, truncatedWriteBuffer); + } + } + + [Theory] + [InlineData(new byte[0], new byte[] { 1 })] + [InlineData(new byte[] { 1 }, new byte[0])] + public void OneShot_Empty_Matches_Instances(byte[] key, byte[] source) + { + using (HMAC hash = Create()) + { + hash.Key = key; + byte[] mac = hash.ComputeHash(source, 0, source.Length); + + byte[] oneShot = HashDataOneShot(key, source); + Assert.Equal(mac, oneShot); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2202HmacTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2202HmacTests.cs index e66cd89..8486486 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2202HmacTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2202HmacTests.cs @@ -24,8 +24,8 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests // The keys for test cases 1, 3, and 5 for RFC2202 are sized to match the // algorithm (16 bytes for MD5, 20 for SHA-1), so they need to be provided by // the more derived type. - protected Rfc2202HmacTests(byte[][] testKeys) : - base(testKeys, s_testData2202) + protected Rfc2202HmacTests(byte[][] testKeys, byte[][] testMacs) : + base(testKeys, s_testData2202, testMacs) { } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc4231HmacTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc4231HmacTests.cs index af8e3e6..771973a 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc4231HmacTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc4231HmacTests.cs @@ -33,8 +33,8 @@ namespace System.Security.Cryptography.Hashing.Algorithms.Tests ByteUtils.AsciiBytes("This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm."), }; - protected Rfc4231HmacTests() : - base(s_testKeys4231, s_testData4231) + protected Rfc4231HmacTests(byte[][] testMacs) : + base(s_testKeys4231, s_testData4231, testMacs) { } } -- 2.7.4