"CiphertextSize768": "CiphertextSize1024",
"EncapsulationKeySize768": "EncapsulationKeySize1024",
+ "decapsulationKeySize768": "decapsulationKeySize1024",
"encryptionKey": "encryptionKey1024",
"decryptionKey": "decryptionKey1024",
"kemEncaps": "kemEncaps1024",
"pkeEncrypt": "pkeEncrypt1024",
- "DecapsulationKey768": "DecapsulationKey1024",
- "NewDecapsulationKey768": "NewDecapsulationKey1024",
- "newKeyFromSeed": "newKeyFromSeed1024",
+ "DecapsulationKey768": "DecapsulationKey1024",
+ "NewDecapsulationKey768": "NewDecapsulationKey1024",
+ "TestingOnlyNewDecapsulationKey768": "TestingOnlyNewDecapsulationKey1024",
+ "newKeyFromSeed": "newKeyFromSeed1024",
+ "TestingOnlyExpandedBytes768": "TestingOnlyExpandedBytes1024",
"kemDecaps": "kemDecaps1024",
"pkeDecrypt": "pkeDecrypt1024",
package mlkem
import (
+ "bytes"
"crypto/internal/fips140"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/sha3"
return b[:]
}
+// TestingOnlyExpandedBytes1024 returns the decapsulation key as a byte slice
+// using the full expanded NIST encoding.
+//
+// This should only be used for ACVP testing. For all other purposes prefer
+// the Bytes method that returns the (much smaller) seed.
+func TestingOnlyExpandedBytes1024(dk *DecapsulationKey1024) []byte {
+ b := make([]byte, 0, decapsulationKeySize1024)
+
+ // ByteEncode₁₂(s)
+ for i := range dk.s {
+ b = polyByteEncode(b, dk.s[i])
+ }
+
+ // ByteEncode₁₂(t) || ρ
+ for i := range dk.t {
+ b = polyByteEncode(b, dk.t[i])
+ }
+ b = append(b, dk.ρ[:]...)
+
+ // H(ek) || z
+ b = append(b, dk.h[:]...)
+ b = append(b, dk.z[:]...)
+
+ return b
+}
+
// EncapsulationKey returns the public encapsulation key necessary to produce
// ciphertexts.
func (dk *DecapsulationKey1024) EncapsulationKey() *EncapsulationKey1024 {
return dk, nil
}
+// TestingOnlyNewDecapsulationKey1024 parses a decapsulation key from its expanded NIST format.
+//
+// Bytes() must not be called on the returned key, as it will not produce the
+// original seed.
+//
+// This function should only be used for ACVP testing. Prefer NewDecapsulationKey1024 for all
+// other purposes.
+func TestingOnlyNewDecapsulationKey1024(b []byte) (*DecapsulationKey1024, error) {
+ if len(b) != decapsulationKeySize1024 {
+ return nil, errors.New("mlkem: invalid NIST decapsulation key length")
+ }
+
+ dk := &DecapsulationKey1024{}
+ for i := range dk.s {
+ var err error
+ dk.s[i], err = polyByteDecode[nttElement](b[:encodingSize12])
+ if err != nil {
+ return nil, errors.New("mlkem: invalid secret key encoding")
+ }
+ b = b[encodingSize12:]
+ }
+
+ ek, err := NewEncapsulationKey1024(b[:EncapsulationKeySize1024])
+ if err != nil {
+ return nil, err
+ }
+ dk.ρ = ek.ρ
+ dk.h = ek.h
+ dk.encryptionKey1024 = ek.encryptionKey1024
+ b = b[EncapsulationKeySize1024:]
+
+ if !bytes.Equal(dk.h[:], b[:32]) {
+ return nil, errors.New("mlkem: inconsistent H(ek) in encoded bytes")
+ }
+ b = b[32:]
+
+ copy(dk.z[:], b)
+
+ // Generate a random d value for use in Bytes(). This is a safety mechanism
+ // that avoids returning a broken key vs a random key if this function is
+ // called in contravention of the TestingOnlyNewDecapsulationKey1024 function
+ // comment advising against it.
+ drbg.Read(dk.d[:])
+
+ return dk, nil
+}
+
// kemKeyGen1024 generates a decapsulation key.
//
// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and
//go:generate go run generate1024.go -input mlkem768.go -output mlkem1024.go
import (
+ "bytes"
"crypto/internal/fips140"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/sha3"
CiphertextSize768 = k*encodingSize10 + encodingSize4
EncapsulationKeySize768 = k*encodingSize12 + 32
+ decapsulationKeySize768 = k*encodingSize12 + EncapsulationKeySize768 + 32 + 32
)
// ML-KEM-1024 parameters.
CiphertextSize1024 = k1024*encodingSize11 + encodingSize5
EncapsulationKeySize1024 = k1024*encodingSize12 + 32
+ decapsulationKeySize1024 = k1024*encodingSize12 + EncapsulationKeySize1024 + 32 + 32
)
// A DecapsulationKey768 is the secret key used to decapsulate a shared key from a
return b[:]
}
+// TestingOnlyExpandedBytes768 returns the decapsulation key as a byte slice
+// using the full expanded NIST encoding.
+//
+// This should only be used for ACVP testing. For all other purposes prefer
+// the Bytes method that returns the (much smaller) seed.
+func TestingOnlyExpandedBytes768(dk *DecapsulationKey768) []byte {
+ b := make([]byte, 0, decapsulationKeySize768)
+
+ // ByteEncode₁₂(s)
+ for i := range dk.s {
+ b = polyByteEncode(b, dk.s[i])
+ }
+
+ // ByteEncode₁₂(t) || ρ
+ for i := range dk.t {
+ b = polyByteEncode(b, dk.t[i])
+ }
+ b = append(b, dk.ρ[:]...)
+
+ // H(ek) || z
+ b = append(b, dk.h[:]...)
+ b = append(b, dk.z[:]...)
+
+ return b
+}
+
// EncapsulationKey returns the public encapsulation key necessary to produce
// ciphertexts.
func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768 {
return dk, nil
}
+// TestingOnlyNewDecapsulationKey768 parses a decapsulation key from its expanded NIST format.
+//
+// Bytes() must not be called on the returned key, as it will not produce the
+// original seed.
+//
+// This function should only be used for ACVP testing. Prefer NewDecapsulationKey768 for all
+// other purposes.
+func TestingOnlyNewDecapsulationKey768(b []byte) (*DecapsulationKey768, error) {
+ if len(b) != decapsulationKeySize768 {
+ return nil, errors.New("mlkem: invalid NIST decapsulation key length")
+ }
+
+ dk := &DecapsulationKey768{}
+ for i := range dk.s {
+ var err error
+ dk.s[i], err = polyByteDecode[nttElement](b[:encodingSize12])
+ if err != nil {
+ return nil, errors.New("mlkem: invalid secret key encoding")
+ }
+ b = b[encodingSize12:]
+ }
+
+ ek, err := NewEncapsulationKey768(b[:EncapsulationKeySize768])
+ if err != nil {
+ return nil, err
+ }
+ dk.ρ = ek.ρ
+ dk.h = ek.h
+ dk.encryptionKey = ek.encryptionKey
+ b = b[EncapsulationKeySize768:]
+
+ if !bytes.Equal(dk.h[:], b[:32]) {
+ return nil, errors.New("mlkem: inconsistent H(ek) in encoded bytes")
+ }
+ b = b[32:]
+
+ copy(dk.z[:], b)
+
+ // Generate a random d value for use in Bytes(). This is a safety mechanism
+ // that avoids returning a broken key vs a random key if this function is
+ // called in contravention of the TestingOnlyNewDecapsulationKey768 function
+ // comment advising against it.
+ drbg.Read(dk.d[:])
+
+ return dk, nil
+}
+
// kemKeyGen generates a decapsulation key.
//
// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and
{"algorithm":"HMAC-SHA3-384","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":384,"min":32}],"revision":"1.0"},
{"algorithm":"HMAC-SHA3-512","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":512,"min":32}],"revision":"1.0"},
- {"algorithm":"PBKDF","capabilities":[{"iterationCount":[{"min":1,"max":10000,"increment":1}],"keyLen":[{"min":112,"max":4096,"increment":8}],"passwordLen":[{"min":8,"max":64,"increment":1}],"saltLen":[{"min":128,"max":512,"increment":8}],"hmacAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}],"revision":"1.0"}
-]
\ No newline at end of file
+ {"algorithm":"PBKDF","capabilities":[{"iterationCount":[{"min":1,"max":10000,"increment":1}],"keyLen":[{"min":112,"max":4096,"increment":8}],"passwordLen":[{"min":8,"max":64,"increment":1}],"saltLen":[{"min":128,"max":512,"increment":8}],"hmacAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}],"revision":"1.0"},
+
+ {"algorithm":"ML-KEM","mode":"keyGen","revision":"FIPS203","parameterSets":["ML-KEM-768","ML-KEM-1024"]},
+ {"algorithm":"ML-KEM","mode":"encapDecap","revision":"FIPS203","parameterSets":["ML-KEM-768","ML-KEM-1024"],"functions":["encapsulation","decapsulation"]}
+]
{"Wrapper": "go", "In": "vectors/HMAC-SHA3-384.bz2", "Out": "expected/HMAC-SHA3-384.bz2"},
{"Wrapper": "go", "In": "vectors/HMAC-SHA3-512.bz2", "Out": "expected/HMAC-SHA3-512.bz2"},
- {"Wrapper": "go", "In": "vectors/PBKDF.bz2", "Out": "expected/PBKDF.bz2"}
+ {"Wrapper": "go", "In": "vectors/PBKDF.bz2", "Out": "expected/PBKDF.bz2"},
+
+ {"Wrapper": "go", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"}
]
\ No newline at end of file
"crypto/internal/cryptotest"
"crypto/internal/fips140"
"crypto/internal/fips140/hmac"
+ "crypto/internal/fips140/mlkem"
"crypto/internal/fips140/pbkdf2"
"crypto/internal/fips140/sha256"
"crypto/internal/fips140/sha3"
// https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#section-7
// PBKDF2 algorithm capabilities:
// https://pages.nist.gov/ACVP/draft-celi-acvp-pbkdf.html#section-7.3
+ // ML-KEM algorithm capabilities:
+ // https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html#section-7.3
//go:embed acvp_capabilities.json
capabilitiesJson []byte
"HMAC-SHA3-512": cmdHmacAft(func() fips140.Hash { return sha3.New512() }),
"PBKDF": cmdPbkdf(),
+
+ "ML-KEM-768/keyGen": cmdMlKem768KeyGenAft(),
+ "ML-KEM-768/encap": cmdMlKem768EncapAft(),
+ "ML-KEM-768/decap": cmdMlKem768DecapAft(),
+ "ML-KEM-1024/keyGen": cmdMlKem1024KeyGenAft(),
+ "ML-KEM-1024/encap": cmdMlKem1024EncapAft(),
+ "ML-KEM-1024/decap": cmdMlKem1024DecapAft(),
}
)
return h, nil
}
+func cmdMlKem768KeyGenAft() command {
+ return command{
+ requiredArgs: 1, // Seed
+ handler: func(args [][]byte) ([][]byte, error) {
+ seed := args[0]
+
+ dk, err := mlkem.NewDecapsulationKey768(seed)
+ if err != nil {
+ return nil, fmt.Errorf("generating ML-KEM 768 decapsulation key: %w", err)
+ }
+
+ // Important: we must return the full encoding of dk, not the seed.
+ return [][]byte{dk.EncapsulationKey().Bytes(), mlkem.TestingOnlyExpandedBytes768(dk)}, nil
+ },
+ }
+}
+
+func cmdMlKem768EncapAft() command {
+ return command{
+ requiredArgs: 2, // Public key, entropy
+ handler: func(args [][]byte) ([][]byte, error) {
+ pk := args[0]
+ entropy := args[1]
+
+ ek, err := mlkem.NewEncapsulationKey768(pk)
+ if err != nil {
+ return nil, fmt.Errorf("generating ML-KEM 768 encapsulation key: %w", err)
+ }
+
+ if len(entropy) != 32 {
+ return nil, fmt.Errorf("wrong entropy length: got %d, want 32", len(entropy))
+ }
+
+ sharedKey, ct := ek.EncapsulateInternal((*[32]byte)(entropy[:32]))
+
+ return [][]byte{ct, sharedKey}, nil
+ },
+ }
+}
+
+func cmdMlKem768DecapAft() command {
+ return command{
+ requiredArgs: 2, // Private key, ciphertext
+ handler: func(args [][]byte) ([][]byte, error) {
+ pk := args[0]
+ ct := args[1]
+
+ dk, err := mlkem.TestingOnlyNewDecapsulationKey768(pk)
+ if err != nil {
+ return nil, fmt.Errorf("generating ML-KEM 768 decapsulation key: %w", err)
+ }
+
+ sharedKey, err := dk.Decapsulate(ct)
+ if err != nil {
+ return nil, fmt.Errorf("decapsulating ML-KEM 768 ciphertext: %w", err)
+ }
+
+ return [][]byte{sharedKey}, nil
+ },
+ }
+}
+
+func cmdMlKem1024KeyGenAft() command {
+ return command{
+ requiredArgs: 1, // Seed
+ handler: func(args [][]byte) ([][]byte, error) {
+ seed := args[0]
+
+ dk, err := mlkem.NewDecapsulationKey1024(seed)
+ if err != nil {
+ return nil, fmt.Errorf("generating ML-KEM 1024 decapsulation key: %w", err)
+ }
+
+ // Important: we must return the full encoding of dk, not the seed.
+ return [][]byte{dk.EncapsulationKey().Bytes(), mlkem.TestingOnlyExpandedBytes1024(dk)}, nil
+ },
+ }
+}
+
+func cmdMlKem1024EncapAft() command {
+ return command{
+ requiredArgs: 2, // Public key, entropy
+ handler: func(args [][]byte) ([][]byte, error) {
+ pk := args[0]
+ entropy := args[1]
+
+ ek, err := mlkem.NewEncapsulationKey1024(pk)
+ if err != nil {
+ return nil, fmt.Errorf("generating ML-KEM 1024 encapsulation key: %w", err)
+ }
+
+ if len(entropy) != 32 {
+ return nil, fmt.Errorf("wrong entropy length: got %d, want 32", len(entropy))
+ }
+
+ sharedKey, ct := ek.EncapsulateInternal((*[32]byte)(entropy[:32]))
+
+ return [][]byte{ct, sharedKey}, nil
+ },
+ }
+}
+
+func cmdMlKem1024DecapAft() command {
+ return command{
+ requiredArgs: 2, // Private key, ciphertext
+ handler: func(args [][]byte) ([][]byte, error) {
+ pk := args[0]
+ ct := args[1]
+
+ dk, err := mlkem.TestingOnlyNewDecapsulationKey1024(pk)
+ if err != nil {
+ return nil, fmt.Errorf("generating ML-KEM 1024 decapsulation key: %w", err)
+ }
+
+ sharedKey, err := dk.Decapsulate(ct)
+ if err != nil {
+ return nil, fmt.Errorf("decapsulating ML-KEM 1024 ciphertext: %w", err)
+ }
+
+ return [][]byte{sharedKey}, nil
+ },
+ }
+}
+
func TestACVP(t *testing.T) {
testenv.SkipIfShortAndSlow(t)
const (
bsslModule = "boringssl.googlesource.com/boringssl.git"
- bsslVersion = "v0.0.0-20241015160643-2587c4974dbe"
+ bsslVersion = "v0.0.0-20241218033850-ca3146c56300"
goAcvpModule = "github.com/cpu/go-acvp"
- goAcvpVersion = "v0.0.0-20241011151719-6e0509dcb7ce"
+ goAcvpVersion = "v0.0.0-20250102201911-6839fc40f9f8"
)
// In crypto/tls/bogo_shim_test.go the test is skipped if run on a builder with runtime.GOOS == "windows"