diff --git a/internal/kmac/kmac.go b/internal/kmac/kmac.go deleted file mode 100644 index 695ff5e42..000000000 --- a/internal/kmac/kmac.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package kmac provides function for creating KMAC instances. -// KMAC is a Message Authentication Code that based on SHA-3 and -// specified in NIST Special Publication 800-185, "SHA-3 Derived Functions: -// cSHAKE, KMAC, TupleHash and ParallelHash" [1] -// -// [1] https://doi.org/10.6028/NIST.SP.800-185 -package kmac - -import ( - "encoding/binary" - "fmt" - "hash" - - "golang.org/x/crypto/sha3" -) - -const ( - // According to [1]: - // "When used as a MAC, applications of this Recommendation shall - // not select an output length L that is less than 32 bits, and - // shall only select an output length less than 64 bits after a - // careful risk analysis is performed." - // 64 bits was selected for safety. - kmacMinimumTagSize = 8 - rate128 = 168 - rate256 = 136 -) - -// KMAC specific context -type kmac struct { - sha3.ShakeHash // cSHAKE context and Read/Write operations - tagSize int // tag size - // initBlock is the KMAC specific initialization set of bytes. It is initialized - // by newKMAC function and stores the key, encoded by the method specified in 3.3 of [1]. - // It is stored here in order for Reset() to be able to put context into - // initial state. - initBlock []byte - rate int -} - -// NewKMAC128 returns a new KMAC hash providing 128 bits of security using -// the given key, which must have 16 bytes or more, generating the given tagSize -// bytes output and using the given customizationString. -// Note that unlike other hash implementations in the standard library, -// the returned Hash does not implement encoding.BinaryMarshaler -// or encoding.BinaryUnmarshaler. -func NewKMAC128(key []byte, tagSize int, customizationString []byte) (h hash.Hash, err error) { - c := sha3.NewCShake128([]byte("KMAC"), customizationString) - h = newKMAC(key, tagSize, c, rate128) - if len(key) < 16 { - return h, fmt.Errorf("kmac: key is too short with %d bytes: should be at least %d", len(key), 16) - } - return h, nil -} - -// NewKMAC256 returns a new KMAC hash providing 256 bits of security using -// the given key, which must have 32 bytes or more, generating the given tagSize -// bytes output and using the given customizationString. -// Note that unlike other hash implementations in the standard library, -// the returned Hash does not implement encoding.BinaryMarshaler -// or encoding.BinaryUnmarshaler. -func NewKMAC256(key []byte, tagSize int, customizationString []byte) (h hash.Hash, err error) { - c := sha3.NewCShake256([]byte("KMAC"), customizationString) - h = newKMAC(key, tagSize, c, rate256) - if len(key) < 32 { - return h, fmt.Errorf("kmac: key is too short with %d bytes: should be at least %d", len(key), 32) - } - return h, nil -} - -func newKMAC(key []byte, tagSize int, c sha3.ShakeHash, rate int) hash.Hash { - if tagSize < kmacMinimumTagSize { - panic("tagSize is too small") - } - k := &kmac{ShakeHash: c, tagSize: tagSize, rate: rate} - // leftEncode returns max 9 bytes - k.initBlock = make([]byte, 0, 9+len(key)) - k.initBlock = append(k.initBlock, leftEncode(uint64(len(key)*8))...) - k.initBlock = append(k.initBlock, key...) - k.Write(bytepad(k.initBlock, k.BlockSize())) - return k -} - -// Reset resets the hash to initial state. -func (k *kmac) Reset() { - k.ShakeHash.Reset() - k.Write(bytepad(k.initBlock, k.BlockSize())) -} - -// BlockSize returns the hash block size. -func (k *kmac) BlockSize() int { - return k.rate -} - -// Size returns the tag size. -func (k *kmac) Size() int { - return k.tagSize -} - -// Sum appends the current KMAC to b and returns the resulting slice. -// It does not change the underlying hash state. -func (k *kmac) Sum(b []byte) []byte { - dup := k.ShakeHash.Clone() - dup.Write(rightEncode(uint64(k.tagSize * 8))) - hash := make([]byte, k.tagSize) - dup.Read(hash) - return append(b, hash...) -} - -func bytepad(input []byte, w int) []byte { - // leftEncode always returns max 9 bytes - buf := make([]byte, 0, 9+len(input)+w) - buf = append(buf, leftEncode(uint64(w))...) - buf = append(buf, input...) - padlen := w - (len(buf) % w) - return append(buf, make([]byte, padlen)...) -} - -func leftEncode(value uint64) []byte { - var b [9]byte - binary.BigEndian.PutUint64(b[1:], value) - // Trim all but last leading zero bytes - i := byte(1) - for i < 8 && b[i] == 0 { - i++ - } - // Prepend number of encoded bytes - b[i-1] = 9 - i - return b[i-1:] -} - -func rightEncode(value uint64) []byte { - var b [9]byte - binary.BigEndian.PutUint64(b[:8], value) - // Trim all but last leading zero bytes - i := byte(0) - for i < 7 && b[i] == 0 { - i++ - } - // Append number of encoded bytes - b[8] = 8 - i - return b[i:] -} diff --git a/internal/kmac/kmac_test.go b/internal/kmac/kmac_test.go deleted file mode 100644 index 07bff8c1c..000000000 --- a/internal/kmac/kmac_test.go +++ /dev/null @@ -1,142 +0,0 @@ -/// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package kmac_test implements a vector-based test suite for the cSHAKE KMAC implementation -package kmac_test - -import ( - "bytes" - "encoding/hex" - "fmt" - "hash" - "testing" - - "github.com/ProtonMail/go-crypto/internal/kmac" -) - -// Test vectors from -// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/cSHAKE_samples.pdf -var kmacTests = []struct { - security int - key, data, customization, tag string -}{ - { - 128, - "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", - "00010203", - "", - "E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E", - }, - { - 128, - "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", - "00010203", - "My Tagged Application", - "3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5", - }, - { - 128, - "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7", - "My Tagged Application", - "1F5B4E6CCA02209E0DCB5CA635B89A15E271ECC760071DFD805FAA38F9729230", - }, - { - 256, - "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", - "00010203", - "My Tagged Application", - "20C570C31346F703C9AC36C61C03CB64C3970D0CFC787E9B79599D273A68D2F7F69D4CC3DE9D104A351689F27CF6F5951F0103F33F4F24871024D9C27773A8DD", - }, - { - 256, - "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7", - "", - "75358CF39E41494E949707927CEE0AF20A3FF553904C86B08F21CC414BCFD691589D27CF5E15369CBBFF8B9A4C2EB17800855D0235FF635DA82533EC6B759B69", - }, - { - 256, - "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", - "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7", - "My Tagged Application", - "B58618F71F92E1D56C1B8C55DDD7CD188B97B4CA4D99831EB2699A837DA2E4D970FBACFDE50033AEA585F1A2708510C32D07880801BD182898FE476876FC8965", - }, -} - -func TestKMAC(t *testing.T) { - for i, test := range kmacTests { - key, err := hex.DecodeString(test.key) - if err != nil { - t.Errorf("error decoding KAT: %s", err) - } - tag, err := hex.DecodeString(test.tag) - if err != nil { - t.Errorf("error decoding KAT: %s", err) - } - var mac hash.Hash - if test.security == 128 { - mac, err = kmac.NewKMAC128(key, len(tag), []byte(test.customization)) - } else { - mac, err = kmac.NewKMAC256(key, len(tag), []byte(test.customization)) - } - if err != nil { - t.Fatal(err) - } - data, err := hex.DecodeString(test.data) - if err != nil { - t.Errorf("error decoding KAT: %s", err) - } - mac.Write(data) - computedTag := mac.Sum(nil) - if !bytes.Equal(tag, computedTag) { - t.Errorf("#%d: got %x, want %x", i, tag, computedTag) - } - if mac.Size() != len(tag) { - t.Errorf("#%d: Size() = %x, want %x", i, mac.Size(), len(tag)) - } - // Test if it works after Reset. - mac.Reset() - mac.Write(data) - computedTag = mac.Sum(nil) - if !bytes.Equal(tag, computedTag) { - t.Errorf("#%d: got %x, want %x", i, tag, computedTag) - } - // Test if Sum does not change state. - if len(data) > 1 { - mac.Reset() - mac.Write(data[0:1]) - mac.Sum(nil) - mac.Write(data[1:]) - computedTag = mac.Sum(nil) - if !bytes.Equal(tag, computedTag) { - t.Errorf("#%d: got %x, want %x", i, tag, computedTag) - } - } - } -} -func ExampleNewKMAC256() { - key := []byte("this is a secret key; you should generate a strong random key that's at least 32 bytes long") - tag := make([]byte, 16) - msg := []byte("The quick brown fox jumps over the lazy dog") - // Example 1: Simple KMAC - k, err := kmac.NewKMAC256(key, len(tag), []byte("Partition1")) - if err != nil { - panic(err) - } - k.Write(msg) - k.Sum(tag[:0]) - fmt.Println(hex.EncodeToString(tag)) - // Example 2: Different customization string produces different digest - k, err = kmac.NewKMAC256(key, 16, []byte("Partition2")) - if err != nil { - panic(err) - } - k.Write(msg) - k.Sum(tag[:0]) - fmt.Println(hex.EncodeToString(tag)) - // Output: - //3814d78758add078334b8ab9e5c4f942 - //3762371e99e1e01ab17742b95c0360da -} diff --git a/openpgp/mlkem_ecdh/mlkem_ecdh.go b/openpgp/mlkem_ecdh/mlkem_ecdh.go index 686a25528..a8fafb655 100644 --- a/openpgp/mlkem_ecdh/mlkem_ecdh.go +++ b/openpgp/mlkem_ecdh/mlkem_ecdh.go @@ -7,7 +7,6 @@ import ( "fmt" "io" - "github.com/ProtonMail/go-crypto/internal/kmac" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" "golang.org/x/crypto/sha3" @@ -19,7 +18,6 @@ import ( const ( maxSessionKeyLength = 64 - domainSeparator = "OpenPGPCompositeKDFv1" MlKemSeedLen = 64 ) @@ -140,8 +138,8 @@ func Decrypt(priv *PrivateKey, kEphemeral, ecEphemeral, ciphertext []byte) (msg return keywrap.Unwrap(kek, ciphertext) } -// buildKey implements the composite KDF as specified in -// https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-05.html#name-key-combiner +// buildKey implements the composite KDF 2a from +// https://mailarchive.ietf.org/arch/msg/openpgp/NMTCy707LICtxIhP3Xt1U5C8MF0/ func buildKey(pub *PublicKey, eccSecretPoint, eccEphemeral, eccPublicKey, mlkemKeyShare, mlkemEphemeral []byte, mlkemPublicKey kem.PublicKey) ([]byte, error) { h := sha3.New256() @@ -160,35 +158,21 @@ func buildKey(pub *PublicKey, eccSecretPoint, eccEphemeral, eccPublicKey, mlkemK // mlkemEphemeral - the ML-KEM ciphertext encoded as an octet string // mlkemPublicKey - The ML-KEM public key of the recipient as an octet string // algId - the OpenPGP algorithm ID of the public-key encryption algorithm - // domainSeparator – the UTF-8 encoding of the string "OpenPGPCompositeKDFv1" // eccKeyShare - the ECDH key share encoded as an octet string // eccEphemeral - the ECDH ciphertext encoded as an octet string // eccPublicKey - The ECDH public key of the recipient as an octet string - // KEK = KMAC256( - // mlkemKeyShare || eccKeyShare, - // mlkemEphemeral || eccEphemeral || mlkemPublicKey || ecdhPublicKey || algId, - // 256 (32 bytes), - // domainSeparator - // ) - - kMacKeyBuffer := make([]byte, len(mlkemKeyShare)+len(eccKeyShare)) - copy(kMacKeyBuffer[:len(mlkemKeyShare)], mlkemKeyShare) - copy(kMacKeyBuffer[len(mlkemKeyShare):], eccKeyShare) - - k, err := kmac.NewKMAC256(kMacKeyBuffer, 32, []byte(domainSeparator)) - if err != nil { - return nil, err - } - - // kmac hash never returns an error - _, _ = k.Write(mlkemEphemeral) - _, _ = k.Write(eccEphemeral) - _, _ = k.Write(serializedMlkemPublicKey) - _, _ = k.Write(eccPublicKey) - _, _ = k.Write([]byte{pub.AlgId}) - - return k.Sum(nil), nil + // 2a. SHA3-256(mlkemKeyShare || eccKeyShare || eccEphemeral || eccPublicKey || Domain) + // where Domain is "Domain" for LAMPS, and "mlkemEphemeral || mlkemPublicKey || algId" for OpenPGP + h.Reset() + _, _ = h.Write(mlkemKeyShare) + _, _ = h.Write(eccKeyShare) + _, _ = h.Write(eccEphemeral) + _, _ = h.Write(eccPublicKey) + _, _ = h.Write(mlkemEphemeral) + _, _ = h.Write(serializedMlkemPublicKey) + _, _ = h.Write([]byte{pub.AlgId}) + return h.Sum(nil), nil } // Validate checks that the public key corresponds to the private key