package hpke
import (
"crypto/ecdh"
"crypto/internal/rand"
"errors"
"internal/byteorder"
"slices"
)
type KEM interface {
ID () uint16
GenerateKey () (PrivateKey , error )
NewPublicKey ([]byte ) (PublicKey , error )
NewPrivateKey ([]byte ) (PrivateKey , error )
DeriveKeyPair (ikm []byte ) (PrivateKey , error )
encSize () int
}
func NewKEM (id uint16 ) (KEM , error ) {
switch id {
case 0x0010 :
return DHKEM (ecdh .P256 ()), nil
case 0x0011 :
return DHKEM (ecdh .P384 ()), nil
case 0x0012 :
return DHKEM (ecdh .P521 ()), nil
case 0x0020 :
return DHKEM (ecdh .X25519 ()), nil
case 0x0041 :
return MLKEM768 (), nil
case 0x0042 :
return MLKEM1024 (), nil
case 0x647a :
return MLKEM768X25519 (), nil
case 0x0050 :
return MLKEM768P256 (), nil
case 0x0051 :
return MLKEM1024P384 (), nil
default :
return nil , errors .New ("unsupported KEM" )
}
}
type PublicKey interface {
KEM () KEM
Bytes () []byte
encap () (sharedSecret, enc []byte , err error )
}
type PrivateKey interface {
KEM () KEM
Bytes () ([]byte , error )
PublicKey () PublicKey
decap (enc []byte ) (sharedSecret []byte , err error )
}
type dhKEM struct {
kdf KDF
id uint16
curve ecdh .Curve
Nsecret uint16
Nsk uint16
Nenc int
}
func (kem *dhKEM ) extractAndExpand (dhKey , kemContext []byte ) ([]byte , error ) {
suiteID := byteorder .BEAppendUint16 ([]byte ("KEM" ), kem .id )
eaePRK , err := kem .kdf .labeledExtract (suiteID , nil , "eae_prk" , dhKey )
if err != nil {
return nil , err
}
return kem .kdf .labeledExpand (suiteID , eaePRK , "shared_secret" , kemContext , kem .Nsecret )
}
func (kem *dhKEM ) ID () uint16 {
return kem .id
}
func (kem *dhKEM ) encSize () int {
return kem .Nenc
}
var dhKEMP256 = &dhKEM {HKDFSHA256 (), 0x0010 , ecdh .P256 (), 32 , 32 , 65 }
var dhKEMP384 = &dhKEM {HKDFSHA384 (), 0x0011 , ecdh .P384 (), 48 , 48 , 97 }
var dhKEMP521 = &dhKEM {HKDFSHA512 (), 0x0012 , ecdh .P521 (), 64 , 66 , 133 }
var dhKEMX25519 = &dhKEM {HKDFSHA256 (), 0x0020 , ecdh .X25519 (), 32 , 32 , 32 }
func DHKEM (curve ecdh .Curve ) KEM {
switch curve {
case ecdh .P256 ():
return dhKEMP256
case ecdh .P384 ():
return dhKEMP384
case ecdh .P521 ():
return dhKEMP521
case ecdh .X25519 ():
return dhKEMX25519
default :
return unsupportedCurveKEM {}
}
}
type unsupportedCurveKEM struct {}
func (unsupportedCurveKEM ) ID () uint16 {
return 0
}
func (unsupportedCurveKEM ) GenerateKey () (PrivateKey , error ) {
return nil , errors .New ("unsupported curve" )
}
func (unsupportedCurveKEM ) NewPublicKey ([]byte ) (PublicKey , error ) {
return nil , errors .New ("unsupported curve" )
}
func (unsupportedCurveKEM ) NewPrivateKey ([]byte ) (PrivateKey , error ) {
return nil , errors .New ("unsupported curve" )
}
func (unsupportedCurveKEM ) DeriveKeyPair ([]byte ) (PrivateKey , error ) {
return nil , errors .New ("unsupported curve" )
}
func (unsupportedCurveKEM ) encSize () int {
return 0
}
type dhKEMPublicKey struct {
kem *dhKEM
pub *ecdh .PublicKey
}
func NewDHKEMPublicKey (pub *ecdh .PublicKey ) (PublicKey , error ) {
kem , ok := DHKEM (pub .Curve ()).(*dhKEM )
if !ok {
return nil , errors .New ("unsupported curve" )
}
return &dhKEMPublicKey {
kem : kem ,
pub : pub ,
}, nil
}
func (kem *dhKEM ) NewPublicKey (data []byte ) (PublicKey , error ) {
pub , err := kem .curve .NewPublicKey (data )
if err != nil {
return nil , err
}
return NewDHKEMPublicKey (pub )
}
func (pk *dhKEMPublicKey ) KEM () KEM {
return pk .kem
}
func (pk *dhKEMPublicKey ) Bytes () []byte {
return pk .pub .Bytes ()
}
var testingOnlyGenerateKey func () *ecdh .PrivateKey
func (pk *dhKEMPublicKey ) encap () (sharedSecret []byte , encapPub []byte , err error ) {
privEph , err := pk .pub .Curve ().GenerateKey (rand .Reader )
if err != nil {
return nil , nil , err
}
if testingOnlyGenerateKey != nil {
privEph = testingOnlyGenerateKey ()
}
dhVal , err := privEph .ECDH (pk .pub )
if err != nil {
return nil , nil , err
}
encPubEph := privEph .PublicKey ().Bytes ()
encPubRecip := pk .pub .Bytes ()
kemContext := append (encPubEph , encPubRecip ...)
sharedSecret , err = pk .kem .extractAndExpand (dhVal , kemContext )
if err != nil {
return nil , nil , err
}
return sharedSecret , encPubEph , nil
}
type dhKEMPrivateKey struct {
kem *dhKEM
priv ecdh .KeyExchanger
}
func NewDHKEMPrivateKey (priv ecdh .KeyExchanger ) (PrivateKey , error ) {
kem , ok := DHKEM (priv .Curve ()).(*dhKEM )
if !ok {
return nil , errors .New ("unsupported curve" )
}
return &dhKEMPrivateKey {
kem : kem ,
priv : priv ,
}, nil
}
func (kem *dhKEM ) GenerateKey () (PrivateKey , error ) {
priv , err := kem .curve .GenerateKey (rand .Reader )
if err != nil {
return nil , err
}
return NewDHKEMPrivateKey (priv )
}
func (kem *dhKEM ) NewPrivateKey (ikm []byte ) (PrivateKey , error ) {
priv , err := kem .curve .NewPrivateKey (ikm )
if err != nil {
return nil , err
}
return NewDHKEMPrivateKey (priv )
}
func (kem *dhKEM ) DeriveKeyPair (ikm []byte ) (PrivateKey , error ) {
suiteID := byteorder .BEAppendUint16 ([]byte ("KEM" ), kem .id )
prk , err := kem .kdf .labeledExtract (suiteID , nil , "dkp_prk" , ikm )
if err != nil {
return nil , err
}
if kem == dhKEMX25519 {
s , err := kem .kdf .labeledExpand (suiteID , prk , "sk" , nil , kem .Nsk )
if err != nil {
return nil , err
}
return kem .NewPrivateKey (s )
}
var counter uint8
for counter < 4 {
s , err := kem .kdf .labeledExpand (suiteID , prk , "candidate" , []byte {counter }, kem .Nsk )
if err != nil {
return nil , err
}
if kem == dhKEMP521 {
s [0 ] &= 0x01
}
r , err := kem .NewPrivateKey (s )
if err != nil {
counter ++
continue
}
return r , nil
}
panic ("chance of four rejections is < 2^-128" )
}
func (k *dhKEMPrivateKey ) KEM () KEM {
return k .kem
}
func (k *dhKEMPrivateKey ) Bytes () ([]byte , error ) {
priv , ok := k .priv .(*ecdh .PrivateKey )
if !ok {
return nil , errors .New ("ecdh: private key does not support Bytes" )
}
if k .kem == dhKEMX25519 {
b := priv .Bytes ()
b [0 ] &= 248
b [31 ] &= 127
b [31 ] |= 64
return b , nil
}
return priv .Bytes (), nil
}
func (k *dhKEMPrivateKey ) PublicKey () PublicKey {
return &dhKEMPublicKey {
kem : k .kem ,
pub : k .priv .PublicKey (),
}
}
func (k *dhKEMPrivateKey ) decap (encPubEph []byte ) ([]byte , error ) {
pubEph , err := k .priv .Curve ().NewPublicKey (encPubEph )
if err != nil {
return nil , err
}
dhVal , err := k .priv .ECDH (pubEph )
if err != nil {
return nil , err
}
kemContext := append (slices .Clip (encPubEph ), k .priv .PublicKey ().Bytes ()...)
return k .kem .extractAndExpand (dhVal , kemContext )
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .