Рыба проекта. Минимальная функциональность
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package mlkem768
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/mlkem"
|
||||
"crypto/sha256"
|
||||
"crypto/sha3"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"git.tswf.io/infra/go-synapse-backupper/pkg/domain/crypto"
|
||||
)
|
||||
|
||||
// suiteID is the scheme identifier for ML-KEM-768.
|
||||
const suiteID uint16 = 0x0006
|
||||
|
||||
// ErrDecapsulationFailed is returned when ciphertext decapsulation fails,
|
||||
// typically due to a tampered or invalid ciphertext.
|
||||
var ErrDecapsulationFailed = errors.New("decapsulation failed")
|
||||
|
||||
// DefaultRegistry is the package-level registry for ML-KEM-768.
|
||||
var DefaultRegistry = crypto.NewRegistry()
|
||||
|
||||
// kemAdapter wraps the Go stdlib crypto/mlkem implementation to satisfy
|
||||
// the pkg/domain/crypto.KEM interface.
|
||||
type kemAdapter struct{}
|
||||
|
||||
// New creates a new KEM adapter instance.
|
||||
func New() crypto.KEM {
|
||||
return &kemAdapter{}
|
||||
}
|
||||
|
||||
// SchemeID returns the ML-KEM-768 scheme identifier (0x0006).
|
||||
func (k *kemAdapter) SchemeID() uint16 {
|
||||
return suiteID
|
||||
}
|
||||
|
||||
// GenerateKeyPair generates a new ML-KEM-768 key pair.
|
||||
//
|
||||
// Note: the rand parameter is part of the KEM interface contract but is
|
||||
// ignored here because the stdlib crypto/mlkem.GenerateKey768 uses
|
||||
// crypto/rand internally.
|
||||
func (k *kemAdapter) GenerateKeyPair(
|
||||
rand io.Reader,
|
||||
) (
|
||||
crypto.RecipientPub,
|
||||
crypto.RecipientPriv,
|
||||
error,
|
||||
) {
|
||||
dk, err := mlkem.GenerateKey768()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ek := dk.EncapsulationKey()
|
||||
rawPub := ek.Bytes()
|
||||
keyID := computeKeyID(rawPub)
|
||||
|
||||
pub := &pubKey{
|
||||
key: ek,
|
||||
keyID: keyID,
|
||||
}
|
||||
priv := &privKey{
|
||||
key: dk,
|
||||
keyID: keyID,
|
||||
}
|
||||
|
||||
return pub, priv, nil
|
||||
}
|
||||
|
||||
// Encapsulate generates a shared secret and ciphertext for the given public key.
|
||||
//
|
||||
// Note: the rand parameter is part of the KEM interface contract but is
|
||||
// ignored here because the stdlib (*EncapsulationKey768).Encapsulate uses
|
||||
// crypto/rand internally.
|
||||
//
|
||||
// CRITICAL: the stdlib returns (sharedKey, ciphertext) but this adapter
|
||||
// swaps the order to (ciphertext, sharedSecret) to match the domain KEM
|
||||
// interface contract.
|
||||
func (k *kemAdapter) Encapsulate(
|
||||
pub crypto.RecipientPub,
|
||||
rand io.Reader,
|
||||
) (
|
||||
ciphertext []byte,
|
||||
sharedSecret []byte,
|
||||
err error,
|
||||
) {
|
||||
p, ok := pub.(*pubKey)
|
||||
if !ok {
|
||||
raw := pub.Raw()
|
||||
ek, parseErr := mlkem.NewEncapsulationKey768(raw)
|
||||
if parseErr != nil {
|
||||
return nil, nil, fmt.Errorf("invalid public key for ML-KEM-768: %w", parseErr)
|
||||
}
|
||||
p = &pubKey{key: ek, keyID: computeKeyID(raw)}
|
||||
}
|
||||
|
||||
// stdlib returns (sharedKey, ciphertext); we swap to (ciphertext, sharedSecret).
|
||||
ss, ct := p.key.Encapsulate()
|
||||
|
||||
return ct, ss, nil
|
||||
}
|
||||
|
||||
// LoadPriv loads an ML-KEM-768 private key from raw bytes.
|
||||
func (k *kemAdapter) LoadPriv(
|
||||
raw []byte,
|
||||
) (
|
||||
crypto.RecipientPriv,
|
||||
error,
|
||||
) {
|
||||
dk, err := mlkem.NewDecapsulationKey768(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ML-KEM-768 private key: %w", err)
|
||||
}
|
||||
pubRaw := dk.EncapsulationKey().Bytes()
|
||||
return &privKey{key: dk, keyID: computeKeyID(pubRaw)}, nil
|
||||
}
|
||||
|
||||
// Decapsulate recovers the shared secret from a ciphertext using the private key.
|
||||
// For tampered ciphertexts, it returns ErrDecapsulationFailed.
|
||||
func (k *kemAdapter) Decapsulate(
|
||||
priv crypto.RecipientPriv,
|
||||
ciphertext []byte,
|
||||
) (
|
||||
sharedSecret []byte,
|
||||
err error,
|
||||
) {
|
||||
p, ok := priv.(*privKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid private key type for ML-KEM-768")
|
||||
}
|
||||
|
||||
ss, err := p.key.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
return nil, errors.Join(ErrDecapsulationFailed, err)
|
||||
}
|
||||
|
||||
// crypto/mlkem.Decapsulate implements implicit rejection: it returns a
|
||||
// pseudorandom shared secret instead of an error for invalid ciphertexts.
|
||||
// Perform explicit rejection by recomputing the implicit-rejection value
|
||||
// Kout = SHAKE256(z || ciphertext) and comparing it with the result.
|
||||
// If they match, the ciphertext was invalid.
|
||||
seed := p.key.Bytes()
|
||||
z := seed[32:]
|
||||
shake := sha3.NewSHAKE256()
|
||||
// sha3.ShakeHash.Write/Read never return an error; explicitly ignore to satisfy errcheck.
|
||||
_, _ = shake.Write(z)
|
||||
_, _ = shake.Write(ciphertext)
|
||||
computedKout := make([]byte, mlkem.SharedKeySize)
|
||||
_, _ = shake.Read(computedKout)
|
||||
|
||||
if bytes.Equal(ss, computedKout) {
|
||||
return nil, ErrDecapsulationFailed
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// pubKey wraps *mlkem.EncapsulationKey768 to satisfy crypto.RecipientPub.
|
||||
type pubKey struct {
|
||||
key *mlkem.EncapsulationKey768
|
||||
keyID []byte
|
||||
}
|
||||
|
||||
func (p *pubKey) SchemeID() uint16 { return suiteID }
|
||||
func (p *pubKey) KeyID() []byte { return p.keyID }
|
||||
func (p *pubKey) Raw() []byte { return p.key.Bytes() }
|
||||
|
||||
// privKey wraps *mlkem.DecapsulationKey768 to satisfy crypto.RecipientPriv.
|
||||
type privKey struct {
|
||||
key *mlkem.DecapsulationKey768
|
||||
keyID []byte
|
||||
}
|
||||
|
||||
func (p *privKey) SchemeID() uint16 { return suiteID }
|
||||
func (p *privKey) KeyID() []byte { return p.keyID }
|
||||
func (p *privKey) Raw() []byte { return p.key.Bytes() }
|
||||
|
||||
// computeKeyID derives the first 8 bytes of SHA-256 over the first 8 bytes of raw key material.
|
||||
func computeKeyID(raw []byte) []byte {
|
||||
h := sha256.Sum256(raw[:8])
|
||||
return h[:8]
|
||||
}
|
||||
|
||||
// init registers the ML-KEM-768 factory under suiteID 0x0006.
|
||||
func init() {
|
||||
_ = DefaultRegistry.Register(
|
||||
suiteID,
|
||||
func() crypto.KEM {
|
||||
return New()
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user