Skip to content

Instantly share code, notes, and snippets.

@PhilETaylor
Last active October 16, 2024 20:04
Show Gist options
  • Save PhilETaylor/6efbdd007ac55098ee87b500bc7611be to your computer and use it in GitHub Desktop.
Save PhilETaylor/6efbdd007ac55098ee87b500bc7611be to your computer and use it in GitHub Desktop.
<?php
// Just a random example entity
// /vendor/philetaylor/base/src/Entity/Changelog.php
namespace Base\Entity;
use Base\Doctrine\Encryption\Types\Encrypted;
use Base\Repository\ChangelogRepository;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface;
use Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface;
use Knp\DoctrineBehaviors\Contract\Entity\UuidableInterface;
use Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait;
use Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
use Knp\DoctrineBehaviors\Model\Uuidable\UuidableTrait;
/**
* Changelog.
*/
#[ORM\Table(name: 'changelog')]
#[ORM\Index(name: 'posted', columns: ['posted'])]
#[ORM\Entity(repositoryClass: ChangelogRepository::class)]
class Changelog implements BlameableInterface, TimestampableInterface, UuidableInterface
{
use BlameableTrait;
use TimestampableTrait;
use UuidableTrait;
#[ORM\Column(name: 'id', type: Types::INTEGER)]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private ?int $id = null;
#[ORM\Column(name: 'posted', type: Types::DATETIME_MUTABLE)]
private ?DateTime $posted = null;
#[ORM\Column(name: 'type', type: Types::STRING, length: 255)]
private ?string $type = null;
#[ORM\Column(name: '`change`', type: Encrypted::ENCRYPTED)]
private ?string $change = null;
#[ORM\Column(name: 'published', type: Types::INTEGER)]
private ?int $published = null;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
public function getPosted(): DateTime
{
return $this->posted;
}
public function setPosted(DateTime $posted): void
{
$this->posted = $posted;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): void
{
$this->type = $type;
}
public function getChange(): ?string
{
return $this->change;
}
public function setChange(string $change): void
{
$this->change = $change;
}
public function getPublished(): int
{
return $this->published;
}
public function setPublished(int $published): void
{
$this->published = $published;
}
}
<?php
// /vendor/philetaylor/base/src/Doctrine/Encryption/Types/Encrypted.php
namespace Base\Doctrine\Encryption\Types;
use Attribute;
use Base\Doctrine\Encryption\HaliteEncryptor;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Override;
#[Attribute]
class Encrypted extends Type
{
final public const string ENCRYPTED = 'encrypted';
/**
* The encryptor to use for encrypting and decrypting values.
*
* @var HaliteEncryptor
*/
public static $encryptor;
#[Override]
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return $platform->getClobTypeDeclarationSQL($fieldDeclaration);
}
#[Override]
public function getName(): string
{
return self::ENCRYPTED;
}
#[Override]
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
return $this->getEncryptor()->encrypt($value);
}
#[Override]
public function convertToPHPValue($value, AbstractPlatform $platform): ?string
{
return $this->getEncryptor()->decrypt($value);
}
#[Override]
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}
public function getEncryptor(): HaliteEncryptor
{
if (self::$encryptor===null) {
self::$encryptor = new HaliteEncryptor();
}
return self::$encryptor;
}
}
<?php
// /vendor/philetaylor/base/src/Doctrine/Encryption/HaliteEncryptor.php
namespace Base\Doctrine\Encryption;
use ParagonIE\Halite\KeyFactory;
use ParagonIE\Halite\Symmetric\Crypto;
use ParagonIE\Halite\Symmetric\EncryptionKey;
use ParagonIE\HiddenString\HiddenString;
class HaliteEncryptor
{
private readonly EncryptionKey $enc_key;
public function __construct()
{
$this->enc_key = KeyFactory::loadEncryptionKey(__DIR__ . '/../../../../../../' . '.encryptionkeys/live_encryption_key_2018.key');
}
public function encrypt(?string $data = null): ?string
{
if ($data === '' || $data===null || $data === 'null') {
return null;
}
// if we are forcing a decrypt
if (str_ends_with($data, '<DONOTENCRYPT>')) {
return substr($data, 0, \strlen($data) - 14);
}
// already encrypted!
if (str_ends_with($data, '<Ha>')) {
return $data;
}
return Crypto::encrypt(new HiddenString($data), $this->enc_key) . '<Ha>';
}
public function decrypt(?string $ciphertext = null): ?string
{
if ($ciphertext === '' || $ciphertext===null || $ciphertext === 'null') {
return null;
}
if (str_ends_with($ciphertext, '<Ha>')) {
$plaintext = Crypto::decrypt(substr($ciphertext, 0, \strlen($ciphertext) - 4), $this->enc_key);
return $plaintext->getString();
}
return $ciphertext ?: null;
}
}
@PhilETaylor
Copy link
Author

Also need to register the new type in your config files

<?php
// config/packages/doctrine_encrypt.php
declare(strict_types=1);

use Base\Doctrine\Encryption\Types\Encrypted;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Config\DoctrineConfig;

return static function (DoctrineConfig $doctrine, ContainerConfigurator $configurator) {
    $dbal = $doctrine->dbal();
    $dbal->type(Encrypted::ENCRYPTED)->class(Encrypted::class);
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment