Symfony 4 - 使用服务加密/解密学说中使用的字段?

Ele*_*ero 2 php encryption symfony symfony4

所以我的数据库中有一个实体的字段

class Person
{
    // other fields

    /**
     * @var string
     *
     * @ORM\Column(name="last_name", type="string", length=50, nullable=false)
     */
    private $lastName;

    /**
     * @var string
     *
     * @ORM\Column(name="first_name", type="string", length=50, nullable=false)
     */
    private $firstName;

   // getters and setters
}
Run Code Online (Sandbox Code Playgroud)

我有一个名为 SecureEncryptor 的服务。它具有 Decrypt() 和 Encrypt() 函数 - 基本上你只需将加密/未加密(分别)字符串传递给它,它就会执行相应的操作。

问题是我不确定如何将该服务与实体结合使用 - 特别是在考虑表单(类型)时。我的意思是我知道我可以只获取该字段并调用 Decrypt 函数,但这不适用于绑定到 Person 实体的类型。

我开始为解密的人制作一个单独的实体,然后在处理数据库时我会切换它,但这似乎是错误的。我的另一个想法是从实体调用服务,但我读过这也是错误的。

有任何想法吗?

编辑:

这基本上就是我想要做的:

$builder->get('dateOfBirth')
   ->addModelTransformer(new CallbackTransformer(
       function ($encryptedDOB) {
           return $this->encryptor->decrypt($encryptedDOB, salt); // How do I get the salt value here?
       },
      function ($decryptedDOB) {
         return $this->encryptor->encrypt($decryptedDOB, salt); // How do I get the salt value here?
      }
 ));
Run Code Online (Sandbox Code Playgroud)

或者可能在此步骤之前解密/加密数据,但不确定如何完成。

编辑2:

我发现表明您可以在 PRE_SET_DATA 事件中访问实体数据,但您无法在其中添加数据转换器,因此不确定它是如何工作的。

Ele*_*ero 7

经过三天的混乱 - 可能是 20 多个小时的沮丧之后,我终于找到了正确的方法来做到这一点。实体事件监听器

所以我做了以下改动

应用\配置\服务.yaml

parameters:
    ...
    encryption_key: '%kernel.project_dir%/path/to/my/key'

services:
    ...
    App\EventListeners\PatientListener:
        arguments: [@session]
        tags:
            - { name: doctrine.event_listener, event: prePersist }
            - { name: doctrine.event_listener, event: preUpdate }
            - { name: doctrine.event_listener, event: postLoad }
Run Code Online (Sandbox Code Playgroud)

然后我做了服务

<?php

namespace App\EventListeners;

use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Entity\Patients;
use ParagonIE\Halite\HiddenString;
use ParagonIE\Halite\KeyFactory;
use ParagonIE\Halite\Symmetric\Crypto as Symmetric;
use ParagonIE\Halite\Symmetric\EncryptionKey;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Session\Session;

class PatientListener
{
    private $params;
    private $session;
    private $logger;

    public function __construct(ParameterBagInterface $params, 
                                 Session $session, LoggerInterface $logger)
    {
        $this->params = $params;
        $this->session = $session;
        $this->logger = $logger;
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();

        if ($entity instanceof Patients)
        {
            $this->encryptFields($entity);
        }
    }

    public function preUpdate(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();

        if ($entity instanceof Patients)
        {
            $this->encryptFields($entity);
        }
    }

    public function postLoad(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();

        if ($entity instanceof Patients)
        {
            $this->decryptFields($entity);
        }
    }

    private function loadKey() : EncryptionKey
    {
        try
        {
            KeyFactory::loadEncryptionKey($this->params->get('encryption_key'));
        }
        catch(\Throwable $e)
        {
            $this->session->getFlashBag()->add('danger', 'Unable to load encryption key!');
            $this->logger->emergency(
                'Unable to lod the encryption key!', array(
                'error' => $e->getMessage(),
            ));
            throw;
        }
    }

    private function encryptFields(Patients $patient)
    {
        $key = $this->loadKey();

        // Encrypt the variables
        $lastName = $this->encrypt('Last Name', $patient->getLastName(), $key);

        // Set the entity variables
        $patient->setLastName($lastName);

        return $patient;
    }

    private function encrypt($fieldName, $value, $key)
    {
        try {
            return Symmetric::encrypt(
                new HiddenString($value),
                $key
            );
        } catch(\Throwable $e)
        {
            $this->session->getFlashBag()->add('danger', 'Unable to encrypt field');
            $this->logger->critical(
                'Unable to encrypt field "'.$fieldName.'" in Patients entity. DB update terminated.', array(
                'error' => $e->getMessage(),
            ));
            throw;
        }

    }

    private function decryptFields(Patients $patient)
    {
        $key = $this->loadKey();
        $id = $patient->getId();

        // Decrypt the variables
        $lastName = $this->decrypt($id, 'Last Name', $patient->getLastName(), $key);

        // Set the entity variables
        $patient->setLastName($lastName);
    }

    private function decrypt($id, $fieldName, $value, $key)
    {
        try
        {
            return Symmetric::decrypt($value, $key);
        }
        catch (\Throwable $e)
        {
            $this->session->getFlashBag()->add('warning', 'Unable to decrypt field');
            $this->logger->warning(
                'Unable to decrypt field "'.$fieldName.'" in Patients entity for ID: '.$id, array(
                'error' => $e->getMessage(),
            ));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在数据在加载到数据库时被加密,在加载到实体时被解密。

这种方式是正确的,因为以任何其他方式执行此操作(自定义原则类型、数据转换器、在控制器中执行等)总是有可能如果其他人制作另一种形式或在另一个控制器中使用该实体,它可能会离开数据解密(又名非常糟糕)。这种方式可确保数据始终通过原则正确加密和解​​密(除非您执行一些自定义 DQL\SQL,在这种情况下,您可能需要根据您的操作自行处理)。


M. *_*bza 2

如果我理解正确,您正在寻找以透明方式在数据库中加载/存储加密数据的方法。我想我会在加载/保存时实现自定义学说类型并解密/加密。

编辑:实现起来并不难,因为您可以使用Doctrine\DBAL\Types\TextType您将扩展的基础,您感兴趣的是convertToPHPValue()- 解密和convertToDatabaseValue()- 加密。如需更多示例,请查看学说类型定义并找到最适合您需求的一种。