dotnet / dotnet/runtime

HPKE (Hybrid Public Key Encryption)

Open
#129,308 32 comments 2 reactions 2 assignees Claimed by @vcsjones View on GitHub
api-approved area-System.Security
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

# HPKE (Hybrid Public Key Encryption)

## Summary

HPKE provides a common representation of doing symmetric key establishment and encrypting data with that symmetric key, taking over from bespoke implementations.

The current RFC version does not have PQC support, but there are drafts in progress, and they should easily slot in here.

## API

```csharp
namespace System.Security.Cryptography;

[Experimental(/* Next SYSLIB for experimental available */)]
public sealed partial class HpkeSuite : IEquatable
{
public HpkeSuite(HpkeSuite.Kem kem, HpkeSuite.Kdf kdf, HpkeSuite.Aead aead);

public HpkeSuite.Aead AeadAlgorithm { get; }
public HpkeSuite.Kdf KdfAlgorithm { get; }
public HpkeSuite.Kem KemAlgorithm { get; }

public int AeadTagSizeInBytes { get; }
public int DecapsulationKeySizeInBytes { get; }
public int EncapsulatedSecretSizeInBytes { get; }
public int EncapsulationKeySizeInBytes { get; }
public string Name { get; }

// Accelerators for common suites
public static HpkeSuite DHKEMP256_HKDFSHA256_AES128GCM { get; }
public static HpkeSuite DHKEMP384_HKDFSHA384_AES256GCM { get; }
public static HpkeSuite DHKEMX25519_HKDFSHA256_AES128GCM { get; }
public static HpkeSuite DHKEMX25519_HKDFSHA256_ChaCha20Poly1305 { get; }
public static HpkeSuite MLKEM768_HKDFSHA256_AES128GCM { get; }
public static HpkeSuite MLKEM1024_HKDFSHA384_AES256GCM { get; }
public static HpkeSuite MLKEM768P256_HKDFSHA256_AES128GCM { get; }
public static HpkeSuite MLKEM768X25519_HKDFSHA256_ChaCha20Poly1305 { get; }
public static HpkeSuite MLKEM1024P384_HKDFSHA384_AES256GCM { get; }

public int GetCiphertextLength(int plaintextLength);

public override bool Equals(object? obj);
public bool Equals(HpkeSuite? other);
public override int GetHashCode();
public override string ToString();

public static bool operator ==(HpkeSuite? left, HpkeSuite? right);
public static bool operator !=(HpkeSuite? left, HpkeSuite? right);

[Experimental(/* Next SYSLIB for experimental available */)]
public enum Aead
{
// Values match IANA designations https://www.iana.org/assignments/hpke/hpke.xhtml
Aes128Gcm = 1,
Aes256Gcm = 2,
ChaCha20Poly1305 = 3,
}

[Experimental(/* Next SYSLIB for experimental available */)]
public enum Kdf
{
// Values match IANA designations https://www.iana.org/assignments/hpke/hpke.xhtml
HKDF_SHA256 = 1,
HKDF_SHA384 = 2,
HKDF_SHA512 = 3,
SHAKE128 = 16,
SHAKE256 = 17
}

[Experimental(/* Next SYSLIB for experimental available */)]
public enum Kem
{
// Values match IANA designations https://www.iana.org/assignments/hpke/hpke.xhtml
DHKEM_P256_HKDF_SHA256 = 16,
DHKEM_P384_HKDF_SHA384 = 17,
DHKEM_X25519_HKDF_SHA256 = 32,
MLKEM_512 = 64,
MLKEM_768 = 65,
MLKEM_1024 = 66,
MLKEM768_P256 = 80,
MLKEM1024_P384 = 81,
}
}

[Experimental(/* Next SYSLIB for experimental available */)]
public abstract class Hpke : IDisposable
{
protected Hpke(HpkeSuite suite);

public static bool IsSupported(HpkeSuite suite);
public HpkeSuite Suite { get; }

// Key management
public static Hpke GenerateKey(HpkeSuite suite);
public static Hpke ImportDecapsulationKey(HpkeSuite suite, byte[] source);
public static Hpke ImportDecapsulationKey(HpkeSuite suite, ReadOnlySpan source);
public static Hpke ImportEncapsulationKey(HpkeSuite suite, byte[] source);
public static Hpke ImportEncapsulationKey(HpkeSuite suite, ReadOnlySpan source);

public byte[] ExportDecapsulationKey();
public void ExportDecapsulationKey(Span destination);
protected abstract void ExportDecapsulationKeyCore(Span destination);

public byte[] ExportEncapsulationKey();
public void ExportEncapsulationKey(Span destination);
protected abstract void ExportEncapsulationKeyCore(Span destination);

// Single-shot Seal (Base mode)
public void Seal(ReadOnlySpan plaintext, out byte[] encapsulatedSecret, out byte[] ciphertext, ReadOnlySpan aad = default, ReadOnlySpan info = default);
public void Seal(byte[] plaintext, out byte[] encapsulatedSecret, out byte[] ciphertext, byte[]? aad = null, byte[]? info = null);
public void Seal(ReadOnlySpan plaintext, Span encapsulatedSecret, Span ciphertext, ReadOnlySpan aad = default, ReadOnlySpan info = default);
protected abstract void SealCore(ReadOnlySpan plaintext, Span encapsulatedSecret, Span ciphertext, ReadOnlySpan aad, ReadOnlySpan info);

// Single-shot Open (Base mode)
public byte[] Open(ReadOnlySpan encapsulatedSecret, ReadOnlySpan ciphertext, ReadOnlySpan aad = default, ReadOnlySpan info = default);
public byte[] Open(byte[] encapsulatedSecret, byte[] ciphertext, byte[]? aad = null, byte[]? info = null);
public void Open(ReadOnlySpan encapsulatedSecret, ReadOnlySpan ciphertext, Span plaintext, ReadOnlySpan aad = default, ReadOnlySpan info = default);
protected abstract void OpenCore(ReadOnlySpan encapsulatedSecret, ReadOnlySpan ciphertext, Span plaintext, ReadOnlySpan aad, ReadOnlySpan info);

// SetupSender — Base mode
public HpkeSender SetupSender(out byte[] encapsulatedSecret, ReadOnlySpan info = default);
public HpkeSender SetupSender(Span encapsulatedSecret, ReadOnlySpan info = default);
protected abstract HpkeSender SetupSenderCore(Span encapsulatedSecret, ReadOnlySpan info);

// SetupRecipient — Base mode
public HpkeRecipient SetupRecipient(ReadOnlySpan encapsulatedSecret, ReadOnlySpan info = default);
public HpkeRecipient SetupRecipient(byte[] encapsulatedSecret, byte[]? info = null);
protected abstract HpkeRecipient SetupRecipientCore(ReadOnlySpan encapsulatedSecret, ReadOnlySpan info);

// SetupSender — PSK mode
public HpkeSender SetupSenderPsk(ReadOnlySpan psk, ReadOnlySpan pskId, out byte[] encapsulatedSecret, ReadOnlySpan info = default);
public HpkeSender SetupSenderPsk(byte[] psk, byte[] pskId, out byte[] encapsulatedSecret, byte[]? info = null);
public HpkeSender SetupSenderPsk(ReadOnlySpan psk, ReadOnlySpan pskId, Span encapsulatedSecret, ReadOnlySpan info = default);
protected abstract HpkeSender SetupSenderPskCore(Span encapsulatedSecret, ReadOnlySpan info, ReadOnlySpan psk, ReadOnlySpan pskId);

// SetupRecipient — PSK mode
public HpkeRecipient SetupRecipientPsk(ReadOnlySpan encapsulatedSecret, ReadOnlySpan psk, ReadOnlySpan pskId, ReadOnlySpan info = default);
public HpkeRecipient SetupRecipientPsk(byte[] encapsulatedSecret, byte[] psk, byte[] pskId, byte[]? info = null);
protected abstract HpkeRecipient SetupRecipientPskCore(ReadOnlySpan encapsulatedSecret, ReadOnlySpan info, ReadOnlySpan psk, ReadOnlySpan pskId);

// Dispose
public void Dispose();
protected virtual void Dispose(bool disposing);
}

[Experimental(/* Next SYSLIB for experimental available */)]
public abstract class HpkeSender : IDisposable
{
protected HpkeSender();

public byte[] Seal(ReadOnlySpan plaintext, ReadOnlySpan aad = default);
public byte[] Seal(byte[] plaintext, byte[]? aad = null);
public void Seal(ReadOnlySpan plaintext, Span ciphertext, ReadOnlySpan aad = default);
public void Seal(byte[] plaintext, byte[] ciphertext, byte[]? aad = null);
protected abstract void SealCore(ReadOnlySpan plaintext, Span ciphertext, ReadOnlySpan aad);

public byte[] Export(ReadOnlySpan exporterContext, int length);
public byte[] Export(byte[] exporterContext, int length);
public void Export(ReadOnlySpan exporterContext, Span destination);
public void Export(byte[] exporterContext, byte[] destination);
protected abstract void ExportCore(ReadOnlySpan exporterContext, Span destination);

protected abstract int GetAeadTagSizeInBytes();

public void Dispose();
protected virtual void Dispose(bool disposing);
}

[Experimental(/* Next SYSLIB for experimental available */)]
public abstract class HpkeRecipient : IDisposable
{
protected HpkeRecipient();

public byte[] Open(ReadOnlySpan ciphertext, ReadOnlySpan aad = default);
public byte[] Open(byte[] ciphertext, byte[]? aad = null);
public void Open(ReadOnlySpan ciphertext, Span plaintext, ReadOnlySpan aad = default);
public void Open(byte[] ciphertext, byte[] plaintext, byte[]? aad = null);
protected abstract void OpenCore(ReadOnlySpan ciphertext, Span plaintext, ReadOnlySpan aad);

public byte[] Export(ReadOnlySpan exporterContext, int length);
public byte[] Export(byte[] exporterContext, int length);
public void Export(ReadOnlySpan exporterContext, Span destination);
public void Export(byte[] exporterContext, byte[] destination);
protected abstract void ExportCore(ReadOnlySpan exporterContext, Span destination);

protected abstract int GetAeadTagSizeInBytes();

public void Dispose();
protected virtual void Dispose(bool disposing);
}
```

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.