From 90f2a3efe4fe61ffa83ca0dfa66a3fc0a498419a Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Mon, 13 Feb 2023 12:53:19 +0100 Subject: [PATCH] kem: add P-256 + Kyber768Draft00 hybrid --- kem/hybrid/ckem.go | 211 ++++++++++++++++++++++++++++++++++++ kem/hybrid/hybrid.go | 17 ++- kem/schemes/schemes.go | 1 + kem/schemes/schemes_test.go | 1 + 4 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 kem/hybrid/ckem.go diff --git a/kem/hybrid/ckem.go b/kem/hybrid/ckem.go new file mode 100644 index 00000000..118a44a8 --- /dev/null +++ b/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) + 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) + 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 +} diff --git a/kem/hybrid/hybrid.go b/kem/hybrid/hybrid.go index f0384278..be8251c7 100644 --- a/kem/hybrid/hybrid.go +++ b/kem/hybrid/hybrid.go @@ -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, diff --git a/kem/schemes/schemes.go b/kem/schemes/schemes.go index c6c3c4a3..a33e7b96 100644 --- a/kem/schemes/schemes.go +++ b/kem/schemes/schemes.go @@ -42,6 +42,7 @@ var allSchemes = [...]kem.Scheme{ hybrid.Kyber768X25519(), hybrid.Kyber768X448(), hybrid.Kyber1024X448(), + hybrid.P256Kyber768Draft00(), } var allSchemeNames map[string]kem.Scheme diff --git a/kem/schemes/schemes_test.go b/kem/schemes/schemes_test.go index 91fd1fea..e41840b3 100644 --- a/kem/schemes/schemes_test.go +++ b/kem/schemes/schemes_test.go @@ -159,4 +159,5 @@ func Example_schemes() { // Kyber768-X25519 // Kyber768-X448 // Kyber1024-X448 + // P256Kyber768Draft00 }