dotnet / dotnet/runtime

[API Proposal]: TupleHash

Open
#125,890 10 comments 1 reaction 0 assignees View on GitHub
api-suggestion area-System.Security
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

[SP800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf) defines TupleHash, which is a family of algorithms based on SHA-3, that allow securely and unambiguously hashing tuples of byte strings, without being susceptible to defects like `TupleHash("abc", "de")` being equal to `TupleHash("ab", "cde")`.

We already expose APIs for KMAC – another family of algorithms defined in the same document, so I am proposing to add APIs for TupleHash as well. There has been an ask for this in #123625.

Implementation-wise, [Windows CNG](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-algorithm-identifiers) and [OpenSSL 4.0](https://docs.openssl.org/master/man7/EVP_MD-SHAKE/) support cSHAKE (the underlying primitive for TupleHash), so the rule of two is met.

### API Proposal

```csharp
namespace System.Security.Cryptography;

public sealed class TupleHash128 : IDisposable {
public TupleHash128(byte[] customizationString);
public TupleHash128(ReadOnlySpan customizationString);
public TupleHash128();

public static bool IsSupported { get; }

public void AppendElement(byte[] data);
public void AppendElement(ReadOnlySpan data);

public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span destination);

public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span destination);

public TupleHash128 Clone();

public void Dispose();

public void Reset();
}

public sealed class TupleHash256 : IDisposable {
public TupleHash256(byte[] customizationString);
public TupleHash256(ReadOnlySpan customizationString);
public TupleHash256();

public static bool IsSupported { get; }

public void AppendElement(byte[] data);
public void AppendElement(ReadOnlySpan data);

public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span destination);

public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span destination);

public TupleHash256 Clone();

public void Dispose();

public void Reset();
}

public sealed class TupleHashXof128 : IDisposable {
public TupleHashXof128(byte[] customizationString);
public TupleHashXof128(ReadOnlySpan customizationString);
public TupleHashXof128();

public static bool IsSupported { get; }

public void AppendElement(byte[] data);
public void AppendElement(ReadOnlySpan data);

public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span destination);

public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span destination);

public void Dispose();

public void Read(Span destination);
public byte[] Read(int outputLength);

public TupleHashXof128 Clone();

public void Reset();
}

public sealed class TupleHashXof256 : IDisposable {
public TupleHashXof256(byte[] customizationString);
public TupleHashXof256(ReadOnlySpan customizationString);
public TupleHashXof256();

public static bool IsSupported { get; }

public void AppendElement(byte[] data);
public void AppendElement(ReadOnlySpan data);

public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span destination);

public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span destination);

public void Dispose();

public void Read(Span destination);
public byte[] Read(int outputLength);

public TupleHashXof256 Clone();

public void Reset();
}
```

The design is based on the SHAKE APIs, with the following differences:

* Added optional customization string constructor parameter.
* The `AppendData` functions were renamed to `AppendElement` because as mentioned above, successive calls to `AppendElement` cannot be coalesced.
* No one-shot static functions are being proposed at the moment. I'm not sure how useful an API accepting say `byte[][]`/ `ReadOnlySpan` or `(byte[] data, int[] offsets)` would be, because it would require users to have their tuple data laid out in a specific way in memory.

### API Usage

```csharp
var h = new TupleHash128();

h.AppendElement("abc"u8);
h.AppendElement("de"u8);

Console.WriteLine(Convert.ToHexString(h.GetHashAndReset(256 / 8));
```

### Alternative Designs

* Generalize and add APIs for cSHAKE instead of TupleHash. This would require users to implement TupleHash on top of cSHAKE themselves, which might expose them to a pit of failure.
* After TupleHash, the only remaining algorithm defined in SP800-185 will be ParallelHash, which has not been requested yet, and would likely need a different API shape from SHAKE/KMAC/TupleHash altogether.
* We could explore additionally adding raw cSHAKE in the future if needed.
* The proposed API shape assumes that each tuple element exists in a contiguous memory buffer. If we don't want to make such assumption, we would need a `StartElement(int)` method to be called _before_ each element, which would be supplied with an `AppendData` method.
* A `FinishElement()` method to be called _after_ each element cannot be implemented without buffering the element being written, due to algorithm design limitations.

### Risks

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.