🔐 How to Encrypt And Decrypt a varchar with doctrine and Symfony

Ismaile ABDALLAH
2 min readNov 20, 2022

--

Photo by Towfiqu barbhuiya on Unsplash

Sometime we want to encrypt some data directly in database, using Symfony and doctrine. But we want to do it automatically, i mean we don’t want to encrypt manually the data before save it, and decrypt it, when we get the data.

By creating a new doctrine type we can encrypt and decrypt magically the data. Let see together the steps.

Step 1 : Create a service responsible for encryption and decryption

interface EncryptionEngineInterface
{
public function encrypt(string $data): string;

public function decrypt(string $data): string;
}

First of all, using an interface will make our life easier the day we want to change our encryption method.

Let use OpenSsl Encryption implementaion

final class EncryptionOpenSslEngine implements EncryptionEngineInterface
{
public function __construct(
private readonly string $sslCiphering = 'AES-128-CTR',
private readonly int $sslOption = 0,
private readonly string $sslEncryptionIv = '1234567891011121',
private readonly string $sslEncryptionKey = 'EncryptionKey',
) {
}

public function encrypt(string $data): string
{
return openssl_encrypt(
$data,
$this->sslCiphering,
$this->sslEncryptionKey,
$this->sslOption,
$this->sslEncryptionIv
);
}

public function decrypt(string $data): string
{
return openssl_decrypt(
$data,
$this->sslCiphering,
$this->sslEncryptionKey,
$this->sslOption,
$this->sslEncryptionIv
);
}
}

⚠️ Dont forget to have openssl php extension installed ⚠️

Step 2 : Create a new doctrine type

use Doctrine\DBAL\Types\StringType;

final class EncryptType extends StringType
{
public const NAME = 'encrypt';

private EncryptionEngineInterface $encryptionEngine;

public function setEncryptionEngine(EncryptionEngineInterface $encryptionEngine): void
{
$this->encryptionEngine = $encryptionEngine;
}

public function convertToDatabaseValue($value, AbstractPlatform $platform): string
{
return $this->encryptionEngine->encrypt($value);
}

public function convertToPHPValue($value, AbstractPlatform $platform): string
{
return $this->encryptionEngine->decrypt($value);
}

public function canRequireSQLConversion(): bool
{
return true;
}

public function getName(): string
{
return self::NAME;
}
}

Just override the both method convertToDatabaseValue and convertToPHPValue to encrypt and decrypt data by using EncryptionEngineInterface already created.

Step 3 : Declare your service and doctrine type

In your services.yaml

services:
App\EncryptionEngineInterface:
class: App\EncryptionOpenSslEngine
public: true

And in your kernel.php file, override boot method to declare your new doctrine type and inject your encryptionServiceInterface

public function boot()
{
parent::boot();
$this->addEncryptTypeToDoctrine();
}

private function addEncryptTypeToDoctrine(): void
{
if (Type::hasType(EncryptType::NAME) === false) {
/** @var EncryptionEngineInterface $encryptionEngine */
$encryptionEngine = self::getContainer()->get(EncryptionEngineInterface::class);
Type::addType(EncryptType::NAME, EncryptType::class);
/** @var EncryptType $encrypt */
$encrypt = Type::getType(EncryptType::NAME);
$encrypt->setEncryptionEngine($encryptionEngine);
}
}

Step 4 : Use your new doctrine type

#[ORM\Entity()]
class Entity
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;

#[ORM\Column(type: EncryptType::NAME)]
private string $secret;
}

Then use directly your type in you doctrine column definition, and the secret attribute will be encrypted before insert and decrypted when we get the entity.

Enjoy 😃, tell me your opinion in comment

--

--