diff --git a/encrypt/ibe/ibe.go b/encrypt/ibe/ibe.go index e768cbf0b..1c37265b7 100644 --- a/encrypt/ibe/ibe.go +++ b/encrypt/ibe/ibe.go @@ -45,9 +45,9 @@ func H4Tag() []byte { // - the Ciphertext.U point will be on G1 // - ID is the ID towards which we encrypt the message // - msg is the actual message -// - seed is the random seed to generate the random element (sigma) of the encryption +// - additionalData is optional additional data bound via commitment (nil for backward compatibility) // The suite must produce points which implements the `HashablePoint` interface. -func EncryptCCAonG1(s pairing.Suite, master kyber.Point, ID, msg []byte) (*Ciphertext, error) { +func EncryptCCAonG1(s pairing.Suite, master kyber.Point, ID, msg, additionalData []byte) (*Ciphertext, error) { if len(msg) > s.Hash().Size() { return nil, errors.New("plaintext too long for the hash function provided") } @@ -65,8 +65,8 @@ func EncryptCCAonG1(s pairing.Suite, master kyber.Point, ID, msg []byte) (*Ciphe if _, err := rand.Read(sigma); err != nil { return nil, fmt.Errorf("err reading rand sigma: %v", err) } - // 3. Derive r from sigma and msg - r, err := h3(s, sigma, msg) + // 3. Derive r from sigma, msg, and optional additionalData + r, err := h3WithAD(s, sigma, msg, additionalData) if err != nil { return nil, err } @@ -96,7 +96,8 @@ func EncryptCCAonG1(s pairing.Suite, master kyber.Point, ID, msg []byte) (*Ciphe } // DecryptCCAonG1 decrypts ciphertexts encrypted using EncryptCCAonG1 given a G2 "private" point -func DecryptCCAonG1(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte, error) { +// - additionalData is optional additional data that must match the encryption context (nil for backward compatibility) +func DecryptCCAonG1(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error) { if len(c.W) > s.Hash().Size() { return nil, errors.New("ciphertext too long for the hash function provided") } @@ -120,8 +121,8 @@ func DecryptCCAonG1(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte msg := xor(hsigma, c.W) - // 3. Check U = rP - r, err := h3(s, sigma, msg) + // 3. Check U = rP with optional additionalData + r, err := h3WithAD(s, sigma, msg, additionalData) if err != nil { return nil, err } @@ -132,6 +133,18 @@ func DecryptCCAonG1(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte return msg, nil } +// EncryptCCAonG1WithAD encrypts with additional data bound via commitment. +// different AD values produce different ciphertexts for replay attack prevention. +func EncryptCCAonG1WithAD(s pairing.Suite, master kyber.Point, ID, msg, additionalData []byte) (*Ciphertext, error) { + return EncryptCCAonG1(s, master, ID, msg, additionalData) +} + +// DecryptCCAonG1WithAD decrypts with additional data verification. +// wrong AD causes cryptographic failure at rP check. +func DecryptCCAonG1WithAD(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error) { + return DecryptCCAonG1(s, private, c, additionalData) +} + // EncryptCCAonG2 implements the CCA identity-based encryption scheme from // https://crypto.stanford.edu/~dabo/pubs/papers/bfibe.pdf for more information // about the scheme. @@ -140,9 +153,9 @@ func DecryptCCAonG1(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte // - the Ciphertext.U point will be on G2 // - ID is the ID towards which we encrypt the message // - msg is the actual message -// - seed is the random seed to generate the random element (sigma) of the encryption +// - additionalData is optional additional data bound via commitment (nil for backward compatibility) // The suite must produce points which implements the `HashablePoint` interface. -func EncryptCCAonG2(s pairing.Suite, master kyber.Point, ID, msg []byte) (*Ciphertext, error) { +func EncryptCCAonG2(s pairing.Suite, master kyber.Point, ID, msg, additionalData []byte) (*Ciphertext, error) { if len(msg) > s.Hash().Size() { return nil, errors.New("plaintext too long for the hash function provided") } @@ -160,8 +173,8 @@ func EncryptCCAonG2(s pairing.Suite, master kyber.Point, ID, msg []byte) (*Ciphe if _, err := rand.Read(sigma); err != nil { return nil, fmt.Errorf("err reading rand sigma: %v", err) } - // 3. Derive r from sigma and msg - r, err := h3(s, sigma, msg) + // 3. Derive r from sigma, msg, and optional additionalData + r, err := h3WithAD(s, sigma, msg, additionalData) if err != nil { return nil, err } @@ -191,7 +204,8 @@ func EncryptCCAonG2(s pairing.Suite, master kyber.Point, ID, msg []byte) (*Ciphe } // DecryptCCAonG2 decrypts ciphertexts encrypted using EncryptCCAonG2 given a G1 "private" point -func DecryptCCAonG2(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte, error) { +// - additionalData is optional additional data that must match the encryption context (nil for backward compatibility) +func DecryptCCAonG2(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error) { if len(c.W) > s.Hash().Size() { return nil, errors.New("ciphertext too long for the hash function provided") } @@ -215,8 +229,8 @@ func DecryptCCAonG2(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte msg := xor(hsigma, c.W) - // 3. Check U = rP - r, err := h3(s, sigma, msg) + // 3. Check U = rP with optional additionalData + r, err := h3WithAD(s, sigma, msg, additionalData) if err != nil { return nil, err } @@ -227,8 +241,25 @@ func DecryptCCAonG2(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte return msg, nil } +// EncryptCCAonG2WithAD encrypts with additional data bound via commitment. +// different AD values produce different ciphertexts for replay attack prevention. +func EncryptCCAonG2WithAD(s pairing.Suite, master kyber.Point, ID, msg, additionalData []byte) (*Ciphertext, error) { + return EncryptCCAonG2(s, master, ID, msg, additionalData) +} + +// DecryptCCAonG2WithAD decrypts with additional data verification. +// wrong AD causes cryptographic failure at rP check. +func DecryptCCAonG2WithAD(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error) { + return DecryptCCAonG2(s, private, c, additionalData) +} + // hash sigma and msg to get r func h3(s pairing.Suite, sigma, msg []byte) (kyber.Scalar, error) { + return h3WithAD(s, sigma, msg, nil) +} + +// h3WithAD includes additional data in the hash for r generation +func h3WithAD(s pairing.Suite, sigma, msg, additionalData []byte) (kyber.Scalar, error) { h := s.Hash() if h.Size() != s.G1().ScalarLen() { @@ -242,7 +273,10 @@ func h3(s pairing.Suite, sigma, msg []byte) (kyber.Scalar, error) { return nil, fmt.Errorf("err hashing sigma: %v", err) } _, _ = h.Write(msg) - // we hash it a first time: buffer = hash("IBE-H3" || sigma || msg) + if len(additionalData) > 0 { + _, _ = h.Write(additionalData) + } + // hash("IBE-H3" || sigma || msg || additionalData) buffer := h.Sum(nil) hashable, ok := s.G1().Scalar().(*mod.Int) diff --git a/encrypt/ibe/ibe_test.go b/encrypt/ibe/ibe_test.go index 513a5d69d..e3748e0a4 100644 --- a/encrypt/ibe/ibe_test.go +++ b/encrypt/ibe/ibe_test.go @@ -14,8 +14,8 @@ import ( func newSetting(i uint) ( pairing.Suite, kyber.Point, []byte, kyber.Point, - func(s pairing.Suite, master kyber.Point, ID []byte, msg []byte) (*Ciphertext, error), - func(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte, error), + func(s pairing.Suite, master kyber.Point, ID []byte, msg []byte, additionalData []byte) (*Ciphertext, error), + func(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error), ) { if !(i == 1 || i == 2) { panic("invalid test") @@ -50,9 +50,9 @@ func TestValidEncryptionDecrypts(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(1) msg := []byte("Hello World\n") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) - msg2, err := decrypt(suite, sQid, c) + msg2, err := decrypt(suite, sQid, c, nil) require.NoError(t, err) require.Equal(t, msg, msg2) }) @@ -61,9 +61,9 @@ func TestValidEncryptionDecrypts(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(2) msg := []byte("Hello World\n") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) - msg2, err := decrypt(suite, sQid, c) + msg2, err := decrypt(suite, sQid, c, nil) require.NoError(t, err) require.Equal(t, msg, msg2) }) @@ -75,12 +75,12 @@ func TestInvalidSigmaFailsDecryption(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(1) msg := []byte("Hello World\n") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.V = []byte("somenonsense") - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "invalid proof") }) @@ -90,12 +90,12 @@ func TestInvalidSigmaFailsDecryption(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(2) msg := []byte("Hello World\n") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.V = []byte("somenonsense") - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "invalid proof") }) @@ -106,11 +106,11 @@ func TestInvalidMessageFailsDecryption(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(1) msg := []byte("Hello World\n") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.W = []byte("somenonsense") - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "invalid proof") }) @@ -119,11 +119,11 @@ func TestInvalidMessageFailsDecryption(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(2) msg := []byte("Hello World\n") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.W = []byte("somenonsense") - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "invalid proof") }) @@ -133,14 +133,14 @@ func TestVeryLongInputFailsEncryption(t *testing.T) { t.Run("OnG1", func(t *testing.T) { suite, Ppub, ID, _, encrypt, _ := newSetting(1) msg := []byte(strings.Repeat("And you have to understand this, that a prince, especially a new one, cannot observe all those things for which men are esteemed", 1000)) - _, err := encrypt(suite, Ppub, ID, msg) + _, err := encrypt(suite, Ppub, ID, msg, nil) require.Error(t, err) }) t.Run("OnG2", func(t *testing.T) { suite, Ppub, ID, _, encrypt, _ := newSetting(2) msg := []byte(strings.Repeat("And you have to understand this, that a prince, especially a new one, cannot observe all those things for which men are esteemed", 1000)) - _, err := encrypt(suite, Ppub, ID, msg) + _, err := encrypt(suite, Ppub, ID, msg, nil) require.Error(t, err) }) } @@ -149,11 +149,11 @@ func TestVeryLongCipherFailsDecryptionBecauseOfLength(t *testing.T) { t.Run("OnG1", func(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(1) msg := []byte("hello world") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.W = []byte(strings.Repeat("And you have to understand this, that a prince, especially a new one, cannot observe all those things for which men are esteemed", 1000)) - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "ciphertext too long for the hash function provided") @@ -161,11 +161,11 @@ func TestVeryLongCipherFailsDecryptionBecauseOfLength(t *testing.T) { t.Run("OnG2", func(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(2) msg := []byte("hello world") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.W = []byte(strings.Repeat("And you have to understand this, that a prince, especially a new one, cannot observe all those things for which men are esteemed", 1000)) - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "ciphertext too long for the hash function provided") @@ -176,11 +176,11 @@ func TestInvalidWFailsDecryptionBecauseOfLength(t *testing.T) { t.Run("OnG1", func(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(1) msg := []byte("hello world") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.W = []byte(strings.Repeat("A", 25)) - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "XorSigma is of invalid length") @@ -188,11 +188,11 @@ func TestInvalidWFailsDecryptionBecauseOfLength(t *testing.T) { t.Run("OnG2", func(t *testing.T) { suite, Ppub, ID, sQid, encrypt, decrypt := newSetting(2) msg := []byte("hello world") - c, err := encrypt(suite, Ppub, ID, msg) + c, err := encrypt(suite, Ppub, ID, msg, nil) require.NoError(t, err) c.W = []byte(strings.Repeat("A", 25)) - _, err = decrypt(suite, sQid, c) + _, err = decrypt(suite, sQid, c, nil) require.Error(t, err) require.ErrorContains(t, err, "XorSigma is of invalid length") @@ -238,7 +238,7 @@ func TestBackwardsInteropWithTypescript(t *testing.T) { ciphertext := Ciphertext{U: U, W: W, V: V} - result, err := decrypt(suite, beacon, &ciphertext) + result, err := decrypt(suite, beacon, &ciphertext, nil) require.NoError(t, err) require.Equal(t, expectedFileKey, result) } @@ -259,3 +259,177 @@ func TestCPAEncryptOnG1(t *testing.T) { require.NoError(t, err) require.Equal(t, msg, msg2) } + +func TestEncryptDecryptWithAdditionalData(t *testing.T) { + t.Run("OnG1_WithCorrectAD", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(1) + msg := []byte("Hello World\n") + additionalData := []byte("transaction_hash_0x1234") + + c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, additionalData) + require.NoError(t, err) + msg2, err := DecryptCCAonG1WithAD(suite, sQid, c, additionalData) + require.NoError(t, err) + require.Equal(t, msg, msg2) + }) + + t.Run("OnG2_WithCorrectAD", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(2) + msg := []byte("Hello World\n") + additionalData := []byte("transaction_hash_0x1234") + + c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, additionalData) + require.NoError(t, err) + msg2, err := DecryptCCAonG2WithAD(suite, sQid, c, additionalData) + require.NoError(t, err) + require.Equal(t, msg, msg2) + }) + + t.Run("OnG1_WithIncorrectAD_FailsDecryption", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(1) + msg := []byte("Hello World\n") + correctAD := []byte("transaction_hash_0x1234") + incorrectAD := []byte("transaction_hash_0x5678") + + c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, correctAD) + require.NoError(t, err) + + // Attempt to decrypt with incorrect additional data should fail + _, err = DecryptCCAonG1WithAD(suite, sQid, c, incorrectAD) + require.Error(t, err) + require.ErrorContains(t, err, "invalid proof") + }) + + t.Run("OnG2_WithIncorrectAD_FailsDecryption", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(2) + msg := []byte("Hello World\n") + correctAD := []byte("transaction_hash_0x1234") + incorrectAD := []byte("transaction_hash_0x5678") + + c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, correctAD) + require.NoError(t, err) + + // Attempt to decrypt with incorrect additional data should fail + _, err = DecryptCCAonG2WithAD(suite, sQid, c, incorrectAD) + require.Error(t, err) + require.ErrorContains(t, err, "invalid proof") + }) + + t.Run("OnG1_WithMissingAD_FailsDecryption", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(1) + msg := []byte("Hello World\n") + additionalData := []byte("transaction_hash_0x1234") + + c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, additionalData) + require.NoError(t, err) + + // Attempt to decrypt without additional data should fail + _, err = DecryptCCAonG1WithAD(suite, sQid, c, nil) + require.Error(t, err) + require.ErrorContains(t, err, "invalid proof") + }) + + t.Run("OnG2_WithMissingAD_FailsDecryption", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(2) + msg := []byte("Hello World\n") + additionalData := []byte("transaction_hash_0x1234") + + c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, additionalData) + require.NoError(t, err) + + // Attempt to decrypt without additional data should fail + _, err = DecryptCCAonG2WithAD(suite, sQid, c, nil) + require.Error(t, err) + require.ErrorContains(t, err, "invalid proof") + }) + + t.Run("OnG1_WithEmptyAD", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(1) + msg := []byte("Hello World\n") + emptyAD := []byte{} + + c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, emptyAD) + require.NoError(t, err) + msg2, err := DecryptCCAonG1WithAD(suite, sQid, c, emptyAD) + require.NoError(t, err) + require.Equal(t, msg, msg2) + }) + + t.Run("OnG2_WithEmptyAD", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(2) + msg := []byte("Hello World\n") + emptyAD := []byte{} + + c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, emptyAD) + require.NoError(t, err) + msg2, err := DecryptCCAonG2WithAD(suite, sQid, c, emptyAD) + require.NoError(t, err) + require.Equal(t, msg, msg2) + }) +} + +func TestDifferentADProducesDifferentCiphertexts(t *testing.T) { + t.Run("OnG1", func(t *testing.T) { + suite, Ppub, ID, _, _, _ := newSetting(1) + msg := []byte("Hello World\n") + ad1 := []byte("context1") + ad2 := []byte("context2") + + c1, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, ad1) + require.NoError(t, err) + c2, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, ad2) + require.NoError(t, err) + + // Different AD should produce different U points (due to different r values) + require.False(t, c1.U.Equal(c2.U), "Different AD should produce different ciphertexts") + }) + + t.Run("OnG2", func(t *testing.T) { + suite, Ppub, ID, _, _, _ := newSetting(2) + msg := []byte("Hello World\n") + ad1 := []byte("context1") + ad2 := []byte("context2") + + c1, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, ad1) + require.NoError(t, err) + c2, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, ad2) + require.NoError(t, err) + + // Different AD should produce different U points (due to different r values) + require.False(t, c1.U.Equal(c2.U), "Different AD should produce different ciphertexts") + }) +} + +func TestReplayAttackPrevention(t *testing.T) { + t.Run("OnG1_SameCiphertextDifferentContext", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(1) + msg := []byte("payment:100USD") + context1 := []byte("recipient:alice") + context2 := []byte("recipient:bob") + + // Encrypt payment for Alice + ciphertext, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, context1) + require.NoError(t, err) + + // Try to "replay" the ciphertext in Bob's context - should fail + _, err = DecryptCCAonG1WithAD(suite, sQid, ciphertext, context2) + require.Error(t, err) + require.ErrorContains(t, err, "invalid proof") + }) + + t.Run("OnG2_SameCiphertextDifferentContext", func(t *testing.T) { + suite, Ppub, ID, sQid, _, _ := newSetting(2) + msg := []byte("payment:100USD") + context1 := []byte("recipient:alice") + context2 := []byte("recipient:bob") + + // Encrypt payment for Alice + ciphertext, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, context1) + require.NoError(t, err) + + // Try to "replay" the ciphertext in Bob's context - should fail + _, err = DecryptCCAonG2WithAD(suite, sQid, ciphertext, context2) + require.Error(t, err) + require.ErrorContains(t, err, "invalid proof") + }) +}