diff --git a/abe/cpabe/tkn20/example_test.go b/abe/cpabe/tkn20/example_test.go index 6e2c378af..bd880333d 100644 --- a/abe/cpabe/tkn20/example_test.go +++ b/abe/cpabe/tkn20/example_test.go @@ -132,6 +132,6 @@ func Example() { // Output: // (occupation:doctor and country:US) // plaintext size: 27 bytes - // ciphertext size: 2735 bytes + // ciphertext size: 2747 bytes // Successfully recovered plaintext } diff --git a/abe/cpabe/tkn20/format_test.go b/abe/cpabe/tkn20/format_test.go index f8c464d6b..35b64e746 100644 --- a/abe/cpabe/tkn20/format_test.go +++ b/abe/cpabe/tkn20/format_test.go @@ -41,8 +41,23 @@ 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") } diff --git a/abe/cpabe/tkn20/internal/tkn/bk.go b/abe/cpabe/tkn20/internal/tkn/bk.go index 1d3fc3657..c96e86d29 100644 --- a/abe/cpabe/tkn20/internal/tkn/bk.go +++ b/abe/cpabe/tkn20/internal/tkn/bk.go @@ -1,6 +1,7 @@ package tkn import ( + "bytes" "crypto/subtle" "fmt" "io" @@ -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 { @@ -117,27 +121,39 @@ 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 } @@ -145,11 +161,11 @@ func DecryptCCA(ciphertext []byte, key *AttributesKey) ([]byte, error) { 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 } @@ -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 } @@ -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") } diff --git a/abe/cpabe/tkn20/internal/tkn/util.go b/abe/cpabe/tkn20/internal/tkn/util.go index 9afbe88a7..0c0f94f1c 100644 --- a/abe/cpabe/tkn20/internal/tkn/util.go +++ b/abe/cpabe/tkn20/internal/tkn/util.go @@ -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") } @@ -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 { diff --git a/abe/cpabe/tkn20/testdata/ciphertext b/abe/cpabe/tkn20/testdata/ciphertext index d96e2cb75..19648be90 100644 Binary files a/abe/cpabe/tkn20/testdata/ciphertext and b/abe/cpabe/tkn20/testdata/ciphertext differ diff --git a/abe/cpabe/tkn20/testdata/ciphertext_v137 b/abe/cpabe/tkn20/testdata/ciphertext_v137 new file mode 100644 index 000000000..d96e2cb75 Binary files /dev/null and b/abe/cpabe/tkn20/testdata/ciphertext_v137 differ diff --git a/abe/cpabe/tkn20/tkn20.go b/abe/cpabe/tkn20/tkn20.go index 56d4e2014..e03dc0e56 100644 --- a/abe/cpabe/tkn20/tkn20.go +++ b/abe/cpabe/tkn20/tkn20.go @@ -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 (