vmelnik-ukraine / doctrineencryptbundle Goto Github PK
View Code? Open in Web Editor NEWBundle allows you to create doctrine entities with fields that will be protected by encryption algorithms such as AES
License: MIT License
Bundle allows you to create doctrine entities with fields that will be protected by encryption algorithms such as AES
License: MIT License
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?
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
When encrypting the username field of a User entity implementing the UserInterface, login authentication seems to be broken.
I apologize (again), same mistake as explained on a previous issue.
It seems that preUpdate
and prePersist
events can cause some unwanted effects.
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.
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.
Attempted to call function "mcrypt_decrypt" from namespace "VMelnik\DoctrineEncryptBundle\Encryptors".
An entity class that is Embeddable and has Encrypted property is ignored and value is stored as plain text without any encryption whatsoever
Hi,
AnnotationReader is used to parse entities mapping through annotations.
Is it possible to use this bundle with YAML ORM mapping declarations?
Thanks.
if we grep some entity from db more then one time per request, then postload event triggered two times and decryption processed two times, and we have bad entity values
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.
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
Hi,
When I add @Encrypted annotation to a property in my entity, I get the following message on execution :
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
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)));
}
}
}
Hi,
Im trying finyby using encrypted field but seem doesnt work this way.
How can i do it?,
Regards!
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
I write this issue because i don't know why i got this Warning: ReflectionProperty::getValue() expects exactly 1 parameter, 0 given Is i doing something wrong?... Help me!
null in database encrypted too but should not
The problem is the usage of trim() in https://github.com/vmelnik-ukraine/DoctrineEncryptBundle/blob/master/Encryptors/AES256Encryptor.php#L32.
It should be a rtrim() with a second parameter "\0". In rare cases the first or last character of the encrypted value could be a space or return, the decryption goes wrong
http://stackoverflow.com/questions/3422759/php-aes-encrypt-decrypt#comment13920165_3422787
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?
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>
...
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
..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
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",
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,
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.
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.
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:
Maybe we need to lock site while this operations are running?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.