-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: KMAC #93494
Comments
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones Issue DetailsBackground and motivationThis is the continuation of SHA-3 that was introduced in .NET 8, which is to bring in KMAC-128 and KMAC-256 as defined in SP800-185. KMAC is a keyed-hash function based on Keccak with variable length MACs. As such, it takes a key, a message, an optional customization string, and produces a variable length MAC. This API surface is very similar to SHAKE in terms of naming, and supporting both instance based appending and static one-shots. A note on the name "customization string": this is in according with the name of SP800-185. Its name is understood in the context of KMAC. API ProposalNote: The API shape is identical between the classes, the only variation is the name of the class. namespace System.Security.Cryptography;
public sealed class KMAC128 : IDisposable {
public KMAC128(byte[] key, byte[]? customizationString = null);
public KMAC128(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
// One shots
public static byte[] HashData(
byte[] key,
byte[] source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpan<byte> key,
Stream source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpan<byte> key,
Stream source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null,
CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
int outputLength,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
Memory<byte> desintation,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
}
public sealed class KMAC256 : IDisposable {
public KMAC256(byte[] key, byte[]? customizationString = null);
public KMAC256(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
// One shots
public static byte[] HashData(
byte[] key,
byte[] source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpan<byte> key,
Stream source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpan<byte> key,
Stream source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null,
CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
int outputLength,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
Memory<byte> desintation,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
}
public sealed class KMACXOF128 : IDisposable {
public KMACXOF128(byte[] key, byte[]? customizationString = null);
public KMACXOF128(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
// One shots
public static byte[] HashData(
byte[] key,
byte[] source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpan<byte> key,
Stream source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpan<byte> key,
Stream source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null,
CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
int outputLength,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
Memory<byte> desintation,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
}
public sealed class KMACXOF256 : IDisposable {
public KMACXOF256(byte[] key, byte[]? customizationString = null);
public KMACXOF256(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
// One shots
public static byte[] HashData(
byte[] key,
byte[] source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpana<byte> key,
ReadOnlySpan<byte> source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null);
public static byte[] HashData(
ReadOnlySpan<byte> key,
Stream source,
int outputLength,
ReadOnlySpan<byte> customizationString = default);
public static void HashData(
ReadOnlySpan<byte> key,
Stream source,
Span<byte> destination,
ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(
byte[] key,
Stream source,
int outputLength,
byte[]? customizationString = null,
CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
int outputLength,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(
ReadOnlyMemory<byte> key,
Stream source,
Memory<byte> desintation,
ReadOnlyMemory<byte> customizationString = default,
CancellationToken cancellationToken = default);
} API UsageThe usage is similar for all of KMAC depending on XOF and the security strength. byte[] key = GetKmacKey();
ReadOnlySpan<byte> customizationString = "unrelated function"u8;
using (KMAC128 kmac = new KMAC128(key, customizationString))
{
kmac.AppendData("Hello .NET"u8);
byte[] mac = kmac.GetHashAndReset(outputLength: 32);
} Alternative DesignsI had originally considered XOF being a parameter of KMAC instead of separate types. I however landed on separate types, despite them having identical API surfaces between the XOF and non-XOF KMACs. The reason for this is because there is a possibility to further expand the XOF API surface independently of the non-XOF API surface. An API may exist in the future like So why don't we have a There is some argument for not including KMACXOF at all, however it is in a complete enough state between two platforms that it seems worthwhile to support it without making any tradeoffs. RisksNo response
|
I'm not a super-fan of the 7 successive caps of KMACXOF. KmacXof would be the next-most-reasonable, but would be asserting that we think "Kmac128" is a reasonable name... which I'm not sure it is. Otherwise, it seems reasonable to me. Especially the differentiation for if we can ever add the streaming semantics of "squeeze" to the XOFs. |
Given that we called SHAKE in .NET 8 |
namespace System.Security.Cryptography;
public sealed class Kmac128 : IDisposable
{
public Kmac128(byte[] key, byte[]? customizationString = null);
public Kmac128(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
public static byte[] HashData(byte[] key, byte[] source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(byte[] key, Stream source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, Stream source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, Stream source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(byte[] key, Stream source, int outputLength, byte[]? customizationString = null, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(ReadOnlyMemory<byte> key, Stream source, int outputLength, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
}
public sealed class Kmac256 : IDisposable
{
public Kmac256(byte[] key, byte[]? customizationString = null);
public Kmac256(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
public static byte[] HashData(byte[] key, byte[] source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(byte[] key, Stream source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, Stream source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, Stream source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(byte[] key, Stream source, int outputLength, byte[]? customizationString = null, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(ReadOnlyMemory<byte> key, Stream source, int outputLength, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
}
public sealed class KmacXof128 : IDisposable
{
public KmacXof128(byte[] key, byte[]? customizationString = null);
public KmacXof128(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
public static byte[] HashData(byte[] key, byte[] source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(byte[] key, Stream source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, Stream source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, Stream source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(byte[] key, Stream source, int outputLength, byte[]? customizationString = null, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(ReadOnlyMemory<byte> key, Stream source, int outputLength, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
}
public sealed class KmacXof256 : IDisposable
{
public KmacXof256(byte[] key, byte[]? customizationString = null);
public KmacXof256(ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString = default);
public static bool IsSupported { get; }
public void AppendData(byte[] data);
public void AppendData(ReadOnlySpan<byte> data);
public byte[] GetHashAndReset(int outputLength);
public void GetHashAndReset(Span<byte> destination);
public byte[] GetCurrentHash(int outputLength);
public void GetCurrentHash(Span<byte> destination);
public void Dispose();
public static byte[] HashData(byte[] key, byte[] source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static byte[] HashData(byte[] key, Stream source, int outputLength, byte[]? customizationString = null);
public static byte[] HashData(ReadOnlySpan<byte> key, Stream source, int outputLength, ReadOnlySpan<byte> customizationString = default);
public static void HashData(ReadOnlySpan<byte> key, Stream source, Span<byte> destination, ReadOnlySpan<byte> customizationString = default);
public static ValueTask<byte[]> HashDataAsync(byte[] key, Stream source, int outputLength, byte[]? customizationString = null, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(ReadOnlyMemory<byte> key, Stream source, int outputLength, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
public static ValueTask HashDataAsync(ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, ReadOnlyMemory<byte> customizationString = default, CancellationToken cancellationToken = default);
} |
This is on hold pending some input from the Windows team about the CNG implementation. |
We have a path forward on Windows now, so going back to 9.0. |
Background and motivation
This is the continuation of SHA-3 that was introduced in .NET 8, which is to bring in KMAC-128 and KMAC-256 as defined in SP800-185.
KMAC is a keyed-hash function based on Keccak with variable length MACs. As such, it takes a key, a message, an optional customization string, and produces a variable length MAC.
This API surface is very similar to SHAKE in terms of naming, and supporting both instance based appending and static one-shots.
A note on the name "customization string": this is in according with the name of SP800-185. Its name is understood in the context of KMAC.
API Proposal
Note: The API shape is identical between the classes, the only variation is the name of the class.
API Usage
The usage is similar for all of KMAC depending on XOF and the security strength.
Alternative Designs
I had originally considered XOF being a parameter of KMAC instead of separate types. I however landed on separate types, despite them having identical API surfaces between the XOF and non-XOF KMACs.
The reason for this is because there is a possibility to further expand the XOF API surface independently of the non-XOF API surface.
An API may exist in the future like
SqueezeHash
that allows repeated calls to derive the MAC. This API would make sense for the XOF KMACs, but would not make sense for the non-XOF. That API would have to throw.So why don't we have a
SqueezeHash
or similar API? Today, we do not have that capability because OpenSSL does not let XOF "stream" back results. OpenSSL does not let you do successive "squeeze" calls. They may address this in the future. Windows gives us this capability, however it is close enough to our "2+ primitives rule" that I chose not to include it.There is some argument for not including KMACXOF at all, however it is in a complete enough state between two platforms that it seems worthwhile to support it without making any tradeoffs.
Risks
No response
The text was updated successfully, but these errors were encountered: