| File: System\Security\Cryptography\HMACCommon.cs | Web Access |
| Project: src\runtime\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography) |
// 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.Diagnostics.CodeAnalysis; using Internal.Cryptography; namespace System.Security.Cryptography { // // This class provides the common functionality for HMACSHA1, HMACSHA256, HMACMD5, etc. // Ideally, this would be encapsulated in a common base class but the preexisting contract // locks these public classes into deriving directly from HMAC so we have to use encapsulation // and delegation to HMACCommon instead. // // This wrapper adds the ability to change the Key on the fly for compat with the desktop. // internal sealed class HMACCommon { public HMACCommon(string hashAlgorithmId, byte[] key, int blockSize) : this(hashAlgorithmId, (ReadOnlySpan<byte>)key, blockSize) { // If the key is smaller than the block size, the delegated ctor won't have initialized ActualKey, // so set it here as would ChangeKey. ActualKey ??= key; } internal HMACCommon(string hashAlgorithmId, ReadOnlySpan<byte> key, int blockSize) { Debug.Assert(!string.IsNullOrEmpty(hashAlgorithmId)); Debug.Assert(blockSize > 0 || blockSize == -1); _hashAlgorithmId = hashAlgorithmId; _blockSize = blockSize; // note: will not set ActualKey if key size is smaller or equal than blockSize // this is to avoid extra allocation. ActualKey can still be used if key is generated. // Otherwise the ReadOnlySpan overload would actually be slower than byte array overload. ActualKey = ChangeKeyImpl(key); } private HMACCommon(string hashAlgorithmId, HashProvider hmacProvider, int blockSize, byte[]? actualKey) { Debug.Assert(!string.IsNullOrEmpty(hashAlgorithmId)); Debug.Assert(blockSize is > 0 or -1); _hashAlgorithmId = hashAlgorithmId; _hMacProvider = hmacProvider; _blockSize = blockSize; ActualKey = Helpers.CloneByteArray(actualKey); } public int HashSizeInBits => _hMacProvider.HashSizeInBytes * 8; public int HashSizeInBytes => _hMacProvider.HashSizeInBytes; public void ChangeKey(byte[] key) { ActualKey = ChangeKeyImpl(key) ?? key; } [MemberNotNull(nameof(_hMacProvider))] private byte[]? ChangeKeyImpl(ReadOnlySpan<byte> key) { byte[]? modifiedKey = null; // If _blockSize is -1 the key isn't going to be extractable by the object holder, // so there's no point in recalculating it in managed code. if (key.Length > _blockSize && _blockSize > 0) { // Perform RFC 2104, section 2 key adjustment. switch (_hashAlgorithmId) { case HashAlgorithmNames.SHA256: modifiedKey = SHA256.HashData(key); break; case HashAlgorithmNames.SHA384: modifiedKey = SHA384.HashData(key); break; case HashAlgorithmNames.SHA512: modifiedKey = SHA512.HashData(key); break; case HashAlgorithmNames.SHA3_256: Debug.Assert(SHA3_256.IsSupported); modifiedKey = SHA3_256.HashData(key); break; case HashAlgorithmNames.SHA3_384: Debug.Assert(SHA3_384.IsSupported); modifiedKey = SHA3_384.HashData(key); break; case HashAlgorithmNames.SHA3_512: Debug.Assert(SHA3_512.IsSupported); modifiedKey = SHA3_512.HashData(key); break; case HashAlgorithmNames.SHA1: modifiedKey = SHA1.HashData(key); break; case HashAlgorithmNames.MD5 when Helpers.HasMD5: modifiedKey = MD5.HashData(key); break; default: throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, _hashAlgorithmId)); }; } HashProvider? oldHashProvider = _hMacProvider; _hMacProvider = null!; oldHashProvider?.Dispose(true); _hMacProvider = HashProviderDispenser.CreateMacProvider(_hashAlgorithmId, key); return modifiedKey; } // The actual key used for hashing. This will not be the same as the original key passed to ChangeKey() if the original key exceeded the // hash algorithm's block size. (See RFC 2104, section 2) public byte[]? ActualKey { get; private set; } // Adds new data to be hashed. This can be called repeatedly in order to hash data from noncontiguous sources. public void AppendHashData(byte[] data, int offset, int count) => _hMacProvider.AppendHashData(data, offset, count); public void AppendHashData(ReadOnlySpan<byte> source) => _hMacProvider.AppendHashData(source); // Compute the hash based on the appended data and resets the HashProvider for more hashing. public byte[] FinalizeHashAndReset() => _hMacProvider.FinalizeHashAndReset(); public int FinalizeHashAndReset(Span<byte> destination) => _hMacProvider.FinalizeHashAndReset(destination); public bool TryFinalizeHashAndReset(Span<byte> destination, out int bytesWritten) => _hMacProvider.TryFinalizeHashAndReset(destination, out bytesWritten); public int GetCurrentHash(Span<byte> destination) => _hMacProvider.GetCurrentHash(destination); public void Reset() => _hMacProvider.Reset(); public HMACCommon Clone() { return new HMACCommon(_hashAlgorithmId, _hMacProvider.Clone(), _blockSize, ActualKey); } public void Dispose(bool disposing) { if (disposing) { _hMacProvider?.Dispose(true); _hMacProvider = null!; } } private readonly string _hashAlgorithmId; private HashProvider _hMacProvider; private readonly int _blockSize; } }