Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpabe: Serializing ciphertext with 32-bit prefixes. #490

Merged
merged 4 commits into from Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion abe/cpabe/tkn20/example_test.go
Expand Up @@ -84,6 +84,8 @@ func Example() {
if err != nil {
log.Fatalf("%s", err)
}
fmt.Printf("plaintext size: %v bytes\n", len(msgStr))
fmt.Printf("ciphertext size: %v bytes\n", len(ct))

// generate secret key for certain set of attributes
wrongAttrs := cpabe.Attributes{}
Expand Down Expand Up @@ -127,6 +129,9 @@ func Example() {
log.Fatalf("recovered plaintext: %s is not equal to original msg: %s", pt, msgStr)
}
fmt.Println("Successfully recovered plaintext")
// Output: (occupation:doctor and country:US)
// Output:
// (occupation:doctor and country:US)
// plaintext size: 27 bytes
// ciphertext size: 2747 bytes
// Successfully recovered plaintext
}
39 changes: 36 additions & 3 deletions abe/cpabe/tkn20/format_test.go
Expand Up @@ -41,20 +41,53 @@ func TestAttributeKeyFormat(t *testing.T) {
}
}

func TestCiphertext_v137(t *testing.T) {
// As of v1.3.8 ciphertext format changed to use wider prefixes.
// Ciphertexts in the previous format are still decryptable.
// The following functions are backwards-compatible:
// - AttributeKey.Decrypt
// - Attributes.CouldDecrypt
// - Policy.ExtractFromCiphertext
testCiphertext(t, "testdata/ciphertext_v137")
}

func TestCiphertext(t *testing.T) {
ciphertext, err := os.ReadFile("testdata/ciphertext")
testCiphertext(t, "testdata/ciphertext")
}

func testCiphertext(t *testing.T, ctName string) {
t.Logf("Checking ciphertext: %v\n", ctName)
ciphertext, err := os.ReadFile(ctName)
if err != nil {
t.Fatalf("Unable to read ciphertext data")
}
policyKey, err := os.ReadFile("testdata/attributeKey")
attributeKey, err := os.ReadFile("testdata/attributeKey")
if err != nil {
t.Fatalf("Unable to read secret key")
}
sk := AttributeKey{}
err = sk.UnmarshalBinary(policyKey)
err = sk.UnmarshalBinary(attributeKey)
if err != nil {
t.Fatalf("unable to parse secret key")
}
attrs := Attributes{}
attrs.FromMap(map[string]string{"country": "NL", "EU": "true"})
if !attrs.CouldDecrypt(ciphertext) {
t.Fatal("these attributes will be unable to decrypt message")
}
policy := Policy{}
err = policy.FromString("EU: true")
if err != nil {
t.Fatal("error creating policy from string")
}
gotPolicy := new(Policy)
err = gotPolicy.ExtractFromCiphertext(ciphertext)
if err != nil {
t.Fatal("error extracting policy from ciphertext")
}
if !policy.Equal(gotPolicy) {
t.Fatal("ciphertext's policy mismatches the original policy")
}
msg, err := sk.Decrypt(ciphertext)
if err != nil {
t.Fatal("unable to decrypt message")
Expand Down
11 changes: 3 additions & 8 deletions abe/cpabe/tkn20/gen_testdata.go
Expand Up @@ -6,11 +6,10 @@ package main

import (
"encoding"
mrand "math/rand"
"os"
"path/filepath"

cpabe "github.com/cloudflare/circl/abe/cpabe/tkn20"
"github.com/cloudflare/circl/xof"
)

func writeToFile(name string, data []byte) {
Expand All @@ -30,13 +29,9 @@ func dumpToFile(name string, m encoding.BinaryMarshaler) {

func main() {
// Using fixed PRNG for reproducibility,
fixedSeed := int64(0xC1C1C1C1)
prng := mrand.New(mrand.NewSource(fixedSeed))
if prng == nil {
panic("failed to create PRNG")
}
prng := xof.SHAKE128.New()

err := os.MkdirAll(filepath.Join(".", "testdata"), 0o755)
err := os.MkdirAll("testdata", 0o755)
if err != nil {
panic(err)
}
Expand Down
46 changes: 32 additions & 14 deletions abe/cpabe/tkn20/internal/tkn/bk.go
@@ -1,6 +1,7 @@
package tkn

import (
"bytes"
"crypto/subtle"
"fmt"
"io"
Expand All @@ -20,6 +21,9 @@ import (
// for our output size of 256 bits.
const macKeySeedSize = 72

// As of v1.3.8, ciphertexts are prefixed with this string.
const CiphertextVersion = "v1.3.8"

func blakeEncrypt(key []byte, msg []byte) ([]byte, error) {
xof, err := blake2b.NewXOF(blake2b.OutputLengthUnknown, key)
if err != nil {
Expand Down Expand Up @@ -117,39 +121,51 @@ func EncryptCCA(rand io.Reader, public *PublicParams, policy *Policy, msg []byte
if err != nil {
return nil, err
}
macData := appendLenPrefixed(nil, C1)
macData = appendLenPrefixed(macData, env)
macData := appendLen32Prefixed(nil, C1)
macData = appendLen32Prefixed(macData, env)

tag, err := blakeMac(macKey, macData)
if err != nil {
return nil, err
}

ret := appendLenPrefixed(nil, id)
ret = appendLenPrefixed(ret, macData)
ret := append([]byte{}, []byte(CiphertextVersion)...)
ret = appendLenPrefixed(ret, id)
ret = appendLen32Prefixed(ret, macData)
ret = appendLenPrefixed(ret, tag)

return ret, nil
}

type rmLenPref = func([]byte) ([]byte, []byte, error)

func checkCiphertextFormat(ciphertext []byte) (ct []byte, fn rmLenPref) {
const N = len(CiphertextVersion)
if bytes.Equal(ciphertext[0:N], []byte(CiphertextVersion)) {
return ciphertext[N:], removeLen32Prefixed
}
return ciphertext, removeLenPrefixed
}

func DecryptCCA(ciphertext []byte, key *AttributesKey) ([]byte, error) {
id, rest, err := removeLenPrefixed(ciphertext)
rest, removeLenPrefixedVar := checkCiphertextFormat(ciphertext)
id, rest, err := removeLenPrefixed(rest)
if err != nil {
return nil, err
}
macData, rest, err := removeLenPrefixed(rest)
macData, rest, err := removeLenPrefixedVar(rest)
if err != nil {
return nil, err
}
tag, _, err := removeLenPrefixed(rest)
if err != nil {
return nil, err
}
C1, envRaw, err := removeLenPrefixed(macData)
C1, envRaw, err := removeLenPrefixedVar(macData)
if err != nil {
return nil, err
}
env, _, err := removeLenPrefixed(envRaw)
env, _, err := removeLenPrefixedVar(envRaw)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -208,15 +224,16 @@ func DecryptCCA(ciphertext []byte, key *AttributesKey) ([]byte, error) {
}

func CouldDecrypt(ciphertext []byte, a *Attributes) bool {
id, rest, err := removeLenPrefixed(ciphertext)
rest, removeLenPrefixedVar := checkCiphertextFormat(ciphertext)
id, rest, err := removeLenPrefixed(rest)
if err != nil {
return false
}
macData, _, err := removeLenPrefixed(rest)
macData, _, err := removeLenPrefixedVar(rest)
if err != nil {
return false
}
C1, _, err := removeLenPrefixed(macData)
C1, _, err := removeLenPrefixedVar(macData)
if err != nil {
return false
}
Expand All @@ -237,15 +254,16 @@ func CouldDecrypt(ciphertext []byte, a *Attributes) bool {
}

func (p *Policy) ExtractFromCiphertext(ct []byte) error {
_, rest, err := removeLenPrefixed(ct)
rest, removeLenPrefixedVar := checkCiphertextFormat(ct)
_, rest, err := removeLenPrefixed(rest)
if err != nil {
return fmt.Errorf("invalid ciphertext")
}
macData, _, err := removeLenPrefixed(rest)
macData, _, err := removeLenPrefixedVar(rest)
if err != nil {
return fmt.Errorf("invalid ciphertext")
}
C1, _, err := removeLenPrefixed(macData)
C1, _, err := removeLenPrefixedVar(macData)
if err != nil {
return fmt.Errorf("invalid ciphertext")
}
Expand Down
27 changes: 25 additions & 2 deletions abe/cpabe/tkn20/internal/tkn/util.go
Expand Up @@ -42,14 +42,14 @@ func HashStringToScalar(key []byte, value string) *pairing.Scalar {
return s
}

func appendLenPrefixed(a []byte, b []byte) []byte {
func appendLen16Prefixed(a []byte, b []byte) []byte {
a = append(a, 0, 0)
binary.LittleEndian.PutUint16(a[len(a)-2:], uint16(len(b)))
a = append(a, b...)
return a
}

func removeLenPrefixed(data []byte) (next []byte, remainder []byte, err error) {
func removeLen16Prefixed(data []byte) (next []byte, remainder []byte, err error) {
if len(data) < 2 {
return nil, nil, fmt.Errorf("data too short")
}
Expand All @@ -60,6 +60,29 @@ func removeLenPrefixed(data []byte) (next []byte, remainder []byte, err error) {
return data[2 : 2+itemLen], data[2+itemLen:], nil
}

var (
appendLenPrefixed = appendLen16Prefixed
removeLenPrefixed = removeLen16Prefixed
)

func appendLen32Prefixed(a []byte, b []byte) []byte {
a = append(a, 0, 0, 0, 0)
binary.LittleEndian.PutUint32(a[len(a)-4:], uint32(len(b)))
a = append(a, b...)
return a
}

func removeLen32Prefixed(data []byte) (next []byte, remainder []byte, err error) {
if len(data) < 4 {
return nil, nil, fmt.Errorf("data too short")
}
itemLen := int(binary.LittleEndian.Uint32(data))
if (4 + itemLen) > len(data) {
return nil, nil, fmt.Errorf("data too short")
}
return data[4 : 4+itemLen], data[4+itemLen:], nil
}

func marshalBinarySortedMapMatrixG1(m map[string]*matrixG1) ([]byte, error) {
sortedKeys := make([]string, 0, len(m))
for key := range m {
Expand Down
60 changes: 60 additions & 0 deletions abe/cpabe/tkn20/longpt_test.go
@@ -0,0 +1,60 @@
package tkn20_test

import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"testing"

cpabe "github.com/cloudflare/circl/abe/cpabe/tkn20"
"github.com/cloudflare/circl/internal/test"
"github.com/cloudflare/circl/xof"
)

func TestLongPlaintext(t *testing.T) {
// Fixed PRNG for test reproducibility.
prng := xof.SHAKE128.New()

pk, msk, err := cpabe.Setup(prng)
test.CheckNoErr(t, err, "setup failed")

attrs := cpabe.Attributes{}
attrs.FromMap(map[string]string{
"occupation": "doctor",
"country": "US",
"age": "16",
})

sk, err := msk.KeyGen(prng, attrs)
test.CheckNoErr(t, err, "master key generation failed")

policy := cpabe.Policy{}
err = policy.FromString(`(occupation: doctor) and (country: US)`)
test.CheckNoErr(t, err, "policy parsing failed")

const N = 20 // 2^N bytes of plaintext
buffer := make([]byte, 1<<N)
_, err = io.ReadFull(prng, buffer)
test.CheckNoErr(t, err, "reading message from prgn failed")

for i := 0; i < N; i++ {
t.Run(fmt.Sprint(i), func(t *testing.T) {
msg := buffer[:(1 << i)]

ct, err := pk.Encrypt(prng, policy, msg)
test.CheckNoErr(t, err, "encryption failed")

t.Logf("length pt: %v ct: %v", len(msg), len(ct))

pt, err := sk.Decrypt(ct)
test.CheckNoErr(t, err, "decryption failed")

got := sha256.Sum256(pt)
want := sha256.Sum256(msg)
if !bytes.Equal(got[:], want[:]) {
test.ReportError(t, got, want)
}
})
}
}
Binary file modified abe/cpabe/tkn20/testdata/attributeKey
Binary file not shown.
Binary file modified abe/cpabe/tkn20/testdata/ciphertext
Binary file not shown.
Binary file added abe/cpabe/tkn20/testdata/ciphertext_v137
Binary file not shown.
Binary file modified abe/cpabe/tkn20/testdata/publicKey
Binary file not shown.
Binary file modified abe/cpabe/tkn20/testdata/secretKey
Binary file not shown.
9 changes: 9 additions & 0 deletions abe/cpabe/tkn20/tkn20.go
Expand Up @@ -8,6 +8,15 @@
// attribute-based encryption. In A. Kiayias, M. Kohlweiss, P. Wallden, and
// V. Zikas, editors, PKC, volume 12110 of Lecture Notes in Computer Science,
// pages 3–33. Springer, 2020. https://eprint.iacr.org/2019/966
//
// # Update v1.3.8
//
// As of v1.3.8, ciphertext format changed to use wider prefixes.
// Ciphertexts in the previous format are still decryptable.
// The following functions are backwards-compatible:
// - [AttributeKey.Decrypt]
// - [Attributes.CouldDecrypt]
// - [Policy.ExtractFromCiphertext]
package tkn20

import (
Expand Down