using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace PspCrypto.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)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 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); } 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 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. modifiedKey = _hashAlgorithmId switch { "SHA224" => SHA224.HashData(key), _ => throw new CryptographicException(string.Format("'{0}' is not a known hash algorithm.", _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 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 destination) => _hMacProvider.FinalizeHashAndReset(destination); public bool TryFinalizeHashAndReset(Span destination, out int bytesWritten) => _hMacProvider.TryFinalizeHashAndReset(destination, out bytesWritten); public int GetCurrentHash(Span destination) => _hMacProvider.GetCurrentHash(destination); public void Reset() => _hMacProvider.Reset(); public void Dispose(bool disposing) { if (disposing) { _hMacProvider?.Dispose(true); _hMacProvider = null!; } } private readonly string _hashAlgorithmId; private HashProvider _hMacProvider; private readonly int _blockSize; } }