Skip to content

[API Proposal]: Verify HMAC/KMAC APIs #116028

Open
@vcsjones

Description

@vcsjones

Background and motivation

HMAC values often need to be compared in fixed time. Today, that can be done in two steps, using some API that produces an HMAC like HMACSHA256.HashData, and using CryptographicOperations.FixedTimeEqual.

This can be seen as a bit of a pit of failure because oftentimes folks don't do the fixed time part correctly. They may end up using Linq to compare arrays, or SequenceEqual.

Other frameworks offer a more symmetric signature-like API, where they take the HMAC, the data, and do the MAC over the data and the constant time check for you in a single step.

API Proposal

namespace System.Security.Cryptography;

public partial class HMACMD5 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA1 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA256 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA384 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);

    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA512 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA3_256 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA3_384 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class HMACSHA3_512 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, byte[] source, byte[] hash);
    
    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
}

public partial class Kmac128 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, byte[] source, byte[] hash, byte[]? customizationString = null);

    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);

    public bool VerifyCurrentHash(ReadOnlySpan<byte> hash);
    public bool VerifyCurrentHash(byte[] hash);

    public bool VerifyHashAndReset(ReadOnlySpan<byte> hash);
    public bool VerifyHashAndReset(byte[] hash);
}

public partial class Kmac256 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, byte[] source, byte[] hash, byte[]? customizationString = null);

    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);

    public bool VerifyCurrentHash(ReadOnlySpan<byte> hash);
    public bool VerifyCurrentHash(byte[] hash);

    public bool VerifyHashAndReset(ReadOnlySpan<byte> hash);
    public bool VerifyHashAndReset(byte[] hash);
}

public partial class KmacXof128 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, byte[] source, byte[] hash, byte[]? customizationString = null);

    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);

    public bool VerifyCurrentHash(ReadOnlySpan<byte> hash);
    public bool VerifyCurrentHash(byte[] hash);

    public bool VerifyHashAndReset(ReadOnlySpan<byte> hash);
    public bool VerifyHashAndReset(byte[] hash);
}

public partial class KmacXof256 {
    public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, byte[] source, byte[] hash, byte[]? customizationString = null);

    public static bool Verify(ReadOnlySpan<byte> key, System.IO.Stream source, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> customizationString = default);
    public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null);
    public static bool VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, byte[] customizationString = null, CancellationToken cancellationToken = default);
    public static bool VerifyAsync(ReadOnlyMemory<byte> key, System.IO.Stream source, ReadOnlyMemory<byte> hash, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);

    public bool VerifyCurrentHash(ReadOnlySpan<byte> hash);
    public bool VerifyCurrentHash(byte[] hash);

    public bool VerifyHashAndReset(ReadOnlySpan<byte> hash);
    public bool VerifyHashAndReset(byte[] hash);
}

public partial class IncrementalHash {
    public bool VerifyCurrentHash(ReadOnlySpan<byte> hash);
    public bool VerifyCurrentHash(byte[] hash);

    public bool VerifyHashAndReset(ReadOnlySpan<byte> hash);
    public bool VerifyHashAndReset(byte[] hash);
}

public partial class CryptographicOperations {
    public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash);
    public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash);

    public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source, ReadOnlySpan<byte> hash);
    public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash);
    
    public static bool VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, ReadOnlyMemory<byte> hash, CancellationToken cancellationToken = default);
    public static bool VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default);
}

API Usage

Similar to the HashData methods of each respective class, however it accepts an input MAC to verify against, which will be done with CryptographicOperations.

Alternative Designs

  • For similar reasoning as in [API Proposal]: Clone for IncrementalHash and KMAC #103170, no instance methods on HashAlgorithm is proposed.
  • I don't love that these are called VerifyHash. VerifyHmac or something would be better. However we tend to favor consistency, and this is more consistent with the existing method names.
  • There is no clear need for these with plain hashes. If there is a compelling reason to add them, we can.

Risks

It is yet another way to do H/KMAC things.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.SecurityuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions