Symfony 4-使用服务对原则中使用的字段进行加密/解密?

时间:2018-10-02 23:13:57

标签: php symfony encryption 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
}

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

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

我开始为解密的人员创建一个单独的实体,然后在需要处理数据库时将其切换,但这似乎是错误的。我的另一个想法是从Entity调用服务,但我也读错了。

有什么想法吗?

编辑:

这基本上是我想要做的:

$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?
      }
 ));

,或者在此步骤之前解密/加密数据,但不确定如何实现。

编辑2:

我发现this显示可以在PRE_SET_DATA事件中获取实体数据,但是您无法在其中添加数据转换器,因此不确定如何工作。

2 个答案:

答案 0 :(得分:0)

如果我的理解正确,您正在寻找以透明方式在数据库中加载/存储加密数据的方法。我想我应该实现custom doctrine type并在加载/保存时在此处解密/加密。

编辑:实现起来并不难,您可以使用将扩展的Doctrine\DBAL\Types\TextType作为基础,在那里您对convertToPHPValue()-解密和convertToDatabaseValue()-加密感兴趣。要获取更多示例,请查看和定义类型,然后找到最适合您需求的示例。

答案 1 :(得分:0)

搞砸了三天后-大概20多个小时的挫败感,我终于找到了正确的方法。 Entity Event Listeners

所以我进行了以下更改

app \ config \ services.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 }

然后我提供了服务

<?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(),
            ));
        }
    }
}

,现在将数据加载到数据库中时进行加密,而将数据加载到实体中时进行解密。

这种方法是正确的,因为以任何其他方式(自定义教义类型,数据转换器,在控制器中执行此操作等)进行操作,总是有机会使他人进行其他形式的操作或在该控制器中使用该实体可能会使数据解密(也就是非常糟糕)。这样可以确保通过教义始终对数据进行正确的加密和解密(除非您执行一些自定义DQL \ SQL,在这种情况下,您可能需要根据自己的操作进行处理)。