Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
composer.phar
composer.lock
vendor/
build/
.phpunit.result.cache
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
language: php
php:
- 5.5
- 5.6
- 7.0
- 7.1
- 7.4
addons:
code_climate:
repo_token: d2e633c11ce9b2f6cdd21d775d295c9836064eaebe96590c2cb3b2370f893555
Expand All @@ -14,4 +13,4 @@ script:
- ./vendor/bin/phpcs --ignore=*/vendor/* --standard=PSR2 .
- ./vendor/bin/phpcs --standard=./vendor/athens/standard/ruleset.xml src
after_script:
- vendor/bin/test-reporter
- vendor/bin/test-reporter
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ For example:
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>

<column name="my_data" type="varchar" size="255" />
<column name="my_secret_data" type="varbinary" size="255" />
<column name="my_secret_data" type="BLOB" />
<column name="my_searchable_data" type="varbinary" size="255" />

<behavior name="encryption">
<parameter name="column_name_1" value="my_secret_data" />
<parameter name="column_name_searchable_1" value="my_searchable_data" />
<parameter name="searchable" value="false" />
</behavior>
</table>

Expand Down Expand Up @@ -76,7 +79,7 @@ Use

This client library provides a `Cipher` class and one Propel2 Behavior class.

To designate a field as encrypted in your Propel schema, set its type as `varbinary` and include the `encryption` behavior. You may include multiple columns in the `encryption` behavior:
To designate a field as encrypted in your Propel schema, set its type as `VARBINARY`, `LONGVARBINARY` or `BLOB` and include the `encryption` behavior. You may include multiple columns in the `encryption` behavior:

```
<table name="my_class">
Expand Down Expand Up @@ -109,11 +112,40 @@ That's it! The class setters for `MySecretData` and `MySecretData2` now seamless

Remember that search/find and sort are now *broken* for `MySecretData` and `MySecretData2`, for reasons discussed above.

## Filtering
By default all encrypted columns are not searchable. It's possible to make all encrypted columns of a table searchable by setting a parameter `searchable` to `true`
```
<table name="my_class">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="my_data" type="varchar" size="255" />
<column name="my_secret_data" type="varbinary" size="255" />

<behavior name="encryption">
<parameter name="column_name_1" value="my_secret_data" />
<parameter name="searchable" value="true" />
</behavior>
</table>
```
It's also possible to make a particular column as searchable using `column_name_searchable_*` prefix
```
<table name="my_class">
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true"/>
<column name="my_data" type="VARCHAR" size="255" />
<column name="my_secret_data" type="BLOB" />
<column name="my_secret_searchable_data" type="VARBINARY" size="255" />

<behavior name="encryption">
<parameter name="column_name_1" value="my_secret_data" />
<parameter name="column_name_searchable_1" value="my_secret_searchable_data" />
</behavior>
</table>
```
> **Be aware:** For the searchable columns will be used a fixed IV. It looses data security.

Compatibility
=============

* PHP 5.5, 5.6, 7.0
* PHP >=7.1
* Propel2

Todo
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
]
},
"require": {
"php": ">=7.1",
"propel/propel": "~2.0@dev"
},
"require-dev": {
"phpdocumentor/phpdocumentor": "2.7.*",
"phpunit/phpunit": "4.5.*",
"phpunit/phpunit": ">=7.0",
"codeclimate/php-test-reporter": "dev-master",
"athens/standard": "*"
},
Expand Down
59 changes: 44 additions & 15 deletions src/Cipher.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
*/
class Cipher
{

const IV_SIZE = 16;

const ENCRYPTION_METHOD = "aes-256-cbc";

/** @var Cipher */
/**
* @var Cipher
*/
protected static $instance;

/** @var string */
/**
* @var string
*/
protected $passphrase;

/**
Expand All @@ -32,12 +36,18 @@ protected function __construct($passphrase)
/**
* Converts a plain-text string into an encrypted string
*
* @param string $string Plain-text to encrypt.
* @return string The encrypted string.
* @param string|null $string Plain-text to encrypt.
*
* @return string|null The encrypted string.
*/
public function encrypt($string)
public function encrypt(?string $string): ?string
{
$iv = mcrypt_create_iv(self::IV_SIZE, MCRYPT_RAND);
if ($string === null) {
return $string;
}

$iv = random_bytes(self::IV_SIZE);

return $this->doEncrypt($string, $iv);
}

Expand All @@ -58,21 +68,33 @@ public function encrypt($string)
* This method is employed for encrypting Propel columns that are designated as 'searchable'
* in the included EncryptionBehavior.
*
* @param string $string Plain-text to encrypt.
* @return string The encrypted string.
* @param string|null $string Plain-text to encrypt.
*
* @return string|null The encrypted string.
*/
public function deterministicEncrypt($string)
public function deterministicEncrypt(?string $string): ?string
{
if ($string === null) {
return $string;
}

$iv = str_repeat("0", self::IV_SIZE);

// prevent second encryption during ModelCriteria::findOneOrCreate()
if (strpos($string, $iv) === 0) {
return $string;
}

return $this->doEncrypt($string, $iv);
}

/**
* @param string $string
* @param string $iv
*
* @return string
*/
protected function doEncrypt($string, $iv)
protected function doEncrypt(string $string, string $iv): string
{
return $iv.openssl_encrypt($string, self::ENCRYPTION_METHOD, $this->passphrase, 0, $iv);
}
Expand All @@ -81,11 +103,13 @@ protected function doEncrypt($string, $iv)
* Converts an encrypted string into a plain-text string
*
* @param string $encryptedMessage The encrypted string.
*
* @return string The plaint-text string.
*/
public function decrypt($encryptedMessage)
public function decrypt(string $encryptedMessage): string
{
$iv = substr($encryptedMessage, 0, self::IV_SIZE);

return openssl_decrypt(
substr($encryptedMessage, self::IV_SIZE),
self::ENCRYPTION_METHOD,
Expand All @@ -98,9 +122,10 @@ public function decrypt($encryptedMessage)

/**
* @param resource $encryptedStream
*
* @return null|string
*/
public function decryptStream($encryptedStream)
public function decryptStream($encryptedStream): ?string
{
if ($encryptedStream === null) {
return null;
Expand All @@ -111,11 +136,13 @@ public function decryptStream($encryptedStream)

/**
* @param string $passphrase The passphrase to be used to encrypt/decrypt data.
*
* @return void
*
* @throws \Exception If you attempt to initialize the cipher more than one time
* in a page-load via ::createInstance.
*/
public static function createInstance($passphrase)
public static function createInstance(string $passphrase): void
{
if (self::$instance !== null) {
throw new \Exception(
Expand All @@ -128,16 +155,18 @@ public static function createInstance($passphrase)

/**
* @return Cipher
*
* @throws \Exception if ::getInstance is called before cipher is initialized via ::createInstance.
*/
public static function getInstance()
public static function getInstance(): self
{
if (self::$instance === null) {
throw new \Exception(
'Cipher::getInstance() called before initialization. ' .
'Call Cipher::createInstance($passphrase) before ::getInstance().'
);
}

return self::$instance;
}
}
Loading