πŸ” How to Encrypt And Decrypt a varchar with doctrine and Symfony

Ismaile ABDALLAH on 2022-11-19

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