6

I'm using Symfony 2 with Doctrine 2.

I need to encrypt a field in my entity using an encryption service, and I'm wondering where should I put this logic.

I'm using a Controller > Service > Repository architecture.

I was wondering if a listener would be a good idea, my main concern is, if my entity is stored encrypted, if I decrypt it on the fly its state it's gonna be changed and I'm not sure it's a good idea.

How would you implement this?

Trent
  • 5,785
  • 6
  • 32
  • 43

3 Answers3

17

To expand on richsage and targnation's great answers, one way to inject a dependency (e.g., cypto service) into a custom Doctrine mapping type, could be to use a static property and setter:

// MyBundle/Util/Crypto/Types/EncryptedString.php
class EncryptedString extends StringType
{
    /** @var \MyBundle\Util\Crypto */
    protected static $crypto;

    public static function setCrypto(Crypto $crypto)
    {
        static::$crypto = $crypto;
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        $value = parent::convertToDatabaseValue($value, $platform);
        return static::$crypto->encrypt($value);
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        $value = parent::convertToPHPValue($value, $platform);
        return static::$crypto->decrypt($value);
    }

    public function getName()
    {
        return 'encrypted_string';
    }
}

Configuration would look like this:

// MyBundle/MyBundle.php
class MyBundle extends Bundle
{
    public function boot()
    {
        /** @var \MyBundle\Util\Crypto $crypto */
        $crypto = $this->container->get('mybundle.util.crypto');
        EncryptedString::setCrypto($crypto);
    }
}

# app/Resources/config.yml
doctrine:
    dbal:
        types:
            encrypted_string: MyBundle\Util\Crypto\Types\EncryptedString

# MyBundle/Resources/config/services.yml
services:
    mybundle.util.crypto:
        class: MyBundle\Util\Crypto
        arguments: [ %key% ]
15

I don't know if it's the right way at all, but I implemented this recently by creating a custom mapping type, as per the Doctrine docs. Something like the following:

class EncryptedStringType extends TextType
{
    const MYTYPE = 'encryptedstring'; // modify to match your type name

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        return base64_decode($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return base64_encode($value);
    }

    public function getName()
    {
        return self::MYTYPE;
    }
}

I registered this type in my bundle class:

class MyOwnBundle extends Bundle
{
    public function boot()
    {
        $em = $this->container->get("doctrine.orm.entity_manager");
        try
        {
            Type::addType("encryptedstring", "My\OwnBundle\Type\EncryptedStringType");

            $em->
                getConnection()->
                getDatabasePlatform()->
                registerDoctrineTypeMapping("encryptedstring", "encryptedstring");
        } catch (\Doctrine\DBAL\DBALException $e)
        {
            // For some reason this exception gets thrown during
            // the clearing of the cache. I didn't have time to
            // find out why :-)
        }
    }
}

and then I was able to reference it when creating my entities, eg:

/**
 * @ORM\Column(type="encryptedstring")
 * @Assert\NotBlank()
 */
protected $name;

This was a quick implementation, so I'd be interested to know the correct way of doing it. I presume also that your encryption service is something available from the container; I don't know how feasible/possible it would be to pass services into custom types this way either... :-)

richsage
  • 26,912
  • 8
  • 58
  • 65
  • hi, thanks your answer, it's definitely a good idea, however I'm wondering how I could pass my private key? Let's say I pass my EncryptionService to my Custom type using the constructor, I still need to pass it a key to use for encryption, however, as the type is used when persisting & hydrating an entity, I don't how I could do that – Trent Dec 04 '11 at 13:14
  • Yeah, in our case we used the `mcrypt_*` functions and hardcoded the iv and passphrase etc due to time constraints, but I don't know if you could eg create the type as a service and pass parameters in that way. Seems to be a lack of information as to how to do this efficiently! – richsage Dec 04 '11 at 13:21
  • Yeah, I'm actually using the mcrypt_* functions, but the key to encrypt data is generated dynamically so I can't hardcode it. – Trent Dec 04 '11 at 13:23
  • @Trent did you ever find a way to pass the encryption key to the custom type? – Onema Jan 23 '14 at 00:27
  • 2
    FWIW a static IV (shared across all encryptions) opens you up to a host of problems: http://security.stackexchange.com/questions/1093/forced-into-using-a-static-iv-aes – Mark Fox Nov 02 '14 at 03:25
  • @MarkFox absolutely agree, +1 – richsage Nov 03 '14 at 13:43
8

richsage's answer was pretty good, except I wouldn't register the custom type in the bundle class file. It's recommended that you use the config.yml like so:

# ./app/config/confi
doctrine:
    dbal:
        driver:   "%database_driver%"
        {{ etc, etc }}
        types:
            encrypted_string: MyCompany\MyBundle\Type\EncryptedStringType

Then just make sure in your EncryptedStringType class you specify the getName function to return encrypted_string.

Now in your model definition (or annotation) you can use the encrypted_string type.

targnation
  • 917
  • 11
  • 11