🔐 How to Encrypt And Decrypt a varchar with doctrine and Symfony
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