Skip to content
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

kem: add P-256 + Kyber768Draft00 hybrid #402

Merged
merged 1 commit into from Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
211 changes: 211 additions & 0 deletions kem/hybrid/ckem.go
@@ -0,0 +1,211 @@
package hybrid

// TODO move over to crypto/ecdh once we can assume Go 1.20.

import (
"bytes"
"crypto/elliptic"
cryptoRand "crypto/rand"
"crypto/subtle"
"math/big"

"github.com/cloudflare/circl/kem"
"github.com/cloudflare/circl/xof"
)

type cPublicKey struct {
scheme *cScheme
x, y *big.Int
}
type cPrivateKey struct {
scheme *cScheme
key []byte
}
type cScheme struct {
curve elliptic.Curve
}

var p256Kem = &cScheme{elliptic.P256()}

func (sch *cScheme) scSize() int {
return (sch.curve.Params().N.BitLen() + 7) / 8
}

func (sch *cScheme) ptSize() int {
return (sch.curve.Params().BitSize + 7) / 8
}

func (sch *cScheme) Name() string {
return sch.curve.Params().Name
}

func (sch *cScheme) PublicKeySize() int {
return 2*sch.ptSize() + 1
}

func (sch *cScheme) PrivateKeySize() int {
return sch.scSize()
}

func (sch *cScheme) SeedSize() int {
return sch.PrivateKeySize()
}

func (sch *cScheme) SharedKeySize() int {
return sch.ptSize()
}

func (sch *cScheme) CiphertextSize() int {
return sch.PublicKeySize()
}

func (sch *cScheme) EncapsulationSeedSize() int {
return sch.SeedSize()
}

func (sk *cPrivateKey) Scheme() kem.Scheme { return sk.scheme }
func (pk *cPublicKey) Scheme() kem.Scheme { return pk.scheme }

func (sk *cPrivateKey) MarshalBinary() ([]byte, error) {
ret := make([]byte, len(sk.key))
copy(ret, sk.key)
return ret, nil
}

func (sk *cPrivateKey) Equal(other kem.PrivateKey) bool {
oth, ok := other.(*cPrivateKey)
if !ok {
return false
}
if oth.scheme != sk.scheme {
return false
}
return subtle.ConstantTimeCompare(oth.key, sk.key) == 1
}

func (sk *cPrivateKey) Public() kem.PublicKey {
x, y := sk.scheme.curve.ScalarBaseMult(sk.key)
armfazh marked this conversation as resolved.
Show resolved Hide resolved
return &cPublicKey{
sk.scheme,
x,
y,
}
}

func (pk *cPublicKey) Equal(other kem.PublicKey) bool {
oth, ok := other.(*cPublicKey)
if !ok {
return false
}
if oth.scheme != pk.scheme {
return false
}
return oth.x.Cmp(pk.x) == 0 && oth.y.Cmp(pk.y) == 0
}

func (pk *cPublicKey) MarshalBinary() ([]byte, error) {
return elliptic.Marshal(pk.scheme.curve, pk.x, pk.y), nil
}

func (sch *cScheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
seed := make([]byte, sch.SeedSize())
_, err := cryptoRand.Read(seed)
if err != nil {
return nil, nil, err
}
pk, sk := sch.DeriveKeyPair(seed)
return pk, sk, nil
}

func (sch *cScheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) {
if len(seed) != sch.SeedSize() {
panic(kem.ErrSeedSize)
}
h := xof.SHAKE256.New()
_, _ = h.Write(seed)
buf := make([]byte, sch.PrivateKeySize())
_, _ = h.Read(buf)
rnd := bytes.NewReader(buf)
key, x, y, err := elliptic.GenerateKey(sch.curve, rnd)
Comment on lines +126 to +129
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that GenerateKey function can loop more than once, requesting more bytes from the reader.

Suggested change
buf := make([]byte, sch.PrivateKeySize())
_, _ = h.Read(buf)
rnd := bytes.NewReader(buf)
key, x, y, err := elliptic.GenerateKey(sch.curve, rnd)
key, x, y, err := elliptic.GenerateKey(sch.curve, h)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good point. h doesn't implement io.Reader though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(yet)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I can just use the xof package, nice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't address my comment.
you need to pass the xof object, and remove buf.

  1. xof already implements io.reader
  2. the issue is that buf has fixed size, but it is not guaranteed that reader will be called only one time. It could be called many times.

Copy link
Member Author

@bwesterb bwesterb Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not looking at the latest diff: there I fixed it. :)

if err != nil {
panic(err)
}

sk := cPrivateKey{scheme: sch, key: key}
pk := cPublicKey{scheme: sch, x: x, y: y}

return &pk, &sk
}

func (sch *cScheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
seed := make([]byte, sch.EncapsulationSeedSize())
_, err = cryptoRand.Read(seed)
if err != nil {
return
}
return sch.EncapsulateDeterministically(pk, seed)
}

func (pk *cPublicKey) X(sk *cPrivateKey) []byte {
if pk.scheme != sk.scheme {
panic(kem.ErrTypeMismatch)
}

sharedKey := make([]byte, pk.scheme.SharedKeySize())
xShared, _ := pk.scheme.curve.ScalarMult(pk.x, pk.y, sk.key)
xShared.FillBytes(sharedKey)
return sharedKey
}

func (sch *cScheme) EncapsulateDeterministically(
pk kem.PublicKey, seed []byte,
) (ct, ss []byte, err error) {
if len(seed) != sch.EncapsulationSeedSize() {
return nil, nil, kem.ErrSeedSize
}
pub, ok := pk.(*cPublicKey)
if !ok || pub.scheme != sch {
return nil, nil, kem.ErrTypeMismatch
}

pk2, sk2 := sch.DeriveKeyPair(seed)
ss = pub.X(sk2.(*cPrivateKey))
ct, _ = pk2.MarshalBinary()
return
}

func (sch *cScheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) {
if len(ct) != sch.CiphertextSize() {
return nil, kem.ErrCiphertextSize
}

priv, ok := sk.(*cPrivateKey)
if !ok || priv.scheme != sch {
return nil, kem.ErrTypeMismatch
}

pk, err := sch.UnmarshalBinaryPublicKey(ct)
if err != nil {
return nil, err
}

ss := pk.(*cPublicKey).X(priv)
return ss, nil
}

func (sch *cScheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) {
if len(buf) != sch.PublicKeySize() {
return nil, kem.ErrPubKeySize
}
x, y := elliptic.Unmarshal(sch.curve, buf)
return &cPublicKey{sch, x, y}, nil
}

func (sch *cScheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) {
if len(buf) != sch.PrivateKeySize() {
return nil, kem.ErrPrivKeySize
}
ret := cPrivateKey{sch, make([]byte, sch.PrivateKeySize())}
copy(ret.key, buf)
return &ret, nil
}
17 changes: 13 additions & 4 deletions kem/hybrid/hybrid.go
Expand Up @@ -42,18 +42,27 @@ import (

var ErrUninitialized = errors.New("public or private key not initialized")

// Returns the hybrid KEM of Kyber512 and X25519.
// Returns the hybrid KEM of Kyber512Draft00 and X25519.
func Kyber512X25519() kem.Scheme { return kyber512X }

// Returns the hybrid KEM of Kyber768 and X25519.
// Returns the hybrid KEM of Kyber768Draft00 and X25519.
func Kyber768X25519() kem.Scheme { return kyber768X }

// Returns the hybrid KEM of Kyber768 and X448.
// Returns the hybrid KEM of Kyber768Draft00 and X448.
func Kyber768X448() kem.Scheme { return kyber768X4 }

// Returns the hybrid KEM of Kyber1024 and X448.
// Returns the hybrid KEM of Kyber1024Draft00 and X448.
func Kyber1024X448() kem.Scheme { return kyber1024X }

// Returns the hybrid KEM of Kyber768Draft00 and P-256.
func P256Kyber768Draft00() kem.Scheme { return p256Kyber768Draft00 }

var p256Kyber768Draft00 kem.Scheme = &scheme{
"P256Kyber768Draft00",
p256Kem,
kyber768.Scheme(),
}

var kyber512X kem.Scheme = &scheme{
"Kyber512-X25519",
x25519Kem,
Expand Down
1 change: 1 addition & 0 deletions kem/schemes/schemes.go
Expand Up @@ -42,6 +42,7 @@ var allSchemes = [...]kem.Scheme{
hybrid.Kyber768X25519(),
hybrid.Kyber768X448(),
hybrid.Kyber1024X448(),
hybrid.P256Kyber768Draft00(),
}

var allSchemeNames map[string]kem.Scheme
Expand Down
1 change: 1 addition & 0 deletions kem/schemes/schemes_test.go
Expand Up @@ -159,4 +159,5 @@ func Example_schemes() {
// Kyber768-X25519
// Kyber768-X448
// Kyber1024-X448
// P256Kyber768Draft00
}