How to use UniqueEntity constraint on an encrypted field?

157 views Asked by At

I'm working a project based on Symfony 4. I'm trying to make the @UniqueEntity works on an @Encrypted field, and I can't figure out how.

  • Without the @Encrypted annotation, the @UniqueEntity annotation prevents the duplication
  • With the @Encrypted annotation, the @UniqueEntity annotation allows the duplication
/**
 * @ORM\Entity(repositoryClass="App\Repository\DemoRepository")
 * @ORM\HasLifecycleCallbacks()
 * @UniqueEntity(
 *      fields={"example"},
 *      ignoreNull=true,
 * )
 *
 */
class Demo implements LoggableEntityInterface
{
    /**
     * @ORM\Column(type="text", nullable=true)
     * @Encrypted
     */
    private $example;
1

There are 1 answers

1
yivi On

First, I have no clue how that Encrypted package works, but if the content of the fields are encrypted it could be hard or impossible for the framework to check if the new value matches the the old one.

It would depend on how the field was encrypted, and how the check is made. The UniqueEntity is a validation check, so it's performed before the value is saved. It should first encrypt the value, and compare against the encrypted value. The Symfony annotation does not support that workflow.

You could store a hash for the field, the set the Unique check on the hash, not on the field. It would add quite a bit of storage to your table, though.

A naive implementation:

/**
 * @ORM\Entity
 * @UniqueEntity(
 *      fields={"emailHash"},
 *      ignoreNull=true,
 * )
 *
 */
class Foo {

 /**
  * @ORM\Column(length="128")
  * @Encrypted
  */
  private $email;

 /**
  * @ORM\Column(length="40")
  */
  private $emailHash;

  public function getEmail(): string
  {
        return $this->email; 
  }

  public function setEmail(string $email)
  {
        $this->email = $email;
        $this->emailHash = hash('sha1', $email . get_class($this));
  }
}

Now the check will be made against the hash, which won't be encrypted but should be relatively safe to store. Note that I'm also using the classname as a "salt", so the hash is slightly more secure.


Alternatively, you could use the repositoryMethod key of the annotation, and create a custom repository method that takes a raw value, encrypts it, and performs the search by the encrypted value. That would imply that you understand how to use whatever facility your encryption package is using, so you could use it in your new repository method.