From fbaea9e373053a8814fec469ca5f6c40c606ef03 Mon Sep 17 00:00:00 2001 From: coreyhn Date: Sat, 5 Jul 2025 19:10:54 -0600 Subject: [PATCH 1/6] docs(readme): add context type support table and `ste_vec` limitation Add context type compatibility table to clarify which encryption contexts work with which index types. Document that `ste_vec` indexes do not support encryption context to prevent configuration errors. Update bulk operation examples to show context usage patterns. --- README.md | 54 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7d45e50..d84e6c7 100644 --- a/README.md +++ b/README.md @@ -611,7 +611,18 @@ Returns the decrypted plaintext as a string. Provide additional encryption context for an additional layer of security by binding encrypted data to specific contextual information of your choosing. This prevents data encrypted with one context from being decrypted with a different context, even when using the same encryption keys. -Protect.php FFI supports three types of encryption context: +**Context Types** + +The `context` parameter determines what contextual authentication is supported on encrypted data: + +| Context Type | Supported Index Types | Description | +|--------------|----------------------|-------------| +| `identity_claim` | `unique`, `ore`, `match` | Identity-aware encryption using JWT claims (requires CTS authentication) | +| `tag` | `unique`, `ore`, `match` | Label-aware encryption using string tags | +| `value` | `unique`, `ore`, `match` | Attribute-aware encryption using key-value pairs | + +> [!IMPORTANT] +> Encryption context is not supported with `ste_vec` indexes and will cause decryption to fail. #### Identity Claim Context @@ -739,7 +750,7 @@ For improved performance when handling multiple records, use bulk encryption and #### Bulk Encryption -Encrypt multiple plaintext strings using the `encryptBulk()` method. This method accepts a client pointer and a JSON array of objects, where each object specifies the `plaintext`, `column`, and `table` for encryption: +Encrypt multiple plaintext strings using the `encryptBulk()` method. This method accepts a client pointer and a JSON array of objects, where each object specifies the `plaintext`, `column`, `table`, and optional `context` for encryption: ```php use CipherStash\Protect\FFI\Client; @@ -803,7 +814,7 @@ Returns a JSON array where each element follows the same structure as documented #### Bulk Decryption -Decrypt multiple ciphertext strings using the `decryptBulk()` method. This method accepts a client pointer and a JSON array of objects, where each object contains a `ciphertext` key with the base85-encoded ciphertext string: +Decrypt multiple ciphertext strings using the `decryptBulk()` method. This method accepts a client pointer and a JSON array of objects, where each object contains a `ciphertext` with the base85-encoded ciphertext string and an optional `context` for decryption: ```php use CipherStash\Protect\FFI\Client; @@ -839,6 +850,9 @@ try { 'plaintext' => 'john@example.com', 'column' => 'email', 'table' => 'patient_records', + 'context' => [ + 'tag' => ['pii', 'hipaa'], + ], ], [ 'plaintext' => 'Patient shows improvement in mobility and pain management.', @@ -848,25 +862,30 @@ try { ]; $itemsJson = json_encode($items, JSON_THROW_ON_ERROR); - $resultJson = $client->encryptBulk($clientPtr, $itemsJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $ciphertexts = array_column($result, 'c'); + $encryptResultJson = $client->encryptBulk($clientPtr, $itemsJson); + $encryptResults = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + + $decryptItems = array_map(function ($item, $encryptResult) { + $decryptItem = ['ciphertext' => $encryptResult['c']]; - $ciphertextItems = array_map(function ($ciphertext) { - return ['ciphertext' => $ciphertext]; - }, $ciphertexts); + if (isset($item['context'])) { + $decryptItem['context'] = $item['context']; + } - $ciphertextItemsJson = json_encode($ciphertextItems, JSON_THROW_ON_ERROR); + return $decryptItem; + }, $items, $encryptResults); - echo $ciphertextItemsJson; - // [{"ciphertext":"mBbK>BcAYctW$Gy)vK2)Y$&nBBKz{oL1..."},{"ciphertext":"mBbJ<8tOEI+Z`KFUV`q&kmdWtO#DKxW|..."}] + $decryptItemsJson = json_encode($decryptItems, JSON_THROW_ON_ERROR); - $decryptedResultJson = $client->decryptBulk($clientPtr, $ciphertextItemsJson); + echo $decryptItemsJson; + // [{"ciphertext":"mBbK>BcAYctW$Gy)vK2)Y$&nBBKz{oL1...","context":{"tag":["pii","hipaa"]}},{"ciphertext":"mBbJ<8tOEI+Z`KFUV`q&kmdWtO#DKxW|..."}] - echo $decryptedResultJson; + $decryptResultJson = $client->decryptBulk($clientPtr, $decryptItemsJson); + + echo $decryptResultJson; // ["john@example.com","Patient shows improvement in mobility and pain management."] - $plaintexts = json_decode(json: $decryptedResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $decryptResults = json_decode(json: $decryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); } finally { $client->freeClient($clientPtr); } @@ -876,7 +895,7 @@ Returns a JSON array of decrypted plaintext strings in the same order as the inp ### Searchable Encryption -Create search terms that enable querying encrypted data without decryption using the `createSearchTerms()` method. This method accepts a client pointer and a JSON array of objects, where each object specifies the `plaintext`, `column`, and `table` for generating search terms: +Create search terms that enable querying encrypted data without decryption using the `createSearchTerms()` method. This method accepts a client pointer and a JSON array of objects, where each object specifies the `plaintext`, `column`, `table`, and optional `context` for generating search terms: ```php use CipherStash\Protect\FFI\Client; @@ -912,6 +931,9 @@ try { 'plaintext' => 'john@example.com', 'column' => 'email', 'table' => 'patient_records', + 'context' => [ + 'tag' => ['pii', 'hipaa'], + ], ], [ 'plaintext' => '120', From cf70664548f7332121398158711cd641fa4972cb Mon Sep 17 00:00:00 2001 From: coreyhn Date: Sat, 5 Jul 2025 19:15:00 -0600 Subject: [PATCH 2/6] tests(php): add integration tests for encryption context functionality Add test for `ste_vec` context incompatibility to verify that using encryption context with `ste_vec` indexes throws expected exceptions during decryption. This documents the known limitation where context is ignored during encryption, therefore causing decryption with the same context to fail. Add bulk operations test with context to verify roundtrip encryption and decryption works correctly when context is provided for supported index types (`unique`, `ore`, `match`). --- tests/Integration/ClientTest.php | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/Integration/ClientTest.php b/tests/Integration/ClientTest.php index 68d25e8..3000a19 100644 --- a/tests/Integration/ClientTest.php +++ b/tests/Integration/ClientTest.php @@ -551,6 +551,35 @@ public function test_decrypt_throws_exception_with_invalid_context(): void } } + public function test_decrypt_throws_exception_with_context_on_ste_vec_column(): void + { + $client = new Client; + $clientPtr = $client->newClient(self::$config); + + try { + $plaintext = '{"city":"Boston","state":"MA"}'; + + $contextJson = json_encode([ + 'tag' => ['test-context'], + ], JSON_THROW_ON_ERROR); + + // Context is ignored during encryption + $encryptResultJson = $client->encrypt($clientPtr, $plaintext, 'metadata', 'users', $contextJson); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $this->assertArrayHasKey('c', $encryptResult); + $ciphertext = $encryptResult['c']; + $this->assertIsString($ciphertext); + $this->assertNotEmpty($ciphertext); + + // Context causes decryption to fail + $this->expectException(FFIException::class); + $client->decrypt($clientPtr, $ciphertext, $contextJson); + } finally { + $client->freeClient($clientPtr); + } + } + public function test_encrypt_decrypt_bulk_roundtrip(): void { $client = new Client; @@ -694,6 +723,78 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void } } + public function test_encrypt_decrypt_bulk_roundtrip_with_context(): void + { + $client = new Client; + $clientPtr = $client->newClient(self::$config); + + try { + $items = [ + [ + 'plaintext' => 'john@example.com', + 'column' => 'email', + 'table' => 'users', + 'context' => ['tag' => ['test-context']], + ], + [ + 'plaintext' => '29', + 'column' => 'age', + 'table' => 'users', + 'context' => ['tag' => ['test-context']], + ], + [ + 'plaintext' => 'Software Engineer', + 'column' => 'job_title', + 'table' => 'users', + 'context' => ['tag' => ['test-context']], + ], + [ + 'plaintext' => '{"city":"Boston","state":"MA"}', + 'column' => 'metadata', + 'table' => 'users', + ], + ]; + + $itemsJson = json_encode($items, JSON_THROW_ON_ERROR); + $encryptResultJson = $client->encryptBulk($clientPtr, $itemsJson); + $encryptResults = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + + $this->assertIsArray($encryptResults); + $this->assertCount(4, $encryptResults); + + $decryptItems = array_map(function ($item, $encryptResult) { + assert(is_array($encryptResult)); + + $ciphertext = $encryptResult['c']; + $this->assertIsString($ciphertext); + $this->assertNotEmpty($ciphertext); + + $decryptItem = ['ciphertext' => $ciphertext]; + + if (isset($item['context'])) { + $decryptItem['context'] = $item['context']; + } + + return $decryptItem; + }, $items, $encryptResults); + + $decryptItemsJson = json_encode($decryptItems, JSON_THROW_ON_ERROR); + $decryptResultJson = $client->decryptBulk($clientPtr, $decryptItemsJson); + $decryptResults = json_decode(json: $decryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + + $expectedPlaintexts = [ + 'john@example.com', + '29', + 'Software Engineer', + '{"city":"Boston","state":"MA"}', + ]; + + $this->assertEquals($expectedPlaintexts, $decryptResults); + } finally { + $client->freeClient($clientPtr); + } + } + public function test_encrypt_bulk_throws_exception_with_invalid_items(): void { $client = new Client; From eb71a48d6d797cd327246d0512a142f8e63ceaf2 Mon Sep 17 00:00:00 2001 From: coreyhn Date: Sat, 5 Jul 2025 19:19:54 -0600 Subject: [PATCH 3/6] docs(readme): standardize variable names in code examples Standardize encryption/decryption variable naming throughout README examples for consistency. Updates variable names to be more descriptive and explicit about their purpose. This improves code clarity and maintains consistency across the codebase documentation. --- README.md | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index d84e6c7..207402a 100644 --- a/README.md +++ b/README.md @@ -460,16 +460,16 @@ $configJson = json_encode($config, JSON_THROW_ON_ERROR); $clientPtr = $client->newClient($configJson); try { - $resultJson = $client->encrypt( + $encryptResultJson = $client->encrypt( client: $clientPtr, plaintext: 'john@example.com', columnName: 'email', tableName: 'patient_records', ); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $ciphertext = $result['c']; + $ciphertext = $encryptResult['c']; echo $ciphertext; // mBbKlk}G7QdaGiNj$dL7#+AOrA^}*VJx... @@ -585,20 +585,20 @@ $configJson = json_encode($config, JSON_THROW_ON_ERROR); $clientPtr = $client->newClient($configJson); try { - $resultJson = $client->encrypt( + $encryptResultJson = $client->encrypt( client: $clientPtr, plaintext: 'john@example.com', columnName: 'email', tableName: 'patient_records', ); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $ciphertext = $result['c']; + $ciphertext = $encryptResult['c']; - $plaintext = $client->decrypt($clientPtr, $ciphertext); + $decryptResult = $client->decrypt($clientPtr, $ciphertext); - echo $plaintext; + echo $decryptResult; // john@example.com } finally { $client->freeClient($clientPtr); @@ -663,7 +663,7 @@ try { $contextJson = json_encode($context, JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt( + $encryptResultJson = $client->encrypt( client: $clientPtr, plaintext: 'john@example.com', columnName: 'email', @@ -671,13 +671,13 @@ try { contextJson: $contextJson, ); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $ciphertext = $result['c']; + $ciphertext = $encryptResult['c']; - $plaintext = $client->decrypt($clientPtr, $ciphertext, $contextJson); + $decryptResult = $client->decrypt($clientPtr, $ciphertext, $contextJson); - echo $plaintext; + echo $decryptResult; // john@example.com } finally { $client->freeClient($clientPtr); @@ -720,7 +720,7 @@ try { $contextJson = json_encode($context, JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt( + $encryptResultJson = $client->encrypt( client: $clientPtr, plaintext: 'john@example.com', columnName: 'email', @@ -728,13 +728,13 @@ try { contextJson: $contextJson, ); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $ciphertext = $result['c']; + $ciphertext = $encryptResult['c']; - $plaintext = $client->decrypt($clientPtr, $ciphertext, $contextJson); + $decryptResult = $client->decrypt($clientPtr, $ciphertext, $contextJson); - echo $plaintext; + echo $decryptResult; // john@example.com } finally { $client->freeClient($clientPtr); @@ -795,12 +795,12 @@ try { ]; $itemsJson = json_encode($items, JSON_THROW_ON_ERROR); - $resultJson = $client->encryptBulk($clientPtr, $itemsJson); + $encryptResultJson = $client->encryptBulk($clientPtr, $itemsJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $encryptResults = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - foreach ($result as $encryptedData) { - $ciphertext = $encryptedData['c']; + foreach ($encryptResults as $encryptResult) { + $ciphertext = $encryptResult['c']; echo $ciphertext; // mBbKuXT|+vBh~K2WV-!n5_W3DBFd4`Mp... @@ -926,7 +926,7 @@ $configJson = json_encode($config, JSON_THROW_ON_ERROR); $clientPtr = $client->newClient($configJson); try { - $terms = [ + $searchTerms = [ [ 'plaintext' => 'john@example.com', 'column' => 'email', @@ -942,11 +942,11 @@ try { ], ]; - $termsJson = json_encode($terms, JSON_THROW_ON_ERROR); - $resultJson = $client->createSearchTerms($clientPtr, $termsJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $searchTermsJson = json_encode($searchTerms, JSON_THROW_ON_ERROR); + $searchTermsResultJson = $client->createSearchTerms($clientPtr, $searchTermsJson); + $searchTermsResult = json_decode(json: $searchTermsResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - foreach ($result as $searchTerms) { + foreach ($searchTermsResult as $searchTerms) { echo json_encode($searchTerms); // {"hm":"f3ca71fd39ae9d3d1d1fc25141bcb6da...","ob":null,"bf":null,"i":{"t":"patient_records","c":"email"}} } @@ -1056,15 +1056,15 @@ try { $configJson = json_encode($config, JSON_THROW_ON_ERROR); $clientPtr = $client->newClient($configJson); - $resultJson = $client->encrypt( + $encryptResultJson = $client->encrypt( client: $clientPtr, plaintext: 'john@example.com', columnName: 'email', tableName: 'patient_records', ); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $ciphertext = $result['c']; + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $ciphertext = $encryptResult['c']; echo $ciphertext; // mBbKlk}G7QdaGiNj$dL7#+AOrA^}*VJx... From dcaa2a8dea445bd3f10238b58ffc21dcfba1d361 Mon Sep 17 00:00:00 2001 From: coreyhn Date: Sat, 5 Jul 2025 19:22:19 -0600 Subject: [PATCH 4/6] tests(php): standardize variable names in integration tests Standardize encryption/decryption variable naming throughout integration tests for consistency. Updates variable names to be more descriptive and explicit about their purpose. This improves test readability and maintains consistency across the codebase. --- tests/Integration/ClientTest.php | 196 +++++++++++++++---------------- 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/tests/Integration/ClientTest.php b/tests/Integration/ClientTest.php index 3000a19..59852b8 100644 --- a/tests/Integration/ClientTest.php +++ b/tests/Integration/ClientTest.php @@ -96,28 +96,27 @@ public function test_encrypt_decrypt_roundtrip(): void try { $plaintext = 'john@example.com'; - $resultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users'); - - $this->assertNotEquals($plaintext, $resultJson); - - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $this->assertArrayHasKey('k', $result); - $this->assertEquals('ct', $result['k']); - $this->assertArrayHasKey('c', $result); - $this->assertIsString($result['c']); - $this->assertNotEmpty($result['c']); - $this->assertArrayHasKey('dt', $result); - $this->assertEquals('text', $result['dt']); - $this->assertArrayHasKey('i', $result); - $identifier = $result['i']; + $encryptResultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users'); + + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $this->assertArrayHasKey('k', $encryptResult); + $this->assertEquals('ct', $encryptResult['k']); + $this->assertArrayHasKey('c', $encryptResult); + $ciphertext = $encryptResult['c']; + $this->assertIsString($ciphertext); + $this->assertNotEmpty($ciphertext); + $this->assertNotEquals($plaintext, $ciphertext); + $this->assertArrayHasKey('dt', $encryptResult); + $this->assertEquals('text', $encryptResult['dt']); + $this->assertArrayHasKey('i', $encryptResult); + $identifier = $encryptResult['i']; $this->assertIsArray($identifier); $this->assertEquals('users', $identifier['t']); $this->assertEquals('email', $identifier['c']); - $ciphertext = $result['c']; - $decrypted = $client->decrypt($clientPtr, $ciphertext); - $this->assertEquals($plaintext, $decrypted); + $decryptResult = $client->decrypt($clientPtr, $ciphertext); + $this->assertEquals($plaintext, $decryptResult); } finally { $client->freeClient($clientPtr); } @@ -238,44 +237,43 @@ public function test_encrypt_decrypt_complex_json_roundtrip(): void ], ], JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt($clientPtr, $complexJson, 'metadata', 'users'); - - $this->assertNotEquals($complexJson, $resultJson); - - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $this->assertArrayHasKey('k', $result); - $this->assertEquals('sv', $result['k']); - $this->assertArrayHasKey('c', $result); - $this->assertIsString($result['c']); - $this->assertNotEmpty($result['c']); - $this->assertArrayHasKey('dt', $result); - $this->assertEquals('jsonb', $result['dt']); - $this->assertArrayHasKey('sv', $result); - $this->assertIsArray($result['sv']); - $this->assertNotEmpty($result['sv']); - $this->assertArrayHasKey('i', $result); - $identifier = $result['i']; + $encryptResultJson = $client->encrypt($clientPtr, $complexJson, 'metadata', 'users'); + + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $this->assertArrayHasKey('k', $encryptResult); + $this->assertEquals('sv', $encryptResult['k']); + $this->assertArrayHasKey('c', $encryptResult); + $ciphertext = $encryptResult['c']; + $this->assertIsString($ciphertext); + $this->assertNotEmpty($ciphertext); + $this->assertNotEquals($complexJson, $ciphertext); + $this->assertArrayHasKey('dt', $encryptResult); + $this->assertEquals('jsonb', $encryptResult['dt']); + $this->assertArrayHasKey('sv', $encryptResult); + $this->assertIsArray($encryptResult['sv']); + $this->assertNotEmpty($encryptResult['sv']); + $this->assertArrayHasKey('i', $encryptResult); + $identifier = $encryptResult['i']; $this->assertIsArray($identifier); $this->assertEquals('users', $identifier['t']); $this->assertEquals('metadata', $identifier['c']); - $ciphertext = $result['c']; - $decrypted = $client->decrypt($clientPtr, $ciphertext); + $decryptResultJson = $client->decrypt($clientPtr, $ciphertext); - $decryptedData = json_decode(json: $decrypted, associative: true, flags: JSON_THROW_ON_ERROR); + $decryptResult = json_decode(json: $decryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); $originalData = json_decode(json: $complexJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($decryptedData); + $this->assertIsArray($decryptResult); $this->assertIsArray($originalData); - $userProfile = $decryptedData['user_profile']; + $userProfile = $decryptResult['user_profile']; $this->assertIsArray($userProfile); - $billingInfo = $decryptedData['billing_info']; + $billingInfo = $decryptResult['billing_info']; $this->assertIsArray($billingInfo); - $activityData = $decryptedData['activity_data']; + $activityData = $decryptResult['activity_data']; $this->assertIsArray($activityData); - $systemMetadata = $decryptedData['system_metadata']; + $systemMetadata = $decryptResult['system_metadata']; $this->assertIsArray($systemMetadata); $this->assertEquals('CUST-20240315-7892', $userProfile['customer_id']); @@ -413,21 +411,20 @@ public function test_encrypt_decrypt_roundtrip_with_context(): void ], ], JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $contextJson); - $this->assertNotEquals($plaintext, $resultJson); - - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $this->assertArrayHasKey('c', $result); + $encryptResultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $contextJson); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $this->assertArrayHasKey('c', $encryptResult); - $ciphertext = $result['c']; + $ciphertext = $encryptResult['c']; $this->assertIsString($ciphertext); $this->assertNotEmpty($ciphertext); - $this->assertArrayHasKey('dt', $result); - $this->assertEquals('text', $result['dt']); + $this->assertNotEquals($plaintext, $ciphertext); + $this->assertArrayHasKey('dt', $encryptResult); + $this->assertEquals('text', $encryptResult['dt']); - $decrypted = $client->decrypt($clientPtr, $ciphertext, $contextJson); - $this->assertEquals($plaintext, $decrypted); + $decryptResult = $client->decrypt($clientPtr, $ciphertext, $contextJson); + $this->assertEquals($plaintext, $decryptResult); } finally { $client->freeClient($clientPtr); } @@ -448,10 +445,10 @@ public function test_decrypt_fails_with_wrong_tag_context(): void ], ], JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $originalContextJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $ciphertext = $result['c']; + $encryptResultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $originalContextJson); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $ciphertext = $encryptResult['c']; $this->assertIsString($ciphertext); $wrongTagContextJson = json_encode([ @@ -483,10 +480,10 @@ public function test_decrypt_fails_with_wrong_value_context(): void ], ], JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $originalContextJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $ciphertext = $result['c']; + $encryptResultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $originalContextJson); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $ciphertext = $encryptResult['c']; $this->assertIsString($ciphertext); $wrongValueContextJson = json_encode([ @@ -538,10 +535,10 @@ public function test_decrypt_throws_exception_with_invalid_context(): void $plaintext = 'john@example.com'; $contextJson = json_encode(['tag' => ['valid-context']], JSON_THROW_ON_ERROR); - $resultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $contextJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $ciphertext = $result['c']; + $encryptResultJson = $client->encrypt($clientPtr, $plaintext, 'email', 'users', $contextJson); + $encryptResult = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($encryptResult); + $ciphertext = $encryptResult['c']; $this->assertIsString($ciphertext); $this->expectException(FFIException::class); @@ -610,13 +607,13 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void ]; $itemsJson = json_encode($items, JSON_THROW_ON_ERROR); - $resultJson = $client->encryptBulk($clientPtr, $itemsJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $encryptResultJson = $client->encryptBulk($clientPtr, $itemsJson); + $encryptResults = json_decode(json: $encryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $this->assertCount(4, $result); + $this->assertIsArray($encryptResults); + $this->assertCount(4, $encryptResults); - $emailResult = $result[0]; + $emailResult = $encryptResults[0]; $this->assertIsArray($emailResult); $this->assertArrayHasKey('k', $emailResult); $this->assertEquals('ct', $emailResult['k']); @@ -631,7 +628,7 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void $this->assertEquals('users', $emailIdentifier['t']); $this->assertEquals('email', $emailIdentifier['c']); - $ageResult = $result[1]; + $ageResult = $encryptResults[1]; $this->assertIsArray($ageResult); $this->assertArrayHasKey('k', $ageResult); $this->assertEquals('ct', $ageResult['k']); @@ -646,7 +643,7 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void $this->assertEquals('users', $ageIdentifier['t']); $this->assertEquals('age', $ageIdentifier['c']); - $jobTitleResult = $result[2]; + $jobTitleResult = $encryptResults[2]; $this->assertIsArray($jobTitleResult); $this->assertArrayHasKey('k', $jobTitleResult); $this->assertEquals('ct', $jobTitleResult['k']); @@ -661,7 +658,7 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void $this->assertEquals('users', $jobTitleIdentifier['t']); $this->assertEquals('job_title', $jobTitleIdentifier['c']); - $metadataResult = $result[3]; + $metadataResult = $encryptResults[3]; $this->assertIsArray($metadataResult); $this->assertArrayHasKey('k', $metadataResult); $this->assertEquals('sv', $metadataResult['k']); @@ -695,7 +692,7 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void $this->assertEquals('users', $metadataIdentifier['t']); $this->assertEquals('metadata', $metadataIdentifier['c']); - $ciphertexts = array_column($result, 'c'); + $ciphertexts = array_column($encryptResults, 'c'); $this->assertCount(4, $ciphertexts); foreach ($ciphertexts as $ciphertext) { @@ -708,8 +705,8 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void }, $ciphertexts); $encryptedItemsJson = json_encode($encryptedItems, JSON_THROW_ON_ERROR); - $decryptedResultJson = $client->decryptBulk($clientPtr, $encryptedItemsJson); - $decryptedPlaintexts = json_decode(json: $decryptedResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $decryptResultJson = $client->decryptBulk($clientPtr, $encryptedItemsJson); + $decryptResults = json_decode(json: $decryptResultJson, associative: true, flags: JSON_THROW_ON_ERROR); $expectedPlaintexts = [ 'john@example.com', @@ -717,7 +714,8 @@ public function test_encrypt_decrypt_bulk_roundtrip(): void 'Software Engineer', '{"city":"Boston","state":"MA"}', ]; - $this->assertEquals($expectedPlaintexts, $decryptedPlaintexts); + + $this->assertEquals($expectedPlaintexts, $decryptResults); } finally { $client->freeClient($clientPtr); } @@ -827,7 +825,7 @@ public function test_create_search_terms(): void $clientPtr = $client->newClient(self::$config); try { - $terms = [ + $searchTerms = [ [ 'plaintext' => 'john@example.com', 'column' => 'email', @@ -850,44 +848,44 @@ public function test_create_search_terms(): void ], ]; - $termsJson = json_encode($terms, JSON_THROW_ON_ERROR); - $resultJson = $client->createSearchTerms($clientPtr, $termsJson); + $searchTermsJson = json_encode($searchTerms, JSON_THROW_ON_ERROR); + $searchTermsResultJson = $client->createSearchTerms($clientPtr, $searchTermsJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $this->assertCount(4, $result); + $searchTermsResult = json_decode(json: $searchTermsResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($searchTermsResult); + $this->assertCount(4, $searchTermsResult); - $emailTerm = $result[0]; + $emailTerm = $searchTermsResult[0]; $this->assertIsArray($emailTerm); $this->assertNotNull($emailTerm['hm']); $this->assertNull($emailTerm['ob']); $this->assertNotNull($emailTerm['bf']); $this->assertArrayHasKey('i', $emailTerm); - $ageTerm = $result[1]; + $ageTerm = $searchTermsResult[1]; $this->assertIsArray($ageTerm); $this->assertNull($ageTerm['hm']); $this->assertNotNull($ageTerm['ob']); $this->assertNull($ageTerm['bf']); $this->assertArrayHasKey('i', $ageTerm); - $jobTitleTerm = $result[2]; + $jobTitleTerm = $searchTermsResult[2]; $this->assertIsArray($jobTitleTerm); $this->assertNull($jobTitleTerm['hm']); $this->assertNull($jobTitleTerm['ob']); $this->assertNotNull($jobTitleTerm['bf']); $this->assertArrayHasKey('i', $jobTitleTerm); - $metadataTerm = $result[3]; + $metadataTerm = $searchTermsResult[3]; $this->assertIsArray($metadataTerm); $this->assertArrayHasKey('sv', $metadataTerm); $this->assertIsArray($metadataTerm['sv']); $this->assertNotEmpty($metadataTerm['sv']); $this->assertArrayHasKey('i', $metadataTerm); - foreach ($result as $searchTerm) { - $this->assertIsArray($searchTerm); - $identifier = $searchTerm['i']; + foreach ($searchTermsResult as $searchTerms) { + $this->assertIsArray($searchTerms); + $identifier = $searchTerms['i']; $this->assertIsArray($identifier); $this->assertEquals('users', $identifier['t']); $this->assertContains($identifier['c'], ['email', 'age', 'job_title', 'metadata']); @@ -903,7 +901,7 @@ public function test_create_search_terms_with_context(): void $clientPtr = $client->newClient(self::$config); try { - $terms = [ + $searchTerms = [ [ 'plaintext' => 'john@example.com', 'column' => 'email', @@ -912,14 +910,14 @@ public function test_create_search_terms_with_context(): void ], ]; - $termsJson = json_encode($terms, JSON_THROW_ON_ERROR); - $resultJson = $client->createSearchTerms($clientPtr, $termsJson); + $searchTermsJson = json_encode($searchTerms, JSON_THROW_ON_ERROR); + $searchTermsResultJson = $client->createSearchTerms($clientPtr, $searchTermsJson); - $result = json_decode(json: $resultJson, associative: true, flags: JSON_THROW_ON_ERROR); - $this->assertIsArray($result); - $this->assertCount(1, $result); + $searchTermsResult = json_decode(json: $searchTermsResultJson, associative: true, flags: JSON_THROW_ON_ERROR); + $this->assertIsArray($searchTermsResult); + $this->assertCount(1, $searchTermsResult); - $searchTerm = $result[0]; + $searchTerm = $searchTermsResult[0]; $this->assertIsArray($searchTerm); $this->assertNotNull($searchTerm['hm']); $this->assertNull($searchTerm['ob']); From f45b25ebf5ecf1c2c61899a30ca9d918dac71633 Mon Sep 17 00:00:00 2001 From: coreyhn Date: Sat, 5 Jul 2025 19:24:36 -0600 Subject: [PATCH 5/6] docs(readme): standardize parameter terminology Standardize configuration and response terminology throughout README for consistency. Updates "field" references to "parameter" and "response field" to "response parameter". This improves documentation consistency and uses more precise terminology. --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 207402a..7247dd6 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Configuration parameters: **Data Types** -The `cast_as` field determines how plaintext data is processed before encryption: +The `cast_as` parameter determines how plaintext data is processed before encryption: | Type | Description | Example Input | |------|-------------|---------------| @@ -118,9 +118,9 @@ The `cast_as` field determines how plaintext data is processed before encryption **Index Types** -The `indexes` field determines what operations are supported on encrypted data: +The `indexes` parameter determines what operations are supported on encrypted data: -| Index Type | Use Case | Response Field | Plaintext Queries | Search Terms Queries | +| Index Type | Use Case | Response Parameter | Plaintext Queries | Search Terms Queries | |------------|----------|----------------|-------------------|---------------------| | `unique` | Exact equality queries and uniqueness constraints | `hm` | `eql_v2.hmac_256()` | `eql_v2.hmac_256()` | | `ore` | Equality, range comparisons, range queries, and ordering | `ob` | `eql_v2.ore_block_u64_8_256()` | `eql_v2.ore_block_u64_8_256()` | @@ -129,7 +129,7 @@ The `indexes` field determines what operations are supported on encrypted data: **Unique Index (`unique`)** -Enables exact equality queries and database uniqueness constraints. Uses the `hm` response field and works with the `eql_v2.hmac_256()` EQL function. This index generates HMAC-based hashes for exact equality matching. +Enables exact equality queries and database uniqueness constraints. Uses the `hm` response parameter and works with the `eql_v2.hmac_256()` EQL function. This index generates HMAC-based hashes for exact equality matching. Basic usage: @@ -185,7 +185,7 @@ WHERE eql_v2.hmac_256(email) = eql_v2.hmac_256( ); ``` -For database-level uniqueness constraints, add a unique constraint on the `hm` response field: +For database-level uniqueness constraints, add a unique constraint on the `hm` response parameter: ```sql CONSTRAINT unique_email UNIQUE ((email->>'hm')) @@ -193,7 +193,7 @@ CONSTRAINT unique_email UNIQUE ((email->>'hm')) **Order Revealing Encryption Index (`ore`)** -Enables equality, range operations, and ordering on encrypted data. Uses the `ob` response field and works with the `eql_v2.ore_block_u64_8_256()` EQL function. This index creates order-preserving encrypted values for equality checks, range comparisons, range queries, and sorting operations. +Enables equality, range operations, and ordering on encrypted data. Uses the `ob` response parameter and works with the `eql_v2.ore_block_u64_8_256()` EQL function. This index creates order-preserving encrypted values for equality checks, range comparisons, range queries, and sorting operations. Basic usage: @@ -265,7 +265,7 @@ ORDER BY eql_v2.ore_block_u64_8_256(systolic_bp) DESC; **Match Index (`match`)** -Enables full-text search on encrypted text data using bloom filters. Uses the `bf` response field and works with the `eql_v2.bloom_filter()` EQL function. This index creates bloom filter representations of tokenized text for probabilistic matching. +Enables full-text search on encrypted text data using bloom filters. Uses the `bf` response parameter and works with the `eql_v2.bloom_filter()` EQL function. This index creates bloom filter representations of tokenized text for probabilistic matching. Basic usage: @@ -336,7 +336,7 @@ WHERE eql_v2.bloom_filter(medical_notes) @> eql_v2.bloom_filter( **Structured Text Encryption Vector Index (`ste_vec`)** -Enables containment queries on encrypted JSONB data. Uses the `sv` response field and works with the `cs_ste_vec_v2()` EQL function for plaintext queries and PostgreSQL containment operators (`@>`, `<@`) for search terms queries. This index creates structured text encryption vectors that preserve JSON path relationships for encrypted JSONB containment matching. +Enables containment queries on encrypted JSONB data. Uses the `sv` response parameter and works with the `cs_ste_vec_v2()` EQL function for plaintext queries and PostgreSQL containment operators (`@>`, `<@`) for search terms queries. This index creates structured text encryption vectors that preserve JSON path relationships for encrypted JSONB containment matching. Basic usage: @@ -377,7 +377,7 @@ SELECT * FROM patient_records WHERE health_assessment <@ '{"sv":[{"s":"df08a4c4157bdb5bf6fa9be89cf18d10...","t":"22303063343133306135646334356130...","r":"mBbL}QHJ&a(@rwS5n)u^G+Fb+Ex8ofB!...","pa":false}],"i":{"t":"patient_records","c":"health_assessment"}}'; ``` -This index differs from other indexes in its query patterns. Plaintext queries use `cs_ste_vec_v2()` with JSON data and only support the PostgreSQL `@>` operator, while search term queries can use both PostgreSQL `@>` and `<@` operators with pre-computed vectors from the `sv` response field in the search terms response. +This index differs from other indexes in its query patterns. Plaintext queries use `cs_ste_vec_v2()` with JSON data and only support the PostgreSQL `@>` operator, while search term queries can use both PostgreSQL `@>` and `<@` operators with pre-computed vectors from the `sv` response parameter in the search terms response. ### Creating a Client From 13a83ff230f2094146596903bf72aa697de06a55 Mon Sep 17 00:00:00 2001 From: coreyhn Date: Sat, 5 Jul 2025 19:25:35 -0600 Subject: [PATCH 6/6] docs(readme): clarify `int` example to show 32-bit max value Update the example input for the `int` data type in the README to use `2147483647`, the maximum value for a signed 32-bit integer, instead of `29`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7247dd6..ccb7ce6 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ The `cast_as` parameter determines how plaintext data is processed before encryp | `text` | String data | `john@example.com` | | `boolean` | Boolean values | `true` or `false` | | `small_int` | 16-bit integer numbers | `32767` | -| `int` | 32-bit integer numbers | `29` | +| `int` | 32-bit integer numbers | `2147483647` | | `big_int` | 64-bit integer numbers | `9223372036854775807` | | `real` | Single-precision floating point | `25.99` | | `double` | Double-precision floating point | `3.141592653589793` |