From f64228d9524a4d2fae81151a794371c551a2a669 Mon Sep 17 00:00:00 2001 From: Mathieu Girard Date: Fri, 5 May 2023 18:07:33 +0200 Subject: [PATCH] Add check on encryptor for already encrypted values --- src/Encryptors/CiphersweetEncryptor.php | 24 ++++++++++++++++++ src/Encryptors/EncryptorInterface.php | 2 ++ .../DoctrineCiphersweetSubscriber.php | 2 +- .../Encryptors/CiphersweetEncryptorTest.php | 25 +++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Encryptors/CiphersweetEncryptor.php b/src/Encryptors/CiphersweetEncryptor.php index f55b663..5c862ed 100644 --- a/src/Encryptors/CiphersweetEncryptor.php +++ b/src/Encryptors/CiphersweetEncryptor.php @@ -27,6 +27,18 @@ public function prepareForStorage(object $entity, string $fieldName, string $str { $entitClassName = \get_class($entity); + if ($this->isValueEncrypted($string)) { + // If the value is already encrypted and there is no need to get blind index, + // We return it as is. + if (!$index) { + return [$string, []]; + } + + // Otherwise, we try to decrypt it and we generate the corresponding Blind Index + $decryptedString = $this->decrypt($entitClassName, $fieldName, $string, $filterBits, $fastIndexing); + return [$string, [$fieldName.'_bi' => $this->getBlindIndex($entitClassName, $fieldName, $decryptedString, $filterBits, $fastIndexing)]]; + } + $output = []; if (isset($this->cache[$entitClassName][$fieldName][$string])) { $output[] = $this->cache[$entitClassName][$fieldName][$string]; @@ -65,6 +77,11 @@ protected function doEncrypt(string $entitClassName, string $fieldName, string $ public function decrypt(string $entity_classname, string $fieldName, string $string, int $filterBits = self::DEFAULT_FILTER_BITS, bool $fastIndexing = self::DEFAULT_FAST_INDEXING): string { + // If $string is not encrypted, we return it as is. + if (!$this->isValueEncrypted($string)) { + return $string; + } + if (isset($this->cache[$entity_classname][$fieldName][$string])) { return $this->cache[$entity_classname][$fieldName][$string]; } @@ -109,4 +126,11 @@ public function getPrefix(): string { return $this->engine->getBackend()->getPrefix(); } + + public function isValueEncrypted(?string $value): bool + { + return $value !== null && strpos($value, $this->getPrefix()) === 0; + } + + } diff --git a/src/Encryptors/EncryptorInterface.php b/src/Encryptors/EncryptorInterface.php index a883147..252298b 100644 --- a/src/Encryptors/EncryptorInterface.php +++ b/src/Encryptors/EncryptorInterface.php @@ -21,4 +21,6 @@ public function decrypt(string $entity_classname, string $fieldName, string $str public function getBlindIndex($entityName, $fieldName, string $value, int $filterBits = self::DEFAULT_FILTER_BITS, bool $fastIndexing = self::DEFAULT_FAST_INDEXING): string; public function getPrefix(): string; + + public function isValueEncrypted(?string $value): bool; } diff --git a/src/Subscribers/DoctrineCiphersweetSubscriber.php b/src/Subscribers/DoctrineCiphersweetSubscriber.php index 083c12c..b329dc2 100644 --- a/src/Subscribers/DoctrineCiphersweetSubscriber.php +++ b/src/Subscribers/DoctrineCiphersweetSubscriber.php @@ -380,7 +380,7 @@ private function handleDecryptOperation(int $oid, $value, \ReflectionProperty $r */ private function isValueEncrypted(?string $value): bool { - return $value !== null && strpos($value, $this->encryptor->getPrefix()) === 0; + return $this->encryptor->isValueEncrypted($value); } /** diff --git a/tests/Unit/Encryptors/CiphersweetEncryptorTest.php b/tests/Unit/Encryptors/CiphersweetEncryptorTest.php index 8a01c47..b59b756 100644 --- a/tests/Unit/Encryptors/CiphersweetEncryptorTest.php +++ b/tests/Unit/Encryptors/CiphersweetEncryptorTest.php @@ -57,4 +57,29 @@ public function testDecrypt() $this->assertSame('test', $result); $this->assertSame(0, $this->encryptor->callsCount['decrypt'], 'doDecrypt is never called because cache is set upon prepareForStorage call'); } + + public function testDecryptNonEncryptedValue() + { + [$encryptedString] = $this->encryptor->prepareForStorage(new MyEntity('132456'), 'account_name', 'test'); + + $decryptedString = $this->encryptor->decrypt(MyEntity::class, 'account_name', $encryptedString); + $untouchedString = $this->encryptor->decrypt(MyEntity::class, 'account_name', $decryptedString); + + $this->assertSame($decryptedString, $untouchedString); + $this->assertSame(1, $this->encryptor->callsCount['encrypt']); + $this->assertSame(0, $this->encryptor->callsCount['decrypt'], 'doDecrypt is never called either because of cache set upon prepareForStorage call or we detect value is already decrypted'); + } + + public function testEncryptAlreadyEncryptedValue() + { + [$encryptedString] = $this->encryptor->prepareForStorage(new MyEntity('132456'), 'account_name', 'test', false); + [$unTouchedEncryptedString] = $this->encryptor->prepareForStorage(new MyEntity('132456'), 'account_name', 'test', false); + [$unTouchedEncryptedStringBis, $bi] = $this->encryptor->prepareForStorage(new MyEntity('132456'), 'account_name', 'test', true); + + $this->assertSame($encryptedString, $unTouchedEncryptedString); + $this->assertSame($encryptedString, $unTouchedEncryptedStringBis); + $this->assertSame(8, mb_strlen($bi['account_name_bi'])); + $this->assertSame(1, $this->encryptor->callsCount['encrypt']); + $this->assertSame(0, $this->encryptor->callsCount['decrypt']); + } }