Giter Club home page Giter Club logo

doctrineencryptbundle's People

Contributors

maks-rafalko avatar mkraemer avatar vmelnik-ukraine avatar westinpigott avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

doctrineencryptbundle's Issues

This is not AES

I'm having problems with this part:

mcrypt_encrypt(
    MCRYPT_RIJNDAEL_256, 
    $this->secretKey, 
    $data, 
    MCRYPT_MODE_ECB,     
    mcrypt_create_iv(
        mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB),
        MCRYPT_RAND
    )
)

MCRYPT_RIJNDAEL_256 is not AES (http://stackoverflow.com/questions/4537099/problem-with-aes-256-between-java-and-php/4539318#4539318).

The 256 in that constant refers to the blocksize, not the keysize. Use MCRYPT_RIJNDAEL_128 to get the same algorithm as AES. The keysize is set just by the number of bytes in the key argument you supply. So supply 32 bytes and you get AES with a 256-bit key.

MCRYPT_MODE_ECB doesn't use an IV (http://stackoverflow.com/questions/1789709/is-it-possible-to-use-aes-with-an-iv-in-ecb-mode). So why are you setting one?

Migrate existing data

Hi,

Is there a built-in way to update the existing data to be able to encode them in database?
My problem is that I can't loose the existing data and need to encode them.

One solution would be to create a Symfony command which would get the non-encoded data, retrieve the entity and use the regular setter.

Thanks,
Nicolas

improve event subscriber

It seems that preUpdate and prePersist events can cause some unwanted effects.

  • encryption should be done only when we are saving object to database
  • fields with annotation Encrypted should stay raw at object to allow further use
// some entity with secret field
$entityManager->persist($entity);

here secret field is already encrypted, so i can't use this entity further in my workflow/templates.

Empty field value throws exception

When I want to encrypt an optional field which can be null, I get the following error:

Field "myField" is not a valid field of the entity "XXX\XXXBundle\Entity\MyEntity" in PreUpdateEventArgs.

YAML ORM mapping declarations

Hi,

AnnotationReader is used to parse entities mapping through annotations.
Is it possible to use this bundle with YAML ORM mapping declarations?

Thanks.

fix bug with update of encrypted entity

When we create entity the prePersist lifecycle event is dispatches and encryption works good, but when we updating entity which already in our database, prePersist event not triggered for this entity. Need to use preUpdate event and changeSets.

Size of key error?

here is my error and i dont know how to solve it :(

ContextErrorException: Warning: mcrypt_encrypt(): Size of key is too large for this algorithm in **path/vendor/vmelnik/doctrine-encrypt-bundle/VMelnik/DoctrineEncryptBundle/Encryptors/AES256Encryptor.php line 34

thank you very much

Field "xxx" is not a valid field of the entity "AppBundle\Entity\Xxx" in PreUpdateEventArgs

Hi,

When I add @Encrypted annotation to a property in my entity, I get the following message on execution :
image

The record in DB is done, but there is still a problem with my entity Pass.
If I remove @Encrypted annotation, it works but the property is inevitably stored in clear in DB.

I followed your documentation to install and use the bundle.
So am I doing something wrong ? Or have I forgot something ?

PS: I work with Wamp in local

Check changeset

I propose that in DoctrineEncryptSubscriber::preUpdate() we check the entityChangeSet[] for a field before trying to update the field.

    foreach ($properties as $refProperty) {
        $propName = $refProperty->getName();

        if (isset($args->getEntityChangeSet()[$propName])) {
            if ($this->annReader->getPropertyAnnotation($refProperty, self::ENCRYPTED_ANN_NAME)) {

                $args->setNewValue($propName, $this->encryptor->encrypt($args->getNewValue($propName)));
            }
        }
    }

FindBy encrypted field

Hi,

Im trying finyby using encrypted field but seem doesnt work this way.

How can i do it?,

Regards!

Encryption key mannager

I need to be able to set an encryption key each time I set or get an entity (not a key from the config file).
I don't really see where or how I can do that.
I have tried to inehrit your bundle but I can't find a proper way to set a key each time I access a lead.

Do you have any idea how to do that?
thanks

add service for encrypt/decrypt operations

When we using hydration (array) in Doctrine, then life cycle events not triggered and we got encrypted values, not decrypted as expected. Maybe we need some services to do this in a manual way or some smart hook to do this automatic?

Error saving the entity twice in the same request

I had that error and I fixed it. I think the problem is you must decrypt properties after the entity has been persisted and flushed with encrypted values.

<?php

namespace VMelnik\DoctrineEncryptBundle\Subscribers;

use Doctrine\ORM\Events;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\Common\Annotations\Reader;
use \Doctrine\ORM\EntityManager;
use \ReflectionClass;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use VMelnik\DoctrineEncryptBundle\Encryptors\EncryptorInterface;

/**
 * Doctrine event subscriber which encrypt/decrypt entities
 */
class DoctrineEncryptSubscriber implements EventSubscriber {
    /**
     * Encryptor interface namespace 
     */

    const ENCRYPTOR_INTERFACE_NS = 'VMelnik\DoctrineEncryptBundle\Encryptors\EncryptorInterface';

    /**
     * Encrypted annotation full name
     */
    const ENCRYPTED_ANN_NAME = 'VMelnik\DoctrineEncryptBundle\Configuration\Encrypted';

    /**
     * Encryptor
     * @var EncryptorInterface 
     */
    private $encryptor;

    /**
     * @var LoggerInterface
     */
    protected $logger;    

    /**
     * Annotation reader
     * @var Doctrine\Common\Annotations\Reader
     */
    private $annReader;

    /**
     * @var boolean
     */
    private $encryptionDisabled = false;

    /**
     * @var boolean
     */
    private $debug = false;    

    /**
     * Registr to avoid multi decode operations for one entity
     * @var array
     */
    public static $decodedRegistry = array();

    /**
     * Capitalize string
     * @param string $word
     * @return string
     */
    public static function capitalize($word) {
        if (is_array($word)) {
            $word = $word[0];
        }

        return str_replace(' ', '', ucwords(str_replace(array('-', '_'), ' ', $word)));
    }

    /**
     * Check if we have entity in decoded registry
     * @param Object $entity Some doctrine entity
     * @param \Doctrine\ORM\EntityManager $em
     * @return boolean
     */
    public static function hasInDecodedRegistry($entity, EntityManager $em) {
        $className = get_class($entity);
        $metadata = $em->getClassMetadata($className);
        $getter = 'get' . self::capitalize($metadata->getIdentifier());

        return isset(self::$decodedRegistry[$className][$entity->$getter()]);
    }

    /**
     * Adds entity to decoded registry
     * @param object $entity Some doctrine entity
     * @param \Doctrine\ORM\EntityManager $em
     */
    public static function addToDecodedRegistry($entity, EntityManager $em) {
        $className = get_class($entity);
        $metadata = $em->getClassMetadata($className);
        $getter = 'get' . self::capitalize($metadata->getIdentifier());
        self::$decodedRegistry[$className][$entity->$getter()] = true;
    }

    /**
     * Delete entity from decoded registry
     * @param object $entity Some doctrine entity
     * @param \Doctrine\ORM\EntityManager $em
     */
    public static function removeFromDecodedRegistry($entity, EntityManager $em) {
        $className = get_class($entity);
        $metadata = $em->getClassMetadata($className);
        $getter = 'get' . self::capitalize($metadata->getIdentifier());
        unset(self::$decodedRegistry[$className][$entity->$getter()]);
    }    

    /**
     * Initialization of subscriber
     * @param string $encryptorClass  The encryptor class.  This can be empty if 
     * a service is being provided.
     * @param string $secretKey The secret key. 
     * @param EncryptorInterface|NULL $service (Optional)  An EncryptorInterface.
     * This allows for the use of dependency injection for the encrypters.
     */
    public function __construct(
            LoggerInterface $logger,
            Reader $annReader, 
            $encryptorClass, 
            $secretKey, 
            EncryptorInterface $service = NULL) 
    {
        $this->logger = $logger;
        $this->annReader = $annReader;

        if ($service instanceof EncryptorInterface) {
            $this->encryptor = $service;
        } else {
            $this->encryptor = $this->encryptorFactory($encryptorClass, $secretKey);
        }
    }

    private function beginLog($args, $method)
    {
        if (!$this->debug) return;

        $entity = $args->getEntity();
        $em = $args->getEntityManager();
        $entityClass = $this->getEntityClass($entity);
        $entityIdentifier = $this->getEntityIdentifier($entity, $em);

        $this->logger->info('DoctrineEncryptSubscriber::'.$method.' begin {entity_class} {entity_identifier}', array(
            'entity_class'      => $entityClass,
            'entity_identifier' => $entityIdentifier,
        ));
    }   

    private function endLog($args, $method)
    {
        if (!$this->debug) return;

        $entity = $args->getEntity();
        $em = $args->getEntityManager();
        $entityClass = $this->getEntityClass($entity);
        $entityIdentifier = $this->getEntityIdentifier($entity, $em);

        $this->logger->info('DoctrineEncryptSubscriber::'.$method.' end {entity_class} {entity_identifier} {decode_registry}', array(
            'entity_class'      => $entityClass,
            'entity_identifier' => $entityIdentifier,
            'decode_registry'   => print_r(self::$decodedRegistry, true),               
        ));
    }    

    private function getEntityClass($entity) 
    {
        $className = get_class($entity);
        return $className;
    }

    private function getEntityIdentifier($entity, EntityManager $em)
    {
        $className = get_class($entity);
        $metadata = $em->getClassMetadata($className);
        $getter = 'get' . self::capitalize($metadata->getIdentifier());

        return $entity->$getter();
    }

    /**
     * Listen a prePersist lifecycle event. Checking and encrypt entities
     * which have @Encrypted annotation
     * @param LifecycleEventArgs $lifecycleEventArgs 
     */
    public function prePersist(LifecycleEventArgs $lifecycleEventArgs) 
    {
        if ($this->encryptionDisabled) return;

        $this->beginLog($lifecycleEventArgs, 'prePersist');

        // First time the entity is persisted then it has not been decrypted before
        // and it must be encrypted before being inserted in database, even if it is is not in decodedRegistry
        $forceEncryptOperation = true;

        $this->encryptFields($lifecycleEventArgs, null, $forceEncryptOperation);

        $this->endLog($lifecycleEventArgs, 'prePersist');
    }

    /**
     * Listen a preUpdate lifecycle event. Checking and encrypt entities fields
     * which have @Encrypted annotation. Using changesets to avoid preUpdate event
     * restrictions
     * @param PreUpdateEventArgs $preUpdateEventArgs 
     */
    public function preUpdate(PreUpdateEventArgs $preUpdateEventArgs) 
    {
        if ($this->encryptionDisabled) return;

        $this->beginLog($preUpdateEventArgs, 'preUpdate');

        $this->encryptFields(null, $preUpdateEventArgs);

        $this->endLog($preUpdateEventArgs, 'preUpdate');
    }

    /**
     * Listen a postLoad lifecycle event. Checking and decrypt entities
     * which have @Encrypted annotations
     * @param LifecycleEventArgs $lifecycleEventArgs 
     */
    public function postLoad(LifecycleEventArgs $lifecycleEventArgs) 
    {
        if ($this->encryptionDisabled) return;

        $this->beginLog($lifecycleEventArgs, 'postLoad');

        $this->decryptFields($lifecycleEventArgs, null);

        $this->endLog($lifecycleEventArgs, 'postLoad');
    }

    public function postUpdate(LifecycleEventArgs $lifecycleEventArgs) 
    {
        if ($this->encryptionDisabled) return;

        $this->beginLog($lifecycleEventArgs, 'postUpdate');

        $this->decryptFields($lifecycleEventArgs, null);   

        $this->endLog($lifecycleEventArgs, 'postUpdate');
    }  

    public function postPersist(LifecycleEventArgs $lifecycleEventArgs) 
    {
        if ($this->encryptionDisabled) return;

        $this->beginLog($lifecycleEventArgs, 'postPersist');

        $this->decryptFields($lifecycleEventArgs, null);

        $this->endLog($lifecycleEventArgs, 'postPersist');
    }

    /**
     * Realization of EventSubscriber interface method.
     * @return Array Return all events which this subscriber is listening
     */
    public function getSubscribedEvents() {
        return array(
            Events::prePersist,
            Events::preUpdate,
            Events::postLoad,
            Events::postUpdate,
            Events::postPersist,
        );
    }

    private function processFields($lifecycleEventArgs, $preUpdateEventArgs, $isEncryptOperation, $forceEncryptOperation=false) 
    {
        if ($this->encryptionDisabled) return;

        $preUpdateEvent = false;

        if (!is_null($lifecycleEventArgs)) {
            $entity = $lifecycleEventArgs->getEntity();
            $em = $lifecycleEventArgs->getEntityManager();
        }

        if (!is_null($preUpdateEventArgs)) {
            $entity = $preUpdateEventArgs->getEntity();
            $em = $preUpdateEventArgs->getEntityManager();
            $preUpdateEvent = true;
        }       

        if (!$forceEncryptOperation) {

            // Entity already encrypted
            if ($isEncryptOperation && $this->isEncrypted($entity, $em)) return;

            // Entity already decrypted
            if (!$isEncryptOperation && !$this->isEncrypted($entity, $em)) return;
        }       

        $encryptorMethod = $isEncryptOperation ? 'encrypt' : 'decrypt';
        $reflectionClass = new ReflectionClass($entity);
        $properties = $reflectionClass->getProperties();
        $withAnnotation = false;  // Return if current entity has or not the annotation in any of its properties

        // TODO: add a method to the entity or interface to avoid iterate all properties
        foreach ($properties as $refProperty) {

            // Is this a encrypted property?
            if ($this->annReader->getPropertyAnnotation($refProperty, self::ENCRYPTED_ANN_NAME)) {

                $withAnnotation = true;

                // we have annotation and if it decrypt operation, we must avoid duble decryption
                $propName = $refProperty->getName();

                $methodName = self::capitalize($propName);

                if ($reflectionClass->hasMethod($getter = 'get' . $methodName) && $reflectionClass->hasMethod($setter = 'set' . $methodName)) {

                    // encrypt/decrypt property value
                    if ($preUpdateEvent) {
                        $currentPropValue = $preUpdateEventArgs->getNewValue($propName);
                    } else {
                        $currentPropValue = $entity->$getter();
                    }

                    // encrypt/decrypt property value
                    $newPropValue = $this->encryptor->$encryptorMethod($currentPropValue);

                    if (is_null($newPropValue))  {
                        $this->logger->error('DoctrineEncryptSubscriber::processFields '.$encryptorMethod.' null value {prop_name} {current_value} {new_value}', array(
                                'prop_name'     => $propName,
                                'current_value' => $currentPropValue,
                                'new_value'     => $newPropValue,
                        ));
                    }                   

                    // Set new property value
                    if ($preUpdateEvent) {
                        $preUpdateEventArgs->setNewValue($propName, $newPropValue);
                    }
                    $entity->$setter($newPropValue);

                    if ($isEncryptOperation) {
                        self::removeFromDecodedRegistry($entity, $em);
                    } else {
                        self::addToDecodedRegistry($entity, $em);
                    }

                    //if ($this->debug)  {
                        $this->logger->info('DoctrineEncryptSubscriber::processFields '.$encryptorMethod.' {prop_name} {current_value} {new_value}', array(
                                'prop_name'     => $propName,
                                'current_value' => $currentPropValue,
                                'new_value'     => $newPropValue,
                        ));
                    //}                 

                } else {
                    throw new \RuntimeException(sprintf("Property %s doesn't has getter/setter", $propName));
                }
            }
        }

        return $withAnnotation;
    }

    private function encryptFields($lifecycleEventArgs, $preUpdateEventArgs, $forceEncryptOperation=false) 
    {
        $this->processFields($lifecycleEventArgs, $preUpdateEventArgs, true, $forceEncryptOperation);
    }

    private function decryptFields($lifecycleEventArgs, $preUpdateEventArgs) 
    {
        $this->processFields($lifecycleEventArgs, $preUpdateEventArgs, false);
    }   

    /**
     * Encryptor factory. Checks and create needed encryptor
     * @param string $classFullName Encryptor namespace and name
     * @param string $secretKey Secret key for encryptor
     * @return EncryptorInterface
     * @throws \RuntimeException 
     */
    private function encryptorFactory($classFullName, $secretKey) {
        $refClass = new \ReflectionClass($classFullName);
        if ($refClass->implementsInterface(self::ENCRYPTOR_INTERFACE_NS)) {
            return new $classFullName($secretKey);
        } else {
            throw new \RuntimeException('Encryptor must implements interface EncryptorInterface');
        }
    }

    private function isEncrypted($entity, EntityManager $em) {
        return !$this->isDecrypted($entity, $em);
    }

    private function isDecrypted($entity, EntityManager $em) {
        return self::hasInDecodedRegistry($entity, $em);
    }    

}
...
    <services>
        <!-- Encryption service(subscriber) for encrypt/decrypt entities properties -->    
        <service id="vmelnik_doctrine_encrypt.orm_subscriber" class="%vmelnik_doctrine_encrypt.orm_subscriber.class%" public="false">
            <argument type="service" id="logger" />
            <argument type="service" id="annotation_reader" />
            <argument>%vmelnik_doctrine_encrypt.encryptor_class_name%</argument>
            <argument>%vmelnik_doctrine_encrypt.secret_key%</argument>
            <tag name="doctrine.event_subscriber" />
        </service>
        <service id="vmelnik_doctrine_encrypt.subscriber" alias="vmelnik_doctrine_encrypt.orm_subscriber" />
    </services>
...

Decrypt on join or manual select not working

When accessing decrypted column in a join, or when selecting manually, it will return the raw encrypted data.

i'm using the annotation

/**
 * @var string
 *
 * @ORM\Column(name="iban", type="text")
 * @Encrypted
 */
protected $iban;
queryBuilder = $this->objectManager->getRepository('CoreBundle:iban')->createQueryBuilder('i');
$data = $queryBuilder->getQuery()->getResult();

Works like a charm

queryBuilder = $this->objectManager->getRepository('CoreBundle:user')->createQueryBuilder('u');
$queryBuilder->join('u.ibans', 'i');
$data = $queryBuilder->getQuery()->getResult();

AND

queryBuilder = $this->objectManager->getRepository('CoreBundle:iban')->createQueryBuilder('i');
$queryBuilder->select('i.iban');
$data = $queryBuilder->getQuery()->getResult();

wont work

EncryptableInterface

..Where is the EncryptableInterface ??

i have this error , i dont see it in your repo ?

ClassNotFoundException: Attempted to load interface "EncryptableInterface" from namespace "*my-entity" in *mypath.php line 16. Do you need to "use" it from another namespace?

thx

configuration problem

I install bundle register and have

There is no extension able to load the configuration for "vmelnik_doctrine_encrypt"

i see registered "v_melnik_doctrine_encrypt",

How to update the Encryption Key

Hello,

I want to update the encryption key I am using, however there are too much details that I want to keep and they are encrypted (of course).

How can I decrypt all details, and then encrypt them using the new Key?

Thanks,

Ajax Input field shows encrypted DB value

Hi,
I'm creating a set of fields via ajax and loading + showing the inputs just after.
During the creation it set an encrypted field with a null value as the information hasn't been provided yet.
The input appears and shows "/hOTrAiAmMhXmKhkJ7hRGm24JL1T0Wkvj/JNm2uKAgM=" in the text value when loaded in ajax.
But if I reload the page (all inputs will show now since they exist in the DB) the encrypted fields shows correctly, it's empty as it's supposed to be.

allow to change secret token

If we add bundle to the worked project and our secret key is too long or bad in some other reason for encryption algorithm, we need to safely change it and update data in database. Need to write console command for this.

It must operate with to secret keys, old for decryption and new one to encrypt data.

add console command for db migration

Note to not forget: For new project with empty database we easily can add this bundle, but what if we have a worked project on production server and we must add this bundle? Now we need to clear db to toggle on this bundle, but it is not a solution. We need some kind of console command to allow add this bundle on worked project.

Command must do the following things:

  1. encrypt all data in database if we need to switch on bundle.
  2. decrypt all data in database if we need to switch off bundle.

Maybe we need to lock site while this operations are running?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.