package hpke
import (
"bytes"
"crypto"
"crypto/ecdh"
"crypto/fips140"
"crypto/internal/fips140/drbg"
"crypto/internal/rand"
"crypto/mlkem"
"crypto/sha3"
"errors"
"internal/byteorder"
)
var mlkem768X25519 = &hybridKEM {
id : 0x647a ,
label : `\./` +
`/^\` ,
curve : ecdh .X25519 (),
curveSeedSize : 32 ,
curvePointSize : 32 ,
pqEncapsKeySize : mlkem .EncapsulationKeySize768 ,
pqCiphertextSize : mlkem .CiphertextSize768 ,
pqNewPublicKey : func (data []byte ) (crypto .Encapsulator , error ) {
return mlkem .NewEncapsulationKey768 (data )
},
pqNewPrivateKey : func (data []byte ) (crypto .Decapsulator , error ) {
return mlkem .NewDecapsulationKey768 (data )
},
}
func MLKEM768X25519 () KEM {
return mlkem768X25519
}
var mlkem768P256 = &hybridKEM {
id : 0x0050 ,
label : "MLKEM768-P256" ,
curve : ecdh .P256 (),
curveSeedSize : 32 ,
curvePointSize : 65 ,
pqEncapsKeySize : mlkem .EncapsulationKeySize768 ,
pqCiphertextSize : mlkem .CiphertextSize768 ,
pqNewPublicKey : func (data []byte ) (crypto .Encapsulator , error ) {
return mlkem .NewEncapsulationKey768 (data )
},
pqNewPrivateKey : func (data []byte ) (crypto .Decapsulator , error ) {
return mlkem .NewDecapsulationKey768 (data )
},
}
func MLKEM768P256 () KEM {
return mlkem768P256
}
var mlkem1024P384 = &hybridKEM {
id : 0x0051 ,
label : "MLKEM1024-P384" ,
curve : ecdh .P384 (),
curveSeedSize : 48 ,
curvePointSize : 97 ,
pqEncapsKeySize : mlkem .EncapsulationKeySize1024 ,
pqCiphertextSize : mlkem .CiphertextSize1024 ,
pqNewPublicKey : func (data []byte ) (crypto .Encapsulator , error ) {
return mlkem .NewEncapsulationKey1024 (data )
},
pqNewPrivateKey : func (data []byte ) (crypto .Decapsulator , error ) {
return mlkem .NewDecapsulationKey1024 (data )
},
}
func MLKEM1024P384 () KEM {
return mlkem1024P384
}
type hybridKEM struct {
id uint16
label string
curve ecdh .Curve
curveSeedSize int
curvePointSize int
pqEncapsKeySize int
pqCiphertextSize int
pqNewPublicKey func (data []byte ) (crypto .Encapsulator , error )
pqNewPrivateKey func (data []byte ) (crypto .Decapsulator , error )
}
func (kem *hybridKEM ) ID () uint16 {
return kem .id
}
func (kem *hybridKEM ) encSize () int {
return kem .pqCiphertextSize + kem .curvePointSize
}
func (kem *hybridKEM ) sharedSecret (ssPQ , ssT , ctT , ekT []byte ) []byte {
h := sha3 .New256 ()
h .Write (ssPQ )
h .Write (ssT )
h .Write (ctT )
h .Write (ekT )
h .Write ([]byte (kem .label ))
return h .Sum (nil )
}
type hybridPublicKey struct {
kem *hybridKEM
t *ecdh .PublicKey
pq crypto .Encapsulator
}
func NewHybridPublicKey (pq crypto .Encapsulator , t *ecdh .PublicKey ) (PublicKey , error ) {
switch t .Curve () {
case ecdh .X25519 ():
if _ , ok := pq .(*mlkem .EncapsulationKey768 ); !ok {
return nil , errors .New ("invalid PQ KEM for X25519 hybrid" )
}
return &hybridPublicKey {mlkem768X25519 , t , pq }, nil
case ecdh .P256 ():
if _ , ok := pq .(*mlkem .EncapsulationKey768 ); !ok {
return nil , errors .New ("invalid PQ KEM for P-256 hybrid" )
}
return &hybridPublicKey {mlkem768P256 , t , pq }, nil
case ecdh .P384 ():
if _ , ok := pq .(*mlkem .EncapsulationKey1024 ); !ok {
return nil , errors .New ("invalid PQ KEM for P-384 hybrid" )
}
return &hybridPublicKey {mlkem1024P384 , t , pq }, nil
default :
return nil , errors .New ("unsupported curve" )
}
}
func (kem *hybridKEM ) NewPublicKey (data []byte ) (PublicKey , error ) {
if len (data ) != kem .pqEncapsKeySize +kem .curvePointSize {
return nil , errors .New ("invalid public key size" )
}
pq , err := kem .pqNewPublicKey (data [:kem .pqEncapsKeySize ])
if err != nil {
return nil , err
}
var k *ecdh .PublicKey
fips140 .WithoutEnforcement (func () {
k , err = kem .curve .NewPublicKey (data [kem .pqEncapsKeySize :])
})
if err != nil {
return nil , err
}
return NewHybridPublicKey (pq , k )
}
func (pk *hybridPublicKey ) KEM () KEM {
return pk .kem
}
func (pk *hybridPublicKey ) Bytes () []byte {
return append (pk .pq .Bytes (), pk .t .Bytes ()...)
}
var testingOnlyEncapsulate func () (ss, ct []byte )
func (pk *hybridPublicKey ) encap () (sharedSecret []byte , encapPub []byte , err error ) {
var skE *ecdh .PrivateKey
fips140 .WithoutEnforcement (func () {
skE , err = pk .t .Curve ().GenerateKey (rand .Reader )
})
if err != nil {
return nil , nil , err
}
if testingOnlyGenerateKey != nil {
skE = testingOnlyGenerateKey ()
}
var ssT []byte
fips140 .WithoutEnforcement (func () {
ssT , err = skE .ECDH (pk .t )
})
if err != nil {
return nil , nil , err
}
ctT := skE .PublicKey ().Bytes ()
ssPQ , ctPQ := pk .pq .Encapsulate ()
if testingOnlyEncapsulate != nil {
ssPQ , ctPQ = testingOnlyEncapsulate ()
}
ss := pk .kem .sharedSecret (ssPQ , ssT , ctT , pk .t .Bytes ())
ct := append (ctPQ , ctT ...)
return ss , ct , nil
}
type hybridPrivateKey struct {
kem *hybridKEM
seed []byte
t ecdh .KeyExchanger
pq crypto .Decapsulator
}
func NewHybridPrivateKey (pq crypto .Decapsulator , t ecdh .KeyExchanger ) (PrivateKey , error ) {
return newHybridPrivateKey (pq , t , nil )
}
func (kem *hybridKEM ) GenerateKey () (PrivateKey , error ) {
seed := make ([]byte , 32 )
drbg .Read (seed )
return kem .NewPrivateKey (seed )
}
func (kem *hybridKEM ) NewPrivateKey (priv []byte ) (PrivateKey , error ) {
if len (priv ) != 32 {
return nil , errors .New ("hpke: invalid hybrid KEM secret length" )
}
s := sha3 .NewSHAKE256 ()
s .Write (priv )
seedPQ := make ([]byte , mlkem .SeedSize )
s .Read (seedPQ )
pq , err := kem .pqNewPrivateKey (seedPQ )
if err != nil {
return nil , err
}
seedT := make ([]byte , kem .curveSeedSize )
for {
s .Read (seedT )
var k ecdh .KeyExchanger
fips140 .WithoutEnforcement (func () {
k , err = kem .curve .NewPrivateKey (seedT )
})
if err != nil {
continue
}
return newHybridPrivateKey (pq , k , priv )
}
}
func newHybridPrivateKey(pq crypto .Decapsulator , t ecdh .KeyExchanger , seed []byte ) (PrivateKey , error ) {
switch t .Curve () {
case ecdh .X25519 ():
if _ , ok := pq .Encapsulator ().(*mlkem .EncapsulationKey768 ); !ok {
return nil , errors .New ("invalid PQ KEM for X25519 hybrid" )
}
return &hybridPrivateKey {mlkem768X25519 , bytes .Clone (seed ), t , pq }, nil
case ecdh .P256 ():
if _ , ok := pq .Encapsulator ().(*mlkem .EncapsulationKey768 ); !ok {
return nil , errors .New ("invalid PQ KEM for P-256 hybrid" )
}
return &hybridPrivateKey {mlkem768P256 , bytes .Clone (seed ), t , pq }, nil
case ecdh .P384 ():
if _ , ok := pq .Encapsulator ().(*mlkem .EncapsulationKey1024 ); !ok {
return nil , errors .New ("invalid PQ KEM for P-384 hybrid" )
}
return &hybridPrivateKey {mlkem1024P384 , bytes .Clone (seed ), t , pq }, nil
default :
return nil , errors .New ("unsupported curve" )
}
}
func (kem *hybridKEM ) DeriveKeyPair (ikm []byte ) (PrivateKey , error ) {
suiteID := byteorder .BEAppendUint16 ([]byte ("KEM" ), kem .id )
dk , err := SHAKE256 ().labeledDerive (suiteID , ikm , "DeriveKeyPair" , nil , 32 )
if err != nil {
return nil , err
}
return kem .NewPrivateKey (dk )
}
func (k *hybridPrivateKey ) KEM () KEM {
return k .kem
}
func (k *hybridPrivateKey ) Bytes () ([]byte , error ) {
if k .seed == nil {
return nil , errors .New ("private key seed not available" )
}
return k .seed , nil
}
func (k *hybridPrivateKey ) PublicKey () PublicKey {
return &hybridPublicKey {
kem : k .kem ,
t : k .t .PublicKey (),
pq : k .pq .Encapsulator (),
}
}
func (k *hybridPrivateKey ) decap (enc []byte ) ([]byte , error ) {
if len (enc ) != k .kem .pqCiphertextSize +k .kem .curvePointSize {
return nil , errors .New ("invalid encapsulated key size" )
}
ctPQ , ctT := enc [:k .kem .pqCiphertextSize ], enc [k .kem .pqCiphertextSize :]
ssPQ , err := k .pq .Decapsulate (ctPQ )
if err != nil {
return nil , err
}
var pub *ecdh .PublicKey
fips140 .WithoutEnforcement (func () {
pub , err = k .t .Curve ().NewPublicKey (ctT )
})
if err != nil {
return nil , err
}
var ssT []byte
fips140 .WithoutEnforcement (func () {
ssT , err = k .t .ECDH (pub )
})
if err != nil {
return nil , err
}
ss := k .kem .sharedSecret (ssPQ , ssT , ctT , k .t .PublicKey ().Bytes ())
return ss , nil
}
var mlkem768 = &mlkemKEM {
id : 0x0041 ,
ciphertextSize : mlkem .CiphertextSize768 ,
newPublicKey : func (data []byte ) (crypto .Encapsulator , error ) {
return mlkem .NewEncapsulationKey768 (data )
},
newPrivateKey : func (data []byte ) (crypto .Decapsulator , error ) {
return mlkem .NewDecapsulationKey768 (data )
},
generateKey : func () (crypto .Decapsulator , error ) {
return mlkem .GenerateKey768 ()
},
}
func MLKEM768 () KEM {
return mlkem768
}
var mlkem1024 = &mlkemKEM {
id : 0x0042 ,
ciphertextSize : mlkem .CiphertextSize1024 ,
newPublicKey : func (data []byte ) (crypto .Encapsulator , error ) {
return mlkem .NewEncapsulationKey1024 (data )
},
newPrivateKey : func (data []byte ) (crypto .Decapsulator , error ) {
return mlkem .NewDecapsulationKey1024 (data )
},
generateKey : func () (crypto .Decapsulator , error ) {
return mlkem .GenerateKey1024 ()
},
}
func MLKEM1024 () KEM {
return mlkem1024
}
type mlkemKEM struct {
id uint16
ciphertextSize int
newPublicKey func (data []byte ) (crypto .Encapsulator , error )
newPrivateKey func (data []byte ) (crypto .Decapsulator , error )
generateKey func () (crypto .Decapsulator , error )
}
func (kem *mlkemKEM ) ID () uint16 {
return kem .id
}
func (kem *mlkemKEM ) encSize () int {
return kem .ciphertextSize
}
type mlkemPublicKey struct {
kem *mlkemKEM
pq crypto .Encapsulator
}
func NewMLKEMPublicKey (pub crypto .Encapsulator ) (PublicKey , error ) {
switch pub .(type ) {
case *mlkem .EncapsulationKey768 :
return &mlkemPublicKey {mlkem768 , pub }, nil
case *mlkem .EncapsulationKey1024 :
return &mlkemPublicKey {mlkem1024 , pub }, nil
default :
return nil , errors .New ("unsupported public key type" )
}
}
func (kem *mlkemKEM ) NewPublicKey (data []byte ) (PublicKey , error ) {
pq , err := kem .newPublicKey (data )
if err != nil {
return nil , err
}
return NewMLKEMPublicKey (pq )
}
func (pk *mlkemPublicKey ) KEM () KEM {
return pk .kem
}
func (pk *mlkemPublicKey ) Bytes () []byte {
return pk .pq .Bytes ()
}
func (pk *mlkemPublicKey ) encap () (sharedSecret []byte , encapPub []byte , err error ) {
ss , ct := pk .pq .Encapsulate ()
if testingOnlyEncapsulate != nil {
ss , ct = testingOnlyEncapsulate ()
}
return ss , ct , nil
}
type mlkemPrivateKey struct {
kem *mlkemKEM
pq crypto .Decapsulator
}
func NewMLKEMPrivateKey (priv crypto .Decapsulator ) (PrivateKey , error ) {
switch priv .Encapsulator ().(type ) {
case *mlkem .EncapsulationKey768 :
return &mlkemPrivateKey {mlkem768 , priv }, nil
case *mlkem .EncapsulationKey1024 :
return &mlkemPrivateKey {mlkem1024 , priv }, nil
default :
return nil , errors .New ("unsupported public key type" )
}
}
func (kem *mlkemKEM ) GenerateKey () (PrivateKey , error ) {
pq , err := kem .generateKey ()
if err != nil {
return nil , err
}
return NewMLKEMPrivateKey (pq )
}
func (kem *mlkemKEM ) NewPrivateKey (priv []byte ) (PrivateKey , error ) {
pq , err := kem .newPrivateKey (priv )
if err != nil {
return nil , err
}
return NewMLKEMPrivateKey (pq )
}
func (kem *mlkemKEM ) DeriveKeyPair (ikm []byte ) (PrivateKey , error ) {
suiteID := byteorder .BEAppendUint16 ([]byte ("KEM" ), kem .id )
dk , err := SHAKE256 ().labeledDerive (suiteID , ikm , "DeriveKeyPair" , nil , 64 )
if err != nil {
return nil , err
}
return kem .NewPrivateKey (dk )
}
func (k *mlkemPrivateKey ) KEM () KEM {
return k .kem
}
func (k *mlkemPrivateKey ) Bytes () ([]byte , error ) {
pq , ok := k .pq .(interface {
Bytes () []byte
})
if !ok {
return nil , errors .New ("private key seed not available" )
}
return pq .Bytes (), nil
}
func (k *mlkemPrivateKey ) PublicKey () PublicKey {
return &mlkemPublicKey {
kem : k .kem ,
pq : k .pq .Encapsulator (),
}
}
func (k *mlkemPrivateKey ) decap (enc []byte ) ([]byte , error ) {
return k .pq .Decapsulate (enc )
}
The pages are generated with Golds v0.8.3-preview . (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 .