paragonie/ciphersweet
CipherSweet is a PHP library for fast, secure field-level encryption with searchable encrypted indexes. Designed for applications that need to protect sensitive data at rest while still supporting equality and range queries, with strong cryptography and clean integrations.
Full Changelog: https://github.com/paragonie/ciphersweet/compare/v4.9.0...v4.10.0
export-ignore by @erikn69 in https://github.com/paragonie/ciphersweet/pull/111Full Changelog: https://github.com/paragonie/ciphersweet/compare/v4.8.0...v4.9.0
New Feature: If you use a StaticBlindIndexKeyProvider interface for your Key Providers, you can now designate a specific "tenant" identifier to be static and used for Blind Index root key derivation. This works with EncryptedRow and EncryptedMultiRows.
Full Changelog: https://github.com/paragonie/ciphersweet/compare/v4.7.0...v4.8.0
AAD class, which allows users to bind an encrypted field to the contents of multiple plaintext fields. This class can be used in the same place where a field name or literal value was used previously.EncryptedFile now accepts an optional AAD param, which binds the file's contents to the AAD value.Here's a quick example of the old API, then a diff to use the new AAD features:
<?php
use ParagonIE\CipherSweet\CipherSweet;
use ParagonIE\CipherSweet\EncryptedMultiRows;
/** [@var](https://github.com/var) CipherSweet $engine */
$multiRowEncryptor = new EncryptedMultiRows($engine);
$multiRowEncryptor
->addTextField('table1', 'field1')
->addIntegerField('table1', 'field2')
->addFloatField('table1', 'field3')
->addOptionalBooleanField('table1', 'field4')
->addTextField('table2', 'foo')
->addTextField('table3', 'bar');
$encrypted = $multiRowEncryptor->encryptManyRows([
'table1' => ['field1' => 'hello world', 'field2' => 42, 'field3' => 3.1416],
'table2' => ['id' => 3, 'foo' => 'joy'],
'table3' => ['foo' => 'coy'],
]);
And here's how to easily enable to new features:
$multiRowEncryptor = new EncryptedMultiRows($engine);
$multiRowEncryptor
+ ->setAutoBindContext(true)
+ ->setPrimaryKeyColumn('table2', 'id')
->addTextField('table1', 'field1')
With this change, every encrypted field is explicitly cryptographically bound to its context (table name, field name) with no further action needed from the developer.
Additionally, table2 is cryptographically bound to its primary key (id). This has two consequences:
That second point is the main reason why we are not enabling it by default. (Also, we'd kind of need to know your primary key naming convention, which we cannot know for everyone that uses this library.)
We will update the documentation as soon as possible.
Full Changelog: https://github.com/paragonie/ciphersweet/compare/v4.5.1...v4.6.0
Full Changelog: https://github.com/paragonie/ciphersweet/compare/v3.4.0...v3.4.1
More helpful exception message on NULL values. See #95, #92, #93.
If you do not declare a field optional, it generally will not accept NULL as a value on encrypt. Boolean is the exception to this rule (for backwards compat).
However, non-optional fields (even booleans) must have a ciphertext on the decrypt path.
Encrypt:
TYPE_BOOLEAN + (null) -> ciphertext
TYPE_OPTIONAL_BOOLEAN + (null) -> ciphertext
Decrypt:
TYPE_BOOLEAN + (null) -> TypeError
TYPE_OPTIONAL_BOOLEAN + (null) -> null
Booleans are the weird ones, though.
Encrypt:
TYPE_TEXT + (null) -> TypeError
TYPE_OPTIONAL_TEXT + (null) -> null
Decrypt:
TYPE_TEXT + (null) -> TypeError
TYPE_OPTIONAL_BOOLEAN + (null) -> null
Every other type doesn't tolerate null implicitly. This behavior is because of a very early design decision with boolean types.
Full Changelog: https://github.com/paragonie/ciphersweet/compare/v4.4.0...v4.5.0
Full Changelog: https://github.com/paragonie/ciphersweet/compare/v4.3.0...v4.4.0
Too many users have tripped over the conservative defaults for blind indexing. To alleviate this, we are introducing two new classes in the public API:
FastBlindIndexFastCompoundIndexThis will always use a fast hash; which will be more suitable for all but the absolute most sensitive data.
setPermitEmpty() and getPermitEmpty(() to toggle whether empty values are tolerable in encrypted rows.#[\SensitiveParameter] attribute for PHP 8.2EncryptedRow to EncryptedJsonField.EncryptedJsonFieldIf you're using a modern SQL database that supports JSON documents in each row (i.e. PostgreSQL with JSONB), this new feature allows you to encrypt a subset of a JSON document at rest.
<?php
use ParagonIE\CipherSweet\CipherSweet;
use ParagonIE\CipherSweet\EncryptedJsonField;
use ParagonIE\CipherSweet\EncryptedRow;
use ParagonIE\CipherSweet\JsonFieldMap;
/** [@var](https://github.com/var) CipherSweet $engine */
// Create a JSON Field Map
$map = (new JsonFieldMap())
->addTextField('name')
->addBooleanField('active')
// You can describe a full path to an attribute of a JSON document by passing an array describing it:
->addIntegerField(['address', 0, 'zip_code'])
->addIntegerField('age');
// Instantiate the JSON field on an EncryptedRow:
$encRow = (new EncryptedRow($engine, 'table_name'))
->addJsonField('column', $map);
// You can also do this (if you want it in isolation):
$jsonField = EncryptedJsonField::create($engine, $map, 'table_name', 'column');
// Encrypt some data
$plaintext = [
'user_id' => 3495,
'extra' => 'foo bar baz ...',
// This is the JSON column:
'column' => [
'active' => false,
'name' => 'John Doe',
'address' => [
[
'line1' => '1600 Pennsylvania Ave NW',
'line2' => '',
'city' => 'Washington',
'state' => 'DC',
'zip_code' => 20500
]
],
'age' => 33
],
'extraneous' => 1
];
$encrypted = $encRow->encryptRow($plaintext);
var_dump($encrypted);
This should produce output similar to this (albeit with different ciphertext):
array(4) {
["user_id"]=>
int(3495)
["extra"]=>
string(15) "foo bar baz ..."
["column"]=>
string(499) "{"active":"brng:K9DpP-000NEi1NwRP78fFQ7-Z7PDTR1vWPzb2LfZWMvHELDIZRFjh5KjDnNxC7JXUBhuyg8cNllu","name":"brng:bs039aez6fF-jttL65ZDMKI-OQe-CfvYHhjEir3AHL
Smt9OhivZwy6SI7aMNRzmWQEWThXfICtxq3DVoPJNuAw==","address":[{"line1":"1600 Pennsylvania Ave NW","line2":"","city":"Washington","state":"DC","zip_code":"brng:0CwKzWzj
HhCNS_1A0WLlZCnkv5iiTgP1cBUsHJuIMVLREJIef88eYFJR3RjB2j6_LEz7SiuONOKJXIxW9bYzdA=="}],"age":"brng:sIMNt1UG7FuyLGHQfS9FEsDv6HJuXRJaVeUyWUQ4GY15vZ_G21qJ2KadAMCc9VXcwMPG
OSQ89acrbCc6cRQJ1w=="}"
["extraneous"]=>
int(1)
}
This serializes the encrypted JSON blobs as a string. During decryption, a string is expected as input, and it will return an array once decoded.
$decrypted = $encRow->decryptRow($encrypted);
var_dump($decrypted);
This will yield the following:
array(4) {
["user_id"]=>
int(3495)
["extra"]=>
string(15) "foo bar baz ..."
["column"]=>
array(4) {
["active"]=>
bool(false)
["name"]=>
string(8) "John Doe"
["address"]=>
array(1) {
[0]=>
array(5) {
["line1"]=>
string(24) "1600 Pennsylvania Ave NW"
["line2"]=>
string(0) ""
["city"]=>
string(10) "Washington"
["state"]=>
string(2) "DC"
["zip_code"]=>
int(20500)
}
}
["age"]=>
int(33)
}
["extraneous"]=>
int(1)
}
getTenant($name) method on the MultiTenantKeyProvider class incorrectly only returned the active tenant. This is fixed.getKeyProvider() method to CipherSweet. This is useful for calling methods on a multi-tenant-aware KeyProvider class (i.e. the parent one that wraps other KeyProvider classes).EncryptedRow's decryption path. Fixes #62. (Thanks @lekoala!)ModernCrypto called BoringCrypto which uses BLAKE2b-MAC instead of Poly1305.
BoringCrypto and FIPSCrypto are both suitable for use in multi-tenant data storage situations.rb flag.AlphaNumeric).Backwards compatibility breaks!
CipherSweet v2.x is mostly but not completely backwards compatible with the v1.x branch. Many of the BC breaks were introduced by @mcordingley in #42:
- I removed all mention of the back-end from the key provider. The key providers had no reason to know about the back-end in use, but its presence meant an additional method on the interface and somewhat more complicated instantiation logic. This change made the back-end a required parameter to the
CipherSweetconstructor, but the additional effort of providing it there is (more than) offset by no longer having to provide it to a key provider.- I removed
getDefaultBackend(), as a change in the environment could cause a change in back-end, thereby rendering indexes and encrypted values "invalid". Different environments with the same code could nonetheless try to run different back-ends. :beetle:- I updated the
CipherSweetconstructor to make the back-end again an optional parameter. If not provided, we fall back to a default back-end. Unlike the removed factory method, this will always choose the same back-end implementation. Unless the user has specific reason otherwise, this is the back-end they will want. If the environment is unable to accommodateModernCrypto, attempting to use it should throw an error and prompt either a code change or the installation of libsodium. Either way, the choice made will be explicit and intentional.... snip ...
- Removed
ArrayProvider. It doesn't seem to add anything beyond whatStringProvideralready gives, but is less direct about it.
Additionally, some changes made by Paragon Initiative Enterprises to make the library easier to use and cleaner:
You can now call setFlatIndexes(true); if you don't need ["type" => "foo", "value" => "bar"] and only want "bar".
EncryptedFile for encrypting files and PHP streams with authenticated encryption.EncryptedRow::getBlindIndex() was not behaving as expected.BackendInterface has several new methods added:
deriveKeyFromPassword(string $password, string $salt) should return a string.doStreamEncrypt(resource $in, resource $out, SymmetricKey $key, int $chunkSize = 8192, string $salt = Constants::DUMMY_SALT) should return a boolean value.doStreamDecrypt(resource $in, resource $out, SymmetricKey $key, int $chunkSize = 8192) should return a boolean value.getFileEncryptionSaltOffset() should return an integer.BackendInterface interface now requires a method called getPrefix() that returns a string.How can I help you explore Laravel packages today?